Skip to content

Commit

Permalink
add withdraw eth token test code, eliminate path_to_coin for eth coin
Browse files Browse the repository at this point in the history
  • Loading branch information
dimxy committed Dec 7, 2023
1 parent 12b4836 commit af7684b
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 66 deletions.
11 changes: 8 additions & 3 deletions mm2src/coins/eth/eth_withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ where
.map_to_mm(WithdrawError::InvalidAddress)?;
let (my_balance, my_address, key_pair, derivation_path) = match req.from {
Some(from) => {
let path_to_coin = coin.priv_key_policy.path_to_coin_or_err()?;
let path_to_coin = &coin
.deref()
.derivation_method
.hd_wallet()
.ok_or(WithdrawError::UnexpectedDerivationMethod)?
.derivation_path;
let path_to_address = from.to_address_path(path_to_coin.coin_type())?;
let derivation_path = path_to_address.to_derivation_path(path_to_coin)?;
let (key_pair, address) = match coin.priv_key_policy {
Expand All @@ -60,9 +65,9 @@ where
} => {
let my_pubkey = activated_pubkey
.as_ref()
.or_mm_err(|| WithdrawError::InternalError("no pubkey from trezor".to_string()))?;
.or_mm_err(|| WithdrawError::InternalError("empty trezor xpub".to_string()))?;
let my_pubkey = pubkey_from_xpub_str(my_pubkey)
.map_err(|_| WithdrawError::InternalError("invalid xpub from trezor".to_string()))?;
.map_to_mm(|_| WithdrawError::InternalError("invalid trezor xpub".to_string()))?;
let address = public_to_address(&my_pubkey);
(None, address)
},
Expand Down
21 changes: 7 additions & 14 deletions mm2src/coins/eth/v2_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ impl EthCoin {
}
}

/// Activate eth coin from coin config and and private key build policy,
/// version 2 with no intrinsic tokens creation
/// Activate eth coin from coin config and private key build policy,
/// version 2 of the activation function, with no intrinsic tokens creation
pub async fn eth_coin_from_conf_and_request_v2(
ctx: &MmArc,
ticker: &str,
Expand Down Expand Up @@ -309,20 +309,17 @@ pub async fn eth_coin_from_conf_and_request_v2(
build_address_and_priv_key_policy(ctx, ticker, conf, priv_key_policy, &req.path_to_address, req.gap_limit)
.await?;
let enabled_address = match priv_key_policy {
PrivKeyPolicy::Trezor {
path_to_coin: _,
ref activated_pubkey,
} => {
PrivKeyPolicy::Trezor { ref activated_pubkey } => {
let my_pubkey = activated_pubkey
.as_ref()
.or_mm_err(|| EthActivationV2Error::InternalError("no pubkey from trezor".to_string()))?;
.or_mm_err(|| EthActivationV2Error::InternalError("empty trezor xpub".to_string()))?;
let my_pubkey = pubkey_from_xpub_str(my_pubkey)
.map_err(|_| EthActivationV2Error::InternalError("invalid xpub from trezor".to_string()))?;
.map_to_mm(|_| EthActivationV2Error::InternalError("invalid trezor xpub".to_string()))?;
public_to_address(&my_pubkey)
},
_ => priv_key_policy
.activated_key_or_err()
.map_err(|e| EthActivationV2Error::PrivKeyPolicyNotAllowed(e.into_inner()))?
.mm_err(EthActivationV2Error::PrivKeyPolicyNotAllowed)?
.address(),
};
let enabled_address_str = display_eth_address(&enabled_address);
Expand All @@ -339,10 +336,7 @@ pub async fn eth_coin_from_conf_and_request_v2(
},
) => build_http_transport(ctx, ticker.to_string(), enabled_address_str, key_pair, &req.nodes).await?,
(EthRpcMode::Http, EthPrivKeyPolicy::Trezor { .. }) => {
/*return MmError::err(EthActivationV2Error::PrivKeyPolicyNotAllowed(
PrivKeyPolicyNotAllowed::HardwareWalletNotSupported,
));*/
// for now in-memory privkey which must be always initialised if trezor policy is set
// Get in-memory internal privkey, which for now must be initialised if trezor policy is set
let crypto_ctx = CryptoCtx::from_ctx(ctx)?;
let secp256k1_key_pair = crypto_ctx.mm2_internal_key_pair();
let eth_key_pair = eth::KeyPair::from_secret_slice(&secp256k1_key_pair.private_bytes())
Expand Down Expand Up @@ -513,7 +507,6 @@ pub(crate) async fn build_address_and_priv_key_policy(
.await?;
Ok((
EthPrivKeyPolicy::Trezor {
path_to_coin: Some(path_to_coin),
activated_pubkey: Some(my_pubkey),
},
derivation_method,
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/hd_wallet/withdraw_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl WithdrawFrom {
let coin_type = derivation_path.coin_type();
if coin_type != expected_coin_type {
let error = format!(
"Derivation path '{}' must has '{}' coin type",
"Derivation path '{}' must have '{}' coin type",
derivation_path, expected_coin_type
);
return MmError::err(HDWithdrawError::UnexpectedFromAddress(error));
Expand Down
49 changes: 31 additions & 18 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ pub struct TransactionDetails {
/// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction
pub tx_hex: BytesJson,
/// Transaction hash in hexadecimal format
tx_hash: String,
pub tx_hash: String,
/// Coins are sent from these addresses
from: Vec<String>,
/// Coins are sent to these addresses
Expand Down Expand Up @@ -2219,6 +2219,8 @@ pub enum WithdrawError {
DbError(String),
#[display(fmt = "chain id not set: {}", _0)]
ChainIdRequired(String),
#[display(fmt = "Must use hierarchical deterministic wallet")]
UnexpectedDerivationMethod,
}

impl HttpStatusCode for WithdrawError {
Expand Down Expand Up @@ -2249,9 +2251,10 @@ impl HttpStatusCode for WithdrawError {
WithdrawError::HwError(_) => StatusCode::GONE,
#[cfg(target_arch = "wasm32")]
WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST,
WithdrawError::Transport(_) | WithdrawError::InternalError(_) | WithdrawError::DbError(_) => {
StatusCode::INTERNAL_SERVER_ERROR
},
WithdrawError::Transport(_)
| WithdrawError::InternalError(_)
| WithdrawError::DbError(_)
| WithdrawError::UnexpectedDerivationMethod => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
Expand Down Expand Up @@ -3062,11 +3065,9 @@ pub enum PrivKeyPolicy<T> {
/// Details about how the keys are managed with the Trezor device
/// are abstracted away and are not directly managed by this policy.
Trezor {
/// path to coin for Trezor, user only for eth. TODO: could we get this from trezor itself?
path_to_coin: Option<StandardHDPathToCoin>,
/// pubkey for initially derived account, used for Eth only
/// TODO: maybe better get pubkey each time when it is needed instead of storing here.
/// Also now it is stored as base58 encodes. Maybe better to store as a binary type Secp256k1ExtendedPublicKey
/// TODO: maybe better get the pubkey each time when it is needed instead of storing here.
/// Also now it is stored as base58. Maybe better to store as a binary type Secp256k1ExtendedPublicKey
activated_pubkey: Option<String>,
},
/// The Metamask private key policy, specific to the WASM target architecture.
Expand Down Expand Up @@ -3138,13 +3139,14 @@ impl<T> PrivKeyPolicy<T> {
path_to_coin: derivation_path,
..
} => Some(derivation_path),
PrivKeyPolicy::Trezor { path_to_coin, .. } => path_to_coin.as_ref(),
PrivKeyPolicy::Trezor { .. } => None,
PrivKeyPolicy::Iguana(_) => None,
#[cfg(target_arch = "wasm32")]
PrivKeyPolicy::Metamask(_) => None,
}
}

// TODO: eliminate and use hdwallet.derivation_path
fn path_to_coin_or_err(&self) -> Result<&StandardHDPathToCoin, MmError<PrivKeyPolicyNotAllowed>> {
self.path_to_coin().or_mm_err(|| {
PrivKeyPolicyNotAllowed::UnsupportedMethod(
Expand All @@ -3162,12 +3164,7 @@ impl<T> PrivKeyPolicy<T> {
.mm_err(|e| PrivKeyPolicyNotAllowed::InternalError(e.to_string()))
}

fn is_trezor(&self) -> bool {
matches!(self, PrivKeyPolicy::Trezor {
path_to_coin: _,
activated_pubkey: _
})
}
fn is_trezor(&self) -> bool { matches!(self, PrivKeyPolicy::Trezor { activated_pubkey: _ }) }
}

/// 'CoinWithPrivKeyPolicy' trait is used to get the private key policy of a coin.
Expand Down Expand Up @@ -3289,6 +3286,13 @@ where
///
/// Panic if the address mode is [`DerivationMethod::HDWallet`].
pub async fn unwrap_single_addr(&self) -> Address { self.single_addr_or_err().await.unwrap() }

/*pub fn derivation_path(&self) -> Option<&StandardHDPathToCoin> {
match self {
DerivationMethod::SingleAddress(_) => None,
DerivationMethod::HDWallet(hd_wallet) => Some(hd_wallet.derivation_path()),
}
}*/
}

/// A trait representing coins with specific address derivation methods.
Expand Down Expand Up @@ -4643,11 +4647,20 @@ pub mod for_tests {
from_derivation_path: &str,
fee: Option<WithdrawFee>,
) -> MmResult<TransactionDetails, WithdrawError> {
println!(
"amount={} BigDecimal::from_str(amount).unwrap()={}",
amount,
BigDecimal::from_str(amount).unwrap()
);
let withdraw_req = WithdrawRequest {
amount: BigDecimal::from_str(amount).unwrap(),
from: Some(WithdrawFrom::DerivationPath {
derivation_path: from_derivation_path.to_owned(),
}),
from: if !from_derivation_path.is_empty() {
Some(WithdrawFrom::DerivationPath {
derivation_path: from_derivation_path.to_owned(),
})
} else {
None
},
to: to.to_owned(),
coin: ticker.to_owned(),
max: false,
Expand Down
5 changes: 1 addition & 4 deletions mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,7 @@ pub trait UtxoFieldsWithHardwareWalletBuilder: UtxoCoinBuilderCommonOps {
decimals,
dust_amount,
rpc_client,
priv_key_policy: PrivKeyPolicy::Trezor {
path_to_coin: None,
activated_pubkey: None,
},
priv_key_policy: PrivKeyPolicy::Trezor { activated_pubkey: None },
derivation_method: DerivationMethod::HDWallet(hd_wallet),
history_sync_state: Mutex::new(initial_history_state),
tx_cache,
Expand Down
13 changes: 5 additions & 8 deletions mm2src/coins_activation/src/platform_coin_with_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,10 +367,11 @@ where
EnablePlatformCoinWithTokensError: From<Platform::ActivationError>,
(Platform::ActivationError, EnablePlatformCoinWithTokensError): NotEqual,
{
enable_platform_coin_with_tokens_within_rpc::<Platform>(ctx, None, req).await
// TODO check if we must disable trezor here as it is a non rpc call
enable_platform_coin_with_tokens_impl::<Platform>(ctx, None, req).await
}

pub async fn enable_platform_coin_with_tokens_within_rpc<Platform>(
pub async fn enable_platform_coin_with_tokens_impl<Platform>(
ctx: MmArc,
task_handle: Option<&RpcTaskHandle<InitPlatformCoinWithTokensTask<Platform>>>,
req: EnablePlatformCoinWithTokensReq<Platform::ActivationRequest>,
Expand Down Expand Up @@ -459,12 +460,8 @@ where
async fn cancel(self) {}

async fn run(&mut self, task_handle: &RpcTaskHandle<Self>) -> Result<Self::Item, MmError<Self::Error>> {
enable_platform_coin_with_tokens_within_rpc::<Platform>(
self.ctx.clone(),
Some(task_handle),
self.request.clone(),
)
.await
enable_platform_coin_with_tokens_impl::<Platform>(self.ctx.clone(), Some(task_handle), self.request.clone())
.await
}
}

Expand Down
48 changes: 36 additions & 12 deletions mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7810,8 +7810,8 @@ mod trezor_tests {
use mm2_core::mm_ctx::MmArc;
use mm2_main::mm2::init_hw::init_trezor_user_action;
use mm2_main::mm2::init_hw::{init_trezor, init_trezor_status, InitHwRequest, InitHwResponse};
use mm2_test_helpers::for_tests::{eth_sepolia_trezor_firmware_compat_conf, mm_ctx_with_custom_db_with_conf,
ETH_DEV_SWAP_CONTRACT, ETH_SEPOLIA_NODE};
use mm2_test_helpers::for_tests::{eth_sepolia_trezor_firmware_compat_conf, jst_sepolia_trezor_conf,
mm_ctx_with_custom_db_with_conf, ETH_DEV_SWAP_CONTRACT, ETH_SEPOLIA_NODE};
use rpc_task::{rpc_common::RpcTaskStatusRequest, RpcTaskStatus};
use serde_json::{self as json, json, Value as Json};
use std::io::{stdin, stdout, BufRead, Write};
Expand All @@ -7832,7 +7832,7 @@ mod trezor_tests {
let res = match init_trezor(ctx.clone(), req).await {
Ok(res) => res,
_ => {
panic!("cannot init trezor");
panic!("cannot start init trezor task");
},
};

Expand All @@ -7851,8 +7851,7 @@ mod trezor_tests {
break;
},
RpcTaskStatus::Error(_) => {
log!("device in error state");
break;
panic!("cannot init trezor device");
},
RpcTaskStatus::InProgress(_) => log!("trezor init in progress"),
RpcTaskStatus::UserActionRequired(device_req) => {
Expand Down Expand Up @@ -7928,7 +7927,7 @@ mod trezor_tests {
println!("account_balance={:?}", account_balance);
}

/// Tool to run eth withdraw directly with trezor device or emulator (no-rpc version, added for easier debugging)
/// Test to run eth withdraw directly with trezor device or emulator (for checking or debugging)
/// run cargo test with '--features run-device-tests' option
/// to use trezor emulator also add '--features trezor-udp' option to cargo params
#[test]
Expand All @@ -7939,10 +7938,9 @@ mod trezor_tests {

let ticker = "ETH";

let mut eth_conf = eth_sepolia_trezor_firmware_compat_conf();
eth_conf["mm2"] = 2.into();
let mm_conf = json!({ "coins": [eth_conf] });

let eth_conf = eth_sepolia_trezor_firmware_compat_conf();
let jst_conf = jst_sepolia_trezor_conf();
let mm_conf = json!({ "coins": [eth_conf, jst_conf] });
let ctx = block_on(mm_ctx_with_trezor(mm_conf));
block_on(init_platform_coin_with_tokens_loop::<EthCoin>(
ctx.clone(),
Expand All @@ -7954,7 +7952,7 @@ mod trezor_tests {
{"url": "https://rpc.sepolia.org/"}
],
"swap_contract_address": ETH_DEV_SWAP_CONTRACT,
"erc20_tokens_requests": [],
"erc20_tokens_requests": [{"ticker": "JST"}],
"priv_key_policy": "Trezor"
}))
.unwrap(),
Expand All @@ -7976,8 +7974,9 @@ mod trezor_tests {
.unwrap();
println!("account_balance={:?}", account_balance);

// try to create eth withdrawal tx
let tx_details = block_on(test_withdraw_init_loop(
ctx,
ctx.clone(),
ticker,
"0xc06eFafa6527fc4b3C8F69Afb173964A3780a104",
"0.00001",
Expand All @@ -7989,6 +7988,31 @@ mod trezor_tests {
))
.expect("withdraw must end successfully");
log!("tx_hex={}", serde_json::to_string(&tx_details.tx_hex).unwrap());

// try to create JST ERC20 token withdrawal tx
let tx_details = block_on(test_withdraw_init_loop(
ctx,
"JST",
"0xbAB36286672fbdc7B250804bf6D14Be0dF69fa29",
"0.000000000000000001", // 1 wei
"m/44'/1'/0'/0/0", // Note: Trezor uses 1' type for all testnets
Some(WithdrawFee::EthGas {
gas: ETH_GAS,
gas_price: 0.1_f32.try_into().unwrap(),
}),
))
.expect("withdraw must end successfully");
log!("tx_hex={}", serde_json::to_string(&tx_details.tx_hex).unwrap());

// if you need to send tx:
/* let send_tx_res = block_on(send_raw_transaction(ctx, json!({
"coin": "JST",
"tx_hex": tx_details.tx_hex,
})));
assert!(send_tx_res.is_ok(), "!{} send: {:?}", "JST", send_tx_res);
if send_tx_res.is_ok() {
println!("tx_hash={}", tx_details.tx_hash);
} */
// TODO: check maybe we need to disconnect trezor somehow
}
}
18 changes: 18 additions & 0 deletions mm2src/mm2_test_helpers/src/for_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,24 @@ pub fn jst_sepolia_conf() -> Json {
})
}

pub fn jst_sepolia_trezor_conf() -> Json {
json!({
"coin": "JST",
"name": "jst",
"chain_id": 11155111,
"derivation_path": "m/44'/1'", // Note: Trezor uses 1' coin type for all testnets
"trezor_coin": "tETH",
"protocol": {
"type": "ERC20",
"protocol_data": {
"platform": "ETH",
"chain_id": 11155111,
"contract_address": ETH_SEPOLIA_TOKEN_CONTRACT // 0x948BF5172383F1Bc0Fdf3aBe0630b855694A5D2c" // Yet another JST test contract on Sepolia network
}
}
})
}

pub fn iris_testnet_conf() -> Json {
json!({
"coin": "IRIS-TEST",
Expand Down
Loading

0 comments on commit af7684b

Please sign in to comment.