Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hd_wallet): utxo and evm hd wallet and trezor #1962

Merged
merged 49 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d961a14
wip: account_balance, my_tx_history v2, get_new_address, withdraw for…
shamardy Sep 1, 2023
bfcc3c9
Specify account index when creating a new account, test enabling of U…
shamardy Sep 5, 2023
fe64afa
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Sep 5, 2023
8d6980e
fix trade_test_rick_and_morty and trade_test_electrum_and_eth_coins
shamardy Sep 5, 2023
19e29d0
get swaps working again for HD UTXO
shamardy Sep 6, 2023
c6aa703
revert changes to EnabledCoinBalanceParams
shamardy Sep 6, 2023
5e0f90d
derive addresses up to the one to be enabled
shamardy Sep 6, 2023
dacfc9b
remove unneeded derive_secp256k1_secret_from_full_path function
shamardy Sep 7, 2023
0597876
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Sep 7, 2023
d8f2b42
remove StandardHDCoinAddress and use HDAccountAddressId instead + ref…
shamardy Sep 7, 2023
9aa2b88
use chain from the activation request in enable_hd_wallet
shamardy Sep 7, 2023
38afa8e
remove unneeded todos
shamardy Sep 7, 2023
de14171
remove hd_wallet_derived_pubkey function
shamardy Sep 7, 2023
6cf756e
refactor xpub_extractor to be used only when trezor
shamardy Sep 7, 2023
10513c4
fix wasm tests
shamardy Sep 7, 2023
44813a2
Ignore tests that still need implementations and remove unneeded todos
shamardy Sep 7, 2023
d56d2ea
handle HDAccountAddressId::to_derivation_path errors
shamardy Sep 12, 2023
fe7b84e
update get_enabled_address doc comment
shamardy Sep 12, 2023
fb70886
return error if from is used in withdraw slp request
shamardy Sep 12, 2023
596d08b
use HDAccountAddressId::default() where applicable
shamardy Sep 12, 2023
b993e20
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Sep 12, 2023
48b1db1
remove CoinWithDerivationMethod associated types to reduce bulky cons…
shamardy Sep 13, 2023
a52a432
add doc comments for UtxoHDWallet
shamardy Sep 14, 2023
fdcf99a
add doc comments for HDWalletCoinOps
shamardy Sep 14, 2023
2329403
add doc comments for HDWalletOps
shamardy Sep 14, 2023
7676840
add doc comments for HDAccountOps
shamardy Sep 14, 2023
d05a732
add doc comments for PrivKeyPolicy
shamardy Sep 14, 2023
6777947
add doc comments for DerivationMethod
shamardy Sep 14, 2023
394290f
add doc comments for CoinWithDerivationMethod
shamardy Sep 14, 2023
ee54ff7
allow missing docs in for_tests.rs
shamardy Sep 14, 2023
6b751b8
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Sep 22, 2023
ea96823
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Oct 4, 2023
88c7906
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Oct 27, 2023
cb08701
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Oct 27, 2023
bdd71bb
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Nov 28, 2023
f0e486b
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Jan 30, 2024
b2cd2a5
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Feb 14, 2024
3d517b1
Merge remote-tracking branch 'origin/dev' into hd-account-balance
shamardy Feb 28, 2024
97f6bd6
Merge remote-tracking branch 'dev' into hd-account-balance
shamardy Mar 8, 2024
aed4c7a
Merge remote-tracking branch 'dev' into hd-account-balance
shamardy Apr 2, 2024
25b146e
Merge remote-tracking branch 'dev' into hd-account-balance
shamardy Apr 16, 2024
186bc95
feat(hd_wallet): evm hd wallet and trezor (#1979)
shamardy Apr 22, 2024
93a2cd2
Merge remote-tracking branch 'dev' into hd-account-balance
shamardy Apr 22, 2024
a6b133d
Review fixes: streamline derivation path structs names
shamardy Apr 23, 2024
8761221
try using wasm_bindgen_test_runner for wasm CI tests
shamardy Apr 23, 2024
920e2db
fix: remove verbose attribute in wasm test command
shamardy Apr 23, 2024
4e50190
remove wasm_bindgen_test_runner and increase timeout instead
shamardy Apr 23, 2024
d00e2e9
Merge remote-tracking branch 'dev' into hd-account-balance
shamardy Apr 25, 2024
2f17f7b
fix fmt
shamardy Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 40 additions & 28 deletions mm2src/coins/coin_balance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::hd_pubkey::HDXPubExtractor;
use crate::hd_wallet::{HDAccountOps, HDAddressId, HDWalletCoinOps, HDWalletOps, NewAccountCreatingError,
NewAddressDerivingError};
use crate::hd_wallet::{HDAccountAddressId, HDAccountOps, HDAddressId, HDWalletCoinOps, HDWalletOps,
NewAccountCreatingError, NewAddressDerivingError};
use crate::{BalanceError, BalanceResult, CoinBalance, CoinWithDerivationMethod, DerivationMethod, HDAddress,
MarketCoinOps};
use async_trait::async_trait;
Expand Down Expand Up @@ -120,11 +120,8 @@ pub trait CoinBalanceReportOps {
#[async_trait]
impl<Coin> CoinBalanceReportOps for Coin
where
Coin: CoinWithDerivationMethod<HDWallet = <Coin as HDWalletCoinOps>::HDWallet>
+ HDWalletBalanceOps
+ MarketCoinOps
+ Sync,
<Coin as CoinWithDerivationMethod>::Address: fmt::Display + Sync,
Coin: CoinWithDerivationMethod + HDWalletBalanceOps + MarketCoinOps + Sync,
<Coin as HDWalletCoinOps>::Address: fmt::Display + Sync,
{
async fn coin_balance_report(&self) -> BalanceResult<CoinBalanceReport> {
match self.derivation_method() {
Expand All @@ -151,29 +148,28 @@ where
pub trait EnableCoinBalanceOps {
async fn enable_coin_balance<XPubExtractor>(
&self,
xpub_extractor: &XPubExtractor,
xpub_extractor: Option<XPubExtractor>,
params: EnabledCoinBalanceParams,
path_to_address: &HDAccountAddressId,
) -> MmResult<CoinBalanceReport, EnableCoinBalanceError>
where
XPubExtractor: HDXPubExtractor;
XPubExtractor: HDXPubExtractor + Send;
}

#[async_trait]
impl<Coin> EnableCoinBalanceOps for Coin
where
Coin: CoinWithDerivationMethod<HDWallet = <Coin as HDWalletCoinOps>::HDWallet>
+ HDWalletBalanceOps
+ MarketCoinOps
+ Sync,
<Coin as CoinWithDerivationMethod>::Address: fmt::Display + Sync,
Coin: CoinWithDerivationMethod + HDWalletBalanceOps + MarketCoinOps + Sync,
<Coin as HDWalletCoinOps>::Address: fmt::Display + Sync,
{
async fn enable_coin_balance<XPubExtractor>(
&self,
xpub_extractor: &XPubExtractor,
xpub_extractor: Option<XPubExtractor>,
params: EnabledCoinBalanceParams,
path_to_address: &HDAccountAddressId,
) -> MmResult<CoinBalanceReport, EnableCoinBalanceError>
where
XPubExtractor: HDXPubExtractor,
XPubExtractor: HDXPubExtractor + Send,
{
match self.derivation_method() {
DerivationMethod::SingleAddress(my_address) => self
Expand All @@ -188,7 +184,7 @@ where
})
.mm_err(EnableCoinBalanceError::from),
DerivationMethod::HDWallet(hd_wallet) => self
.enable_hd_wallet(hd_wallet, xpub_extractor, params)
.enable_hd_wallet(hd_wallet, xpub_extractor, params, path_to_address)
.await
.map(CoinBalanceReport::HD),
}
Expand All @@ -207,11 +203,12 @@ pub trait HDWalletBalanceOps: HDWalletCoinOps {
async fn enable_hd_wallet<XPubExtractor>(
&self,
hd_wallet: &Self::HDWallet,
xpub_extractor: &XPubExtractor,
xpub_extractor: Option<XPubExtractor>,
params: EnabledCoinBalanceParams,
path_to_address: &HDAccountAddressId,
) -> MmResult<HDWalletBalance, EnableCoinBalanceError>
where
XPubExtractor: HDXPubExtractor;
XPubExtractor: HDXPubExtractor + Send;

/// Scans for the new addresses of the specified `hd_account` using the given `address_scanner`.
/// Returns balances of the new addresses.
Expand Down Expand Up @@ -344,6 +341,7 @@ pub mod common_impl {
coin: &Coin,
hd_wallet: &Coin::HDWallet,
hd_account: &mut Coin::HDAccount,
chain: Bip44Chain,
address_scanner: &Coin::HDAddressScanner,
scan_new_addresses: bool,
min_addresses_number: Option<u32>,
Expand All @@ -362,7 +360,7 @@ pub mod common_impl {
}

if let Some(min_addresses_number) = min_addresses_number {
gen_new_addresses(coin, hd_wallet, hd_account, &mut addresses, min_addresses_number).await?
gen_new_addresses(coin, hd_wallet, hd_account, chain, &mut addresses, min_addresses_number).await?
}

let total_balance = addresses.iter().fold(CoinBalance::default(), |total, addr_balance| {
Expand All @@ -381,13 +379,14 @@ pub mod common_impl {
pub(crate) async fn enable_hd_wallet<Coin, XPubExtractor>(
coin: &Coin,
hd_wallet: &Coin::HDWallet,
xpub_extractor: &XPubExtractor,
xpub_extractor: Option<XPubExtractor>,
params: EnabledCoinBalanceParams,
path_to_address: &HDAccountAddressId,
) -> MmResult<HDWalletBalance, EnableCoinBalanceError>
where
Coin: HDWalletBalanceOps + MarketCoinOps + Sync,
Coin::Address: fmt::Display,
XPubExtractor: HDXPubExtractor,
XPubExtractor: HDXPubExtractor + Send,
{
let mut accounts = hd_wallet.get_accounts_mut().await;
let address_scanner = coin.produce_hd_address_scanner().await?;
Expand All @@ -396,7 +395,7 @@ pub mod common_impl {
accounts: Vec::with_capacity(accounts.len() + 1),
};

if accounts.is_empty() {
if accounts.get(&path_to_address.account_id).is_none() {
// Is seems that we couldn't find any HD account from the HD wallet storage.
drop(accounts);
info!(
Expand All @@ -405,7 +404,9 @@ pub mod common_impl {
);

// Create new HD account.
let mut new_account = coin.create_new_account(hd_wallet, xpub_extractor).await?;
let mut new_account = coin
.create_new_account(hd_wallet, xpub_extractor, Some(path_to_address.account_id))
.await?;
let scan_new_addresses = matches!(
params.scan_policy,
EnableCoinScanPolicy::ScanIfNewWallet | EnableCoinScanPolicy::Scan
Expand All @@ -415,11 +416,13 @@ pub mod common_impl {
coin,
hd_wallet,
&mut new_account,
path_to_address.chain,
&address_scanner,
scan_new_addresses,
params.min_addresses_number,
params.min_addresses_number.max(Some(path_to_address.address_id + 1)),
)
.await?;
// Todo: The enabled address should be indicated in the response.
result.accounts.push(account_balance);
return Ok(result);
}
Expand All @@ -430,14 +433,23 @@ pub mod common_impl {
coin.ticker()
);
let scan_new_addresses = matches!(params.scan_policy, EnableCoinScanPolicy::Scan);
for (_account_id, hd_account) in accounts.iter_mut() {
for (account_id, hd_account) in accounts.iter_mut() {
let min_addresses_number = if *account_id == path_to_address.account_id {
// The account for the enabled address is already indexed.
// But in case the address index is larger than the number of derived addresses,
// we need to derive new addresses to make sure that the enabled address is indexed.
params.min_addresses_number.max(Some(path_to_address.address_id + 1))
} else {
params.min_addresses_number
};
let account_balance = enable_hd_account(
coin,
hd_wallet,
hd_account,
path_to_address.chain,
&address_scanner,
scan_new_addresses,
params.min_addresses_number,
min_addresses_number,
)
.await?;
result.accounts.push(account_balance);
Expand All @@ -451,6 +463,7 @@ pub mod common_impl {
coin: &Coin,
hd_wallet: &Coin::HDWallet,
hd_account: &mut Coin::HDAccount,
chain: Bip44Chain,
result_addresses: &mut Vec<HDAddressBalance>,
min_addresses_number: u32,
) -> MmResult<(), EnableCoinBalanceError>
Expand All @@ -473,7 +486,6 @@ pub mod common_impl {
}

let to_generate = min_addresses_number - actual_addresses_number;
let chain = hd_wallet.default_receiver_chain();
let ticker = coin.ticker();
let account_id = hd_account.account_id();
info!("Generate '{to_generate}' addresses: ticker={ticker} account_id={account_id}, chain={chain:?}");
Expand Down
22 changes: 11 additions & 11 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use common::{get_utc_timestamp, now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY};
#[cfg(target_arch = "wasm32")]
use common::{now_ms, wait_until_ms};
use crypto::privkey::key_pair_from_secret;
use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy, StandardHDCoinAddress};
use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy};
use derive_more::Display;
use enum_from::EnumFromStringify;
use ethabi::{Contract, Function, Token};
Expand Down Expand Up @@ -110,7 +110,8 @@ use crate::nft::{find_wallet_nft_amount, WithdrawNftResult};
use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error};

mod nonce;
use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom};
use crate::hd_wallet::HDAccountAddressId;
use crate::{PrivKeyPolicy, TransactionResult};
use nonce::ParityNonce;

/// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol
Expand Down Expand Up @@ -709,21 +710,18 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult {
.address_from_str(&req.to)
.map_to_mm(WithdrawError::InvalidAddress)?;
let (my_balance, my_address, key_pair) = match req.from {
Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => {
Some(from) => {
let path_to_coin = coin.priv_key_policy.path_to_coin_or_err()?;
let path_to_address = from.to_address_path(path_to_coin.coin_type())?;
let raw_priv_key = coin
.priv_key_policy
.hd_wallet_derived_priv_key_or_err(path_to_address)?;
.hd_wallet_derived_priv_key_or_err(&path_to_address.to_derivation_path(path_to_coin)?)?;
let key_pair = KeyPair::from_secret_slice(raw_priv_key.as_slice())
.map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?;
let address = key_pair.address();
let balance = coin.address_balance(address).compat().await?;
(balance, address, key_pair)
},
Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => {
return MmError::err(WithdrawError::UnexpectedFromAddress(
"Withdraw from 'AddressId' or 'DerivationPath' is not supported yet for EVM!".to_string(),
))
},
None => (
coin.my_balance().compat().await?,
coin.my_address,
Expand Down Expand Up @@ -2144,6 +2142,8 @@ impl MarketCoinOps for EthCoin {
let pow = self.decimals / 3;
MmNumber::from(1) / MmNumber::from(10u64.pow(pow as u32))
}

fn is_trezor(&self) -> bool { self.priv_key_policy.is_trezor() }
}

pub fn signed_eth_tx_from_bytes(bytes: &[u8]) -> Result<SignedEthTx, String> {
Expand Down Expand Up @@ -5168,7 +5168,7 @@ pub async fn eth_coin_from_conf_and_request(
}
let contract_supports_watchers = req["contract_supports_watchers"].as_bool().unwrap_or_default();

let path_to_address = try_s!(json::from_value::<Option<StandardHDCoinAddress>>(
let path_to_address = try_s!(json::from_value::<Option<HDAccountAddressId>>(
req["path_to_address"].clone()
))
.unwrap_or_default();
Expand Down Expand Up @@ -5437,7 +5437,7 @@ pub async fn get_eth_address(
ctx: &MmArc,
conf: &Json,
ticker: &str,
path_to_address: &StandardHDCoinAddress,
path_to_address: &HDAccountAddressId,
) -> MmResult<MyWalletAddress, GetEthAddressError> {
let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?;
// Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible.
Expand Down
24 changes: 15 additions & 9 deletions mm2src/coins/eth/v2_activation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::*;
use crate::hd_wallet::HDAccountAddressId;
#[cfg(target_arch = "wasm32")] use crate::EthMetamaskPolicy;
use common::executor::AbortedError;
use crypto::{CryptoCtxError, StandardHDCoinAddress};
use crypto::CryptoCtxError;
use enum_from::EnumFromTrait;
use mm2_err_handle::common_errors::WithInternal;
#[cfg(target_arch = "wasm32")]
Expand All @@ -13,6 +14,7 @@ pub enum EthActivationV2Error {
InvalidPayload(String),
InvalidSwapContractAddr(String),
InvalidFallbackSwapContract(String),
InvalidPathToAddress(String),
#[display(fmt = "Expected either 'chain_id' or 'rpc_chain_id' to be set")]
#[cfg(target_arch = "wasm32")]
ExpectedRpcChainId,
Expand Down Expand Up @@ -100,7 +102,7 @@ pub struct EthActivationV2Request {
#[serde(default)]
pub priv_key_policy: EthPrivKeyActivationPolicy,
#[serde(default)]
pub path_to_address: StandardHDCoinAddress,
pub path_to_address: HDAccountAddressId,
}

#[derive(Clone, Deserialize)]
Expand Down Expand Up @@ -334,7 +336,7 @@ pub async fn eth_coin_from_conf_and_request_v2(
pub(crate) async fn build_address_and_priv_key_policy(
conf: &Json,
priv_key_policy: EthPrivKeyBuildPolicy,
path_to_address: &StandardHDCoinAddress,
path_to_address: &HDAccountAddressId,
) -> MmResult<(Address, EthPrivKeyPolicy), EthActivationV2Error> {
match priv_key_policy {
EthPrivKeyBuildPolicy::IguanaPrivKey(iguana) => {
Expand All @@ -344,17 +346,21 @@ pub(crate) async fn build_address_and_priv_key_policy(
},
EthPrivKeyBuildPolicy::GlobalHDAccount(global_hd_ctx) => {
// Consider storing `derivation_path` at `EthCoinImpl`.
let derivation_path = json::from_value(conf["derivation_path"].clone())
let path_to_coin = json::from_value(conf["derivation_path"].clone())
.map_to_mm(|e| EthActivationV2Error::ErrorDeserializingDerivationPath(e.to_string()))?;
let raw_priv_key = global_hd_ctx
.derive_secp256k1_secret(&derivation_path, path_to_address)
.derive_secp256k1_secret(
&path_to_address
.to_derivation_path(&path_to_coin)
.mm_err(|e| EthActivationV2Error::InvalidPathToAddress(e.to_string()))?,
)
.mm_err(|e| EthActivationV2Error::InternalError(e.to_string()))?;
let activated_key_pair = KeyPair::from_secret_slice(raw_priv_key.as_slice())
let activated_key = KeyPair::from_secret_slice(raw_priv_key.as_slice())
.map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string()))?;
let bip39_secp_priv_key = global_hd_ctx.root_priv_key().clone();
Ok((activated_key_pair.address(), EthPrivKeyPolicy::HDWallet {
derivation_path,
activated_key: activated_key_pair,
Ok((activated_key.address(), EthPrivKeyPolicy::HDWallet {
path_to_coin,
activated_key,
bip39_secp_priv_key,
}))
},
Expand Down
13 changes: 2 additions & 11 deletions mm2src/coins/hd_pubkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ pub trait ExtractExtendedPubkey {

async fn extract_extended_pubkey<XPubExtractor>(
&self,
xpub_extractor: &XPubExtractor,
xpub_extractor: Option<XPubExtractor>,
derivation_path: DerivationPath,
) -> MmResult<Self::ExtendedPublicKey, HDExtractPubkeyError>
where
XPubExtractor: HDXPubExtractor;
XPubExtractor: HDXPubExtractor + Send;
}

#[async_trait]
Expand Down Expand Up @@ -146,15 +146,6 @@ where
})
}

/// Constructs an Xpub extractor without checking if the MarketMaker is initialized with a hardware wallet.
pub fn new_unchecked(
ctx: &MmArc,
task_handle: &'task RpcTaskHandle<Task>,
statuses: HwConnectStatuses<Task::InProgressStatus, Task::AwaitingStatus>,
) -> XPubExtractorUnchecked<RpcTaskXPubExtractor<'task, Task>> {
XPubExtractorUnchecked(Self::new(ctx, task_handle, statuses))
}

async fn extract_utxo_xpub_from_trezor(
hw_ctx: &HardwareWalletArc,
task_handle: &RpcTaskHandle<Task>,
Expand Down
Loading
Loading