From c7638a578cee3063c30df201039fb6725fe135df Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 12 Jun 2024 17:34:06 +0300 Subject: [PATCH 01/31] custom token activation as wallet only for evm and tendermint --- mm2src/coins/lightning/ln_conf.rs | 2 +- mm2src/coins/lp_coins.rs | 2 +- mm2src/coins/tendermint/tendermint_coin.rs | 2 +- mm2src/coins/tendermint/tendermint_token.rs | 3 +- mm2src/coins/utxo.rs | 2 +- mm2src/coins/z_coin.rs | 6 +- mm2src/coins_activation/src/init_token.rs | 26 +++++++- mm2src/coins_activation/src/l2/init_l2.rs | 2 +- .../coins_activation/src/l2/init_l2_error.rs | 3 + .../src/platform_coin_with_tokens.rs | 62 +++++++++++++++++-- mm2src/coins_activation/src/prelude.rs | 53 +++++++++++++--- .../standalone_coin/init_standalone_coin.rs | 2 +- .../init_standalone_coin_error.rs | 3 + mm2src/coins_activation/src/token.rs | 27 +++++++- 14 files changed, 168 insertions(+), 27 deletions(-) diff --git a/mm2src/coins/lightning/ln_conf.rs b/mm2src/coins/lightning/ln_conf.rs index 80c5047d62..0b155b488b 100644 --- a/mm2src/coins/lightning/ln_conf.rs +++ b/mm2src/coins/lightning/ln_conf.rs @@ -1,7 +1,7 @@ use crate::utxo::BlockchainNetwork; use lightning::util::config::{ChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct PlatformCoinConfirmationTargets { pub background: u32, pub normal: u32, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 463028a635..ee52098a2d 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4179,7 +4179,7 @@ pub trait IguanaBalanceOps { } #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "type", content = "protocol_data")] pub enum CoinProtocol { UTXO, diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index f073d31d29..14ab2d94f4 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -145,7 +145,7 @@ pub struct TendermintFeeDetails { pub gas_limit: u64, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct TendermintProtocolInfo { decimals: u8, denom: String, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 894982aa83..3810ddfa9d 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -60,9 +60,10 @@ impl Deref for TendermintToken { fn deref(&self) -> &Self::Target { &self.0 } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct TendermintTokenProtocolInfo { pub platform: String, + // Todo: can decimals be gotten from rpc call for custom tokens? pub decimals: u8, pub denom: String, } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index ebd7f72f29..91f19454f3 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -420,7 +420,7 @@ impl RecentlySpentOutPoints { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub enum BlockchainNetwork { #[serde(rename = "mainnet")] Mainnet, diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index d9a416d204..bb550f17f0 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -138,7 +138,7 @@ cfg_native!( const SAPLING_OUTPUT_EXPECTED_HASH: &str = "2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"; ); -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ZcoinConsensusParams { // we don't support coins without overwinter and sapling active so these are mandatory overwinter_activation_height: u32, @@ -155,7 +155,7 @@ pub struct ZcoinConsensusParams { b58_script_address_prefix: [u8; 2], } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct CheckPointBlockInfo { height: u32, hash: H256Json, @@ -163,7 +163,7 @@ pub struct CheckPointBlockInfo { sapling_tree: BytesJson, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ZcoinProtocolInfo { consensus_params: ZcoinConsensusParams, check_point_block: Option, diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index dbc03b1754..279e495d95 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -37,6 +37,7 @@ pub type CancelInitTokenError = CancelRpcTaskError; #[derive(Debug, Deserialize, Clone)] pub struct InitTokenReq { ticker: String, + protocol: Option, activation_params: T, } @@ -90,11 +91,13 @@ where InitTokenError: From, (Token::ActivationError, InitTokenError): NotEqual, { + // Todo: we should check if the contract has been enabled before in case the user is re-enabling the coin using a different ticker if let Ok(Some(_)) = lp_coinfind(&ctx, &request.ticker).await { return MmError::err(InitTokenError::TokenIsAlreadyActivated { ticker: request.ticker }); } - let (_, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &request.ticker)?; + let (_, token_protocol): (_, Token::ProtocolInfo) = + coin_conf_with_protocol(&ctx, &request.ticker, request.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) .await @@ -299,6 +302,17 @@ pub enum InitTokenError { TokenProtocolParseError { ticker: String, error: String }, #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + #[display( + fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", + ticker, + from_config, + from_request + )] + ProtocolMissMatch { + ticker: String, + from_config: CoinProtocol, + from_request: CoinProtocol, + }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] TokenCreationError { ticker: String, error: String }, #[display(fmt = "Could not fetch balance: {}", _0)] @@ -331,6 +345,15 @@ impl From for InitTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokenError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::ProtocolMissMatch { + ticker, + from_config, + from_request, + } => InitTokenError::ProtocolMissMatch { + ticker, + from_config, + from_request, + }, } } } @@ -353,6 +376,7 @@ impl HttpStatusCode for InitTokenError { | InitTokenError::TokenConfigIsNotFound { .. } | InitTokenError::TokenProtocolParseError { .. } | InitTokenError::UnexpectedTokenProtocol { .. } + | InitTokenError::ProtocolMissMatch { .. } | InitTokenError::TokenCreationError { .. } | InitTokenError::PlatformCoinIsNotActivated(_) => StatusCode::BAD_REQUEST, InitTokenError::TaskTimedOut { .. } => StatusCode::REQUEST_TIMEOUT, diff --git a/mm2src/coins_activation/src/l2/init_l2.rs b/mm2src/coins_activation/src/l2/init_l2.rs index 20e66ebbde..e6b0888700 100644 --- a/mm2src/coins_activation/src/l2/init_l2.rs +++ b/mm2src/coins_activation/src/l2/init_l2.rs @@ -79,7 +79,7 @@ where return MmError::err(InitL2Error::L2IsAlreadyActivated(ticker)); } - let (coin_conf_json, protocol_conf): (Json, L2::ProtocolInfo) = coin_conf_with_protocol(&ctx, &ticker)?; + let (coin_conf_json, protocol_conf): (Json, L2::ProtocolInfo) = coin_conf_with_protocol(&ctx, &ticker, None)?; let coin_conf = L2::coin_conf_from_json(coin_conf_json)?; let platform_coin = lp_coinfind_or_err(&ctx, protocol_conf.platform_coin_ticker()) diff --git a/mm2src/coins_activation/src/l2/init_l2_error.rs b/mm2src/coins_activation/src/l2/init_l2_error.rs index d23fd73078..ef3f318105 100644 --- a/mm2src/coins_activation/src/l2/init_l2_error.rs +++ b/mm2src/coins_activation/src/l2/init_l2_error.rs @@ -62,6 +62,9 @@ impl From for InitL2Error { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitL2Error::UnexpectedL2Protocol { ticker, protocol } }, + CoinConfWithProtocolError::ProtocolMissMatch { ticker, .. } => { + InitL2Error::Internal(format!("Protocol from request is not supported for {}", ticker)) + }, } } } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 051dd22fc3..edf5caed83 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -38,6 +38,7 @@ pub type InitPlatformCoinWithTokensTaskManagerShared = #[derive(Clone, Debug, Deserialize)] pub struct TokenActivationRequest { ticker: String, + protocol: Option, #[serde(flatten)] request: Req, } @@ -92,8 +93,19 @@ pub enum InitTokensAsMmCoinsError { CouldNotFetchBalance(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), Internal(String), - TokenProtocolParseError { ticker: String, error: String }, - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + TokenProtocolParseError { + ticker: String, + error: String, + }, + UnexpectedTokenProtocol { + ticker: String, + protocol: CoinProtocol, + }, + ProtocolMissMatch { + ticker: String, + from_config: CoinProtocol, + from_request: CoinProtocol, + }, Transport(String), InvalidPayload(String), } @@ -111,6 +123,16 @@ impl From for InitTokensAsMmCoinsError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokensAsMmCoinsError::UnexpectedTokenProtocol { ticker, protocol } }, + + CoinConfWithProtocolError::ProtocolMissMatch { + ticker, + from_config, + from_request, + } => InitTokensAsMmCoinsError::ProtocolMissMatch { + ticker, + from_config, + from_request, + }, } } } @@ -138,7 +160,7 @@ where let token_params = tokens_requests .into_iter() .map(|req| -> Result<_, MmError> { - let (_, protocol): (_, T::TokenProtocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (_, protocol): (_, T::TokenProtocol) = coin_conf_with_protocol(&ctx, &req.ticker, req.protocol)?; Ok(TokenActivationParams { ticker: req.ticker, activation_request: req.request, @@ -260,6 +282,17 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, protocol: CoinProtocol, }, + #[display( + fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", + ticker, + from_config, + from_request + )] + ProtocolMissMatch { + ticker: String, + from_config: CoinProtocol, + from_request: CoinProtocol, + }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] PlatformCoinCreationError { ticker: String, @@ -300,6 +333,15 @@ impl From for EnablePlatformCoinWithTokensError { error: err.to_string(), } }, + CoinConfWithProtocolError::ProtocolMissMatch { + ticker, + from_config, + from_request, + } => EnablePlatformCoinWithTokensError::ProtocolMissMatch { + ticker, + from_config, + from_request, + }, } } } @@ -327,6 +369,15 @@ impl From for EnablePlatformCoinWithTokensError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) => { EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(e.to_string()) }, + InitTokensAsMmCoinsError::ProtocolMissMatch { + ticker, + from_config, + from_request, + } => EnablePlatformCoinWithTokensError::ProtocolMissMatch { + ticker, + from_config, + from_request, + }, } } } @@ -373,7 +424,8 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::NoSuchTask(_) | EnablePlatformCoinWithTokensError::UnexpectedDeviceActivationPolicy | EnablePlatformCoinWithTokensError::FailedSpawningBalanceEvents(_) - | EnablePlatformCoinWithTokensError::UnexpectedTokenProtocol { .. } => StatusCode::BAD_REQUEST, + | EnablePlatformCoinWithTokensError::UnexpectedTokenProtocol { .. } + | EnablePlatformCoinWithTokensError::ProtocolMissMatch { .. } => StatusCode::BAD_REQUEST, EnablePlatformCoinWithTokensError::Transport(_) => StatusCode::BAD_GATEWAY, } } @@ -449,7 +501,7 @@ where )); } - let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker, None)?; let platform_coin = Platform::enable_platform_coin( ctx.clone(), diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 36fe5aa43b..b203b58a9b 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -4,6 +4,7 @@ use coins::sia::SiaCoinActivationParams; use coins::utxo::UtxoActivationParams; use coins::z_coin::ZcoinActivationParams; use coins::{coin_conf, CoinBalance, CoinProtocol, DerivationMethodResponse, MmCoinEnum}; +use common::drop_mutability; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; @@ -64,8 +65,19 @@ pub trait TryFromCoinProtocol { #[derive(Debug)] pub enum CoinConfWithProtocolError { ConfigIsNotFound(String), - CoinProtocolParseError { ticker: String, err: json::Error }, - UnexpectedProtocol { ticker: String, protocol: CoinProtocol }, + CoinProtocolParseError { + ticker: String, + err: json::Error, + }, + UnexpectedProtocol { + ticker: String, + protocol: CoinProtocol, + }, + ProtocolMissMatch { + ticker: String, + from_config: CoinProtocol, + from_request: CoinProtocol, + }, } /// Determines the coin configuration and protocol information for a given coin or NFT ticker. @@ -74,6 +86,7 @@ pub enum CoinConfWithProtocolError { pub fn coin_conf_with_protocol( ctx: &MmArc, coin: &str, + protocol_from_request: Option, ) -> Result<(Json, T), MmError> { let (conf, coin_protocol) = match Chain::from_nft_ticker(coin) { Ok(chain) => { @@ -85,13 +98,35 @@ pub fn coin_conf_with_protocol( (platform_conf, nft_protocol) }, Err(_) => { - let conf = coin_conf(ctx, coin); - let coin_protocol: CoinProtocol = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { - CoinConfWithProtocolError::CoinProtocolParseError { - ticker: coin.into(), - err, - } - })?; + let mut conf = coin_conf(ctx, coin); + let coin_protocol: CoinProtocol = match json::from_value(conf["protocol"].clone()) { + Ok(protocol_from_config) => { + if let Some(protocol_from_request) = protocol_from_request { + if protocol_from_request != protocol_from_config { + return MmError::err(CoinConfWithProtocolError::ProtocolMissMatch { + ticker: coin.into(), + from_config: protocol_from_config, + from_request: protocol_from_request, + }); + } + } + protocol_from_config + }, + Err(err) => match protocol_from_request { + Some(protocol_from_request) => { + // Todo: Remove this once order matching using contract instead of ticker is implemented + conf["wallet_only"] = json::Value::Bool(true); + protocol_from_request + }, + None => { + return MmError::err(CoinConfWithProtocolError::CoinProtocolParseError { + ticker: coin.into(), + err, + }) + }, + }, + }; + drop_mutability!(conf); (conf, coin_protocol) }, }; diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs index 314f3066b4..a90f53e968 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs @@ -90,7 +90,7 @@ where return MmError::err(InitStandaloneCoinError::CoinIsAlreadyActivated { ticker: request.ticker }); } - let (coin_conf, protocol_info) = coin_conf_with_protocol(&ctx, &request.ticker)?; + let (coin_conf, protocol_info) = coin_conf_with_protocol(&ctx, &request.ticker, None)?; let coins_act_ctx = CoinsActivationContext::from_ctx(&ctx).map_to_mm(InitStandaloneCoinError::Internal)?; let spawner = ctx.spawner(); diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs index 1f0b5db764..9e9278915b 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs @@ -51,6 +51,9 @@ impl From for InitStandaloneCoinError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitStandaloneCoinError::UnexpectedCoinProtocol { ticker, protocol } }, + CoinConfWithProtocolError::ProtocolMissMatch { ticker, .. } => { + InitStandaloneCoinError::Internal(format!("Protocol from request is not supported for {}", ticker)) + }, } } } diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index d1449dea8d..541e867e53 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -49,6 +49,17 @@ pub enum EnableTokenError { ticker: String, protocol: CoinProtocol, }, + #[display( + fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", + ticker, + from_config, + from_request + )] + ProtocolMissMatch { + ticker: String, + from_config: CoinProtocol, + from_request: CoinProtocol, + }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), #[display(fmt = "{} is not a platform coin for token {}", platform_coin_ticker, token_ticker)] @@ -87,6 +98,15 @@ impl From for EnableTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { EnableTokenError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::ProtocolMissMatch { + ticker, + from_config, + from_request, + } => EnableTokenError::ProtocolMissMatch { + ticker, + from_config, + from_request, + }, } } } @@ -104,6 +124,7 @@ impl From for EnableTokenError { #[derive(Debug, Deserialize)] pub struct EnableTokenRequest { ticker: String, + protocol: Option, activation_params: T, } @@ -120,7 +141,8 @@ where return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); } - let (_, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (_, token_protocol): (_, Token::ProtocolInfo) = + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) .await @@ -163,7 +185,8 @@ impl HttpStatusCode for EnableTokenError { | EnableTokenError::PlatformCoinIsNotActivated(_) | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } - | EnableTokenError::InvalidPayload(_) => StatusCode::BAD_REQUEST, + | EnableTokenError::InvalidPayload(_) + | EnableTokenError::ProtocolMissMatch { .. } => StatusCode::BAD_REQUEST, EnableTokenError::TokenProtocolParseError { .. } | EnableTokenError::UnsupportedPlatformCoin { .. } | EnableTokenError::UnexpectedDerivationMethod(_) From 7e95c7f93e136b037ffdd3d48f99d18c76a8d748 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 2 Aug 2024 04:36:34 +0300 Subject: [PATCH 02/31] fix typo in ProtocolMissMatch --- mm2src/coins_activation/src/init_token.rs | 8 ++++---- .../coins_activation/src/l2/init_l2_error.rs | 2 +- .../src/platform_coin_with_tokens.rs | 18 +++++++++--------- mm2src/coins_activation/src/prelude.rs | 4 ++-- .../init_standalone_coin_error.rs | 2 +- mm2src/coins_activation/src/token.rs | 8 ++++---- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index 279e495d95..6a4f8e1fae 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -308,7 +308,7 @@ pub enum InitTokenError { from_config, from_request )] - ProtocolMissMatch { + ProtocolMismatch { ticker: String, from_config: CoinProtocol, from_request: CoinProtocol, @@ -345,11 +345,11 @@ impl From for InitTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokenError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMissMatch { + CoinConfWithProtocolError::ProtocolMismatch { ticker, from_config, from_request, - } => InitTokenError::ProtocolMissMatch { + } => InitTokenError::ProtocolMismatch { ticker, from_config, from_request, @@ -376,7 +376,7 @@ impl HttpStatusCode for InitTokenError { | InitTokenError::TokenConfigIsNotFound { .. } | InitTokenError::TokenProtocolParseError { .. } | InitTokenError::UnexpectedTokenProtocol { .. } - | InitTokenError::ProtocolMissMatch { .. } + | InitTokenError::ProtocolMismatch { .. } | InitTokenError::TokenCreationError { .. } | InitTokenError::PlatformCoinIsNotActivated(_) => StatusCode::BAD_REQUEST, InitTokenError::TaskTimedOut { .. } => StatusCode::REQUEST_TIMEOUT, diff --git a/mm2src/coins_activation/src/l2/init_l2_error.rs b/mm2src/coins_activation/src/l2/init_l2_error.rs index ef3f318105..2af37bb953 100644 --- a/mm2src/coins_activation/src/l2/init_l2_error.rs +++ b/mm2src/coins_activation/src/l2/init_l2_error.rs @@ -62,7 +62,7 @@ impl From for InitL2Error { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitL2Error::UnexpectedL2Protocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMissMatch { ticker, .. } => { + CoinConfWithProtocolError::ProtocolMismatch { ticker, .. } => { InitL2Error::Internal(format!("Protocol from request is not supported for {}", ticker)) }, } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index edf5caed83..d5b88eac2a 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -101,7 +101,7 @@ pub enum InitTokensAsMmCoinsError { ticker: String, protocol: CoinProtocol, }, - ProtocolMissMatch { + ProtocolMismatch { ticker: String, from_config: CoinProtocol, from_request: CoinProtocol, @@ -124,11 +124,11 @@ impl From for InitTokensAsMmCoinsError { InitTokensAsMmCoinsError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMissMatch { + CoinConfWithProtocolError::ProtocolMismatch { ticker, from_config, from_request, - } => InitTokensAsMmCoinsError::ProtocolMissMatch { + } => InitTokensAsMmCoinsError::ProtocolMismatch { ticker, from_config, from_request, @@ -288,7 +288,7 @@ pub enum EnablePlatformCoinWithTokensError { from_config, from_request )] - ProtocolMissMatch { + ProtocolMismatch { ticker: String, from_config: CoinProtocol, from_request: CoinProtocol, @@ -333,11 +333,11 @@ impl From for EnablePlatformCoinWithTokensError { error: err.to_string(), } }, - CoinConfWithProtocolError::ProtocolMissMatch { + CoinConfWithProtocolError::ProtocolMismatch { ticker, from_config, from_request, - } => EnablePlatformCoinWithTokensError::ProtocolMissMatch { + } => EnablePlatformCoinWithTokensError::ProtocolMismatch { ticker, from_config, from_request, @@ -369,11 +369,11 @@ impl From for EnablePlatformCoinWithTokensError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) => { EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(e.to_string()) }, - InitTokensAsMmCoinsError::ProtocolMissMatch { + InitTokensAsMmCoinsError::ProtocolMismatch { ticker, from_config, from_request, - } => EnablePlatformCoinWithTokensError::ProtocolMissMatch { + } => EnablePlatformCoinWithTokensError::ProtocolMismatch { ticker, from_config, from_request, @@ -425,7 +425,7 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::UnexpectedDeviceActivationPolicy | EnablePlatformCoinWithTokensError::FailedSpawningBalanceEvents(_) | EnablePlatformCoinWithTokensError::UnexpectedTokenProtocol { .. } - | EnablePlatformCoinWithTokensError::ProtocolMissMatch { .. } => StatusCode::BAD_REQUEST, + | EnablePlatformCoinWithTokensError::ProtocolMismatch { .. } => StatusCode::BAD_REQUEST, EnablePlatformCoinWithTokensError::Transport(_) => StatusCode::BAD_GATEWAY, } } diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index b203b58a9b..bdb52b71e9 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -73,7 +73,7 @@ pub enum CoinConfWithProtocolError { ticker: String, protocol: CoinProtocol, }, - ProtocolMissMatch { + ProtocolMismatch { ticker: String, from_config: CoinProtocol, from_request: CoinProtocol, @@ -103,7 +103,7 @@ pub fn coin_conf_with_protocol( Ok(protocol_from_config) => { if let Some(protocol_from_request) = protocol_from_request { if protocol_from_request != protocol_from_config { - return MmError::err(CoinConfWithProtocolError::ProtocolMissMatch { + return MmError::err(CoinConfWithProtocolError::ProtocolMismatch { ticker: coin.into(), from_config: protocol_from_config, from_request: protocol_from_request, diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs index 9e9278915b..ad45dfff9b 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs @@ -51,7 +51,7 @@ impl From for InitStandaloneCoinError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitStandaloneCoinError::UnexpectedCoinProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMissMatch { ticker, .. } => { + CoinConfWithProtocolError::ProtocolMismatch { ticker, .. } => { InitStandaloneCoinError::Internal(format!("Protocol from request is not supported for {}", ticker)) }, } diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index 151624a0c0..166b408e13 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -55,7 +55,7 @@ pub enum EnableTokenError { from_config, from_request )] - ProtocolMissMatch { + ProtocolMismatch { ticker: String, from_config: CoinProtocol, from_request: CoinProtocol, @@ -99,11 +99,11 @@ impl From for EnableTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { EnableTokenError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMissMatch { + CoinConfWithProtocolError::ProtocolMismatch { ticker, from_config, from_request, - } => EnableTokenError::ProtocolMissMatch { + } => EnableTokenError::ProtocolMismatch { ticker, from_config, from_request, @@ -187,7 +187,7 @@ impl HttpStatusCode for EnableTokenError { | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } | EnableTokenError::InvalidPayload(_) - | EnableTokenError::ProtocolMissMatch { .. } => StatusCode::BAD_REQUEST, + | EnableTokenError::ProtocolMismatch { .. } => StatusCode::BAD_REQUEST, EnableTokenError::TokenProtocolParseError { .. } | EnableTokenError::UnsupportedPlatformCoin { .. } | EnableTokenError::UnexpectedDerivationMethod(_) From 8c489cf57d4a999afc0acf01b9d7df67f0fd0883 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 2 Aug 2024 04:45:13 +0300 Subject: [PATCH 03/31] review fix: use `ok_or_else` instead of matching `protocol_from_request` --- mm2src/coins_activation/src/prelude.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index bdb52b71e9..6e65384372 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -112,18 +112,15 @@ pub fn coin_conf_with_protocol( } protocol_from_config }, - Err(err) => match protocol_from_request { - Some(protocol_from_request) => { - // Todo: Remove this once order matching using contract instead of ticker is implemented - conf["wallet_only"] = json::Value::Bool(true); - protocol_from_request - }, - None => { - return MmError::err(CoinConfWithProtocolError::CoinProtocolParseError { + Err(err) => { + let protocol_from_request = + protocol_from_request.ok_or_else(|| CoinConfWithProtocolError::CoinProtocolParseError { ticker: coin.into(), err, - }) - }, + })?; + // Todo: Remove this once order matching using contract instead of ticker is implemented + conf["wallet_only"] = json::Value::Bool(true); + protocol_from_request }, }; drop_mutability!(conf); From 2ae59f04386164e6515a9e5da00876950c1c0b6e Mon Sep 17 00:00:00 2001 From: shamardy Date: Sat, 3 Aug 2024 02:35:53 +0300 Subject: [PATCH 04/31] Remove getting `coin_conf` inside `initialize_erc20_token` and pass a `token_conf` parameter instead --- mm2src/coins/eth/v2_activation.rs | 16 +++++++--------- .../src/bch_with_tokens_activation.rs | 4 ++-- .../src/erc20_token_activation.rs | 4 +++- .../src/eth_with_token_activation.rs | 6 +++--- .../src/init_erc20_token_activation.rs | 4 +++- mm2src/coins_activation/src/init_token.rs | 7 ++++++- .../src/platform_coin_with_tokens.rs | 12 ++++++++---- .../coins_activation/src/slp_token_activation.rs | 2 ++ .../src/solana_with_tokens_activation.rs | 4 ++-- .../coins_activation/src/spl_token_activation.rs | 2 ++ .../src/tendermint_token_activation.rs | 2 ++ .../src/tendermint_with_assets_activation.rs | 7 +++++-- mm2src/coins_activation/src/token.rs | 14 +++++++++++--- 13 files changed, 56 insertions(+), 28 deletions(-) diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 3114319fdb..7dd722fe34 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -373,9 +373,10 @@ pub struct NftProtocol { impl EthCoin { pub async fn initialize_erc20_token( &self, + ticker: String, activation_params: Erc20TokenActivationRequest, + token_conf: Json, protocol: Erc20Protocol, - ticker: String, ) -> MmResult { // TODO // Check if ctx is required. @@ -384,10 +385,7 @@ impl EthCoin { .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; - // Todo: custom token will not be found in config here, this should be refactored - let conf = coin_conf(&ctx, &ticker); - - let decimals = match conf["decimals"].as_u64() { + let decimals = match token_conf["decimals"].as_u64() { None | Some(0) => get_token_decimals( &self .web3() @@ -420,7 +418,7 @@ impl EthCoin { let required_confirmations = activation_params .required_confirmations - .unwrap_or_else(|| conf["required_confirmations"].as_u64().unwrap_or(1)) + .unwrap_or_else(|| token_conf["required_confirmations"].as_u64().unwrap_or(1)) .into(); // Create an abortable system linked to the `MmCtx` so if the app is stopped on `MmArc::stop`, @@ -431,9 +429,9 @@ impl EthCoin { platform: protocol.platform, token_addr: protocol.token_addr, }; - let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; - let gas_limit = extract_gas_limit_from_conf(&conf) + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &token_conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &token_conf, &coin_type).await?; + let gas_limit = extract_gas_limit_from_conf(&token_conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; let token = EthCoinImpl { diff --git a/mm2src/coins_activation/src/bch_with_tokens_activation.rs b/mm2src/coins_activation/src/bch_with_tokens_activation.rs index ebda8efcba..426e6b43a1 100644 --- a/mm2src/coins_activation/src/bch_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/bch_with_tokens_activation.rs @@ -63,11 +63,11 @@ impl TokenInitializer for SlpTokenInitializer { async fn enable_tokens( &self, - activation_params: Vec>, + activation_params: Vec<(Json, TokenActivationParams)>, ) -> Result, MmError> { let tokens = activation_params .into_iter() - .map(|params| { + .map(|(_, params)| { // confirmation settings from RPC request have the highest priority let required_confirmations = params.activation_request.required_confirmations.unwrap_or_else(|| { params diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 8ce240b8a5..24948a8ecc 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -10,6 +10,7 @@ use coins::{eth::{v2_activation::{Erc20Protocol, EthTokenActivationError}, use common::Future01CompatExt; use mm2_err_handle::prelude::*; use serde::Serialize; +use serde_json::Value as Json; use std::collections::HashMap; #[derive(Debug, Serialize)] @@ -133,13 +134,14 @@ impl TokenActivationOps for EthCoin { ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + token_conf: Json, protocol_conf: Self::ProtocolInfo, ) -> Result<(Self, Self::ActivationResult), MmError> { match activation_params { EthTokenActivationParams::Erc20(erc20_init_params) => match protocol_conf { EthTokenProtocol::Erc20(erc20_protocol) => { let token = platform_coin - .initialize_erc20_token(erc20_init_params, erc20_protocol, ticker.clone()) + .initialize_erc20_token(ticker.clone(), erc20_init_params, token_conf, erc20_protocol) .await?; let address = display_eth_address(&token.derivation_method().single_addr_or_err().await?); diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index acb791a2d5..f2872ef6c3 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -137,13 +137,13 @@ impl TokenInitializer for Erc20Initializer { async fn enable_tokens( &self, - activation_params: Vec>, + activation_params: Vec<(Json, TokenActivationParams)>, ) -> Result, MmError> { let mut tokens = Vec::with_capacity(activation_params.len()); - for param in activation_params { + for (token_conf, param) in activation_params { let token: EthCoin = self .platform_coin - .initialize_erc20_token(param.activation_request, param.protocol, param.ticker) + .initialize_erc20_token(param.ticker, param.activation_request, token_conf, param.protocol) .await?; tokens.push(token); } diff --git a/mm2src/coins_activation/src/init_erc20_token_activation.rs b/mm2src/coins_activation/src/init_erc20_token_activation.rs index de322c9ee5..e336d2b4b7 100644 --- a/mm2src/coins_activation/src/init_erc20_token_activation.rs +++ b/mm2src/coins_activation/src/init_erc20_token_activation.rs @@ -17,6 +17,7 @@ use mm2_err_handle::prelude::*; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type Erc20TokenTaskManagerShared = InitTokenTaskManagerShared; @@ -118,11 +119,12 @@ impl InitTokenActivationOps for EthCoin { ticker: String, platform_coin: Self::PlatformCoin, activation_request: &Self::ActivationRequest, + token_conf: Json, protocol_conf: Self::ProtocolInfo, _task_handle: InitTokenTaskHandleShared, ) -> Result> { let token = platform_coin - .initialize_erc20_token(activation_request.clone().into(), protocol_conf, ticker) + .initialize_erc20_token(ticker, activation_request.clone().into(), token_conf, protocol_conf) .await?; Ok(token) diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index 6a4f8e1fae..31a2bcc6a5 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -19,6 +19,7 @@ use rpc_task::{RpcTask, RpcTaskError, RpcTaskHandleShared, RpcTaskManager, RpcTa RpcTaskTypes, TaskId}; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::time::Duration; pub type InitTokenResponse = InitRpcTaskResponse; @@ -66,6 +67,7 @@ pub trait InitTokenActivationOps: Into + TokenOf + Clone + Send + Sy ticker: String, platform_coin: Self::PlatformCoin, activation_request: &Self::ActivationRequest, + token_conf: Json, protocol_conf: Self::ProtocolInfo, task_handle: InitTokenTaskHandleShared, ) -> Result>; @@ -96,7 +98,7 @@ where return MmError::err(InitTokenError::TokenIsAlreadyActivated { ticker: request.ticker }); } - let (_, token_protocol): (_, Token::ProtocolInfo) = + let (token_conf, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &request.ticker, request.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) @@ -114,6 +116,7 @@ where let task = InitTokenTask:: { ctx, request, + token_conf, token_protocol, platform_coin, }; @@ -177,6 +180,7 @@ pub async fn cancel_init_token( pub struct InitTokenTask { ctx: MmArc, request: InitTokenReq, + token_conf: Json, token_protocol: Token::ProtocolInfo, platform_coin: Token::PlatformCoin, } @@ -213,6 +217,7 @@ where ticker.clone(), self.platform_coin.clone(), &self.request.activation_params, + self.token_conf.clone(), self.token_protocol.clone(), task_handle.clone(), ) diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index d5b88eac2a..f471753985 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -69,7 +69,10 @@ pub trait TokenInitializer { async fn enable_tokens( &self, - params: Vec>, + params: Vec<( + Json, + TokenActivationParams, + )>, ) -> Result, MmError>; fn platform_coin(&self) -> &::PlatformCoin; @@ -160,12 +163,13 @@ where let token_params = tokens_requests .into_iter() .map(|req| -> Result<_, MmError> { - let (_, protocol): (_, T::TokenProtocol) = coin_conf_with_protocol(&ctx, &req.ticker, req.protocol)?; - Ok(TokenActivationParams { + let (token_conf, protocol): (_, T::TokenProtocol) = + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol)?; + Ok((token_conf, TokenActivationParams { ticker: req.ticker, activation_request: req.request, protocol, - }) + })) }) .collect::, _>>()?; diff --git a/mm2src/coins_activation/src/slp_token_activation.rs b/mm2src/coins_activation/src/slp_token_activation.rs index f9abc166f4..30331494dd 100644 --- a/mm2src/coins_activation/src/slp_token_activation.rs +++ b/mm2src/coins_activation/src/slp_token_activation.rs @@ -7,6 +7,7 @@ use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum}; use mm2_err_handle::prelude::*; use rpc::v1::types::H256 as H256Json; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::collections::HashMap; impl TryPlatformCoinFromMmCoinEnum for BchCoin { @@ -82,6 +83,7 @@ impl TokenActivationOps for SlpToken { ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, ) -> Result<(Self, Self::ActivationResult), MmError> { // confirmation settings from activation params have the highest priority diff --git a/mm2src/coins_activation/src/solana_with_tokens_activation.rs b/mm2src/coins_activation/src/solana_with_tokens_activation.rs index f411995779..858a11bfe8 100644 --- a/mm2src/coins_activation/src/solana_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/solana_with_tokens_activation.rs @@ -57,11 +57,11 @@ impl TokenInitializer for SplTokenInitializer { async fn enable_tokens( &self, - activation_params: Vec>, + activation_params: Vec<(Json, TokenActivationParams)>, ) -> Result, MmError> { let tokens = activation_params .into_iter() - .map(|param| { + .map(|(_, param)| { let ticker = param.ticker.clone(); SplToken::new( param.protocol.decimals, diff --git a/mm2src/coins_activation/src/spl_token_activation.rs b/mm2src/coins_activation/src/spl_token_activation.rs index 2bf40b6a89..7e37c5f5a8 100644 --- a/mm2src/coins_activation/src/spl_token_activation.rs +++ b/mm2src/coins_activation/src/spl_token_activation.rs @@ -7,6 +7,7 @@ use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, MmCoinEnum, use common::Future01CompatExt; use mm2_err_handle::prelude::*; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::collections::HashMap; impl TryPlatformCoinFromMmCoinEnum for SolanaCoin { @@ -92,6 +93,7 @@ impl TokenActivationOps for SplToken { ticker: String, platform_coin: Self::PlatformCoin, _activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, ) -> Result<(Self, Self::ActivationResult), MmError> { let token = Self::new( diff --git a/mm2src/coins_activation/src/tendermint_token_activation.rs b/mm2src/coins_activation/src/tendermint_token_activation.rs index 29598969af..31b7c64192 100644 --- a/mm2src/coins_activation/src/tendermint_token_activation.rs +++ b/mm2src/coins_activation/src/tendermint_token_activation.rs @@ -7,6 +7,7 @@ use coins::{tendermint::{TendermintCoin, TendermintToken, TendermintTokenActivat use common::Future01CompatExt; use mm2_err_handle::prelude::{MapMmError, MmError}; use serde::Serialize; +use serde_json::Value as Json; use std::collections::HashMap; impl From for EnableTokenError { @@ -54,6 +55,7 @@ impl TokenActivationOps for TendermintToken { ticker: String, platform_coin: Self::PlatformCoin, _activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, ) -> Result<(Self, Self::ActivationResult), MmError> { let token = TendermintToken::new(ticker, platform_coin, protocol_conf.decimals, protocol_conf.denom)?; diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index 5a79bbeb6e..cc94332632 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -132,11 +132,14 @@ impl TokenInitializer for TendermintTokenInitializer { async fn enable_tokens( &self, - params: Vec>, + params: Vec<( + Json, + TokenActivationParams, + )>, ) -> Result, MmError> { params .into_iter() - .map(|param| { + .map(|(_, param)| { let ticker = param.ticker.clone(); TendermintToken::new( param.ticker, diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index 166b408e13..ce0461736d 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -12,6 +12,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; pub trait TokenProtocolParams { fn platform_coin_ticker(&self) -> &str; @@ -28,6 +29,7 @@ pub trait TokenActivationOps: Into + platform_coin_with_tokens::Toke ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + token_conf: Json, protocol_conf: Self::ProtocolInfo, ) -> Result<(Self, Self::ActivationResult), MmError>; } @@ -142,7 +144,7 @@ where return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); } - let (_, token_protocol): (_, Token::ProtocolInfo) = + let (token_conf, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) @@ -156,8 +158,14 @@ where } })?; - let (token, activation_result) = - Token::enable_token(req.ticker, platform_coin.clone(), req.activation_params, token_protocol).await?; + let (token, activation_result) = Token::enable_token( + req.ticker, + platform_coin.clone(), + req.activation_params, + token_conf, + token_protocol, + ) + .await?; let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); coins_ctx.add_token(token.clone().into()).await?; From 126570a1d6adb9261a200ba38482dbc63b328255 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 7 Oct 2024 21:09:49 +0300 Subject: [PATCH 05/31] wip: make custom token work for EVM only and add some validation steps --- mm2src/coins/eth.rs | 37 ++++++++++ mm2src/coins/eth/v2_activation.rs | 7 ++ mm2src/coins/lp_coins.rs | 52 +++++++++++++- mm2src/coins_activation/src/init_token.rs | 29 ++------ .../coins_activation/src/l2/init_l2_error.rs | 4 +- .../src/platform_coin_with_tokens.rs | 67 ++++--------------- mm2src/coins_activation/src/prelude.rs | 37 +++++----- .../init_standalone_coin_error.rs | 7 +- mm2src/coins_activation/src/token.rs | 27 ++------ 9 files changed, 143 insertions(+), 124 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 7974e29668..ba10673d35 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -7205,3 +7205,40 @@ impl CommonSwapOpsV2 for EthCoin { self.derive_htlc_pubkey_v2(swap_unique_data).to_bytes() } } + +/// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. +pub fn get_erc20_ticker_by_contract_address(ctx: &MmArc, platform: &str, contract_address: &str) -> Option { + ctx.conf["coins"].as_array()?.iter().find_map(|coin| { + let protocol = coin.get("protocol")?; + let protocol_type = protocol.get("type")?.as_str()?; + if protocol_type != "ERC20" { + return None; + } + let protocol_data = protocol.get("protocol_data")?; + let coin_platform = protocol_data.get("platform")?.as_str()?; + let coin_contract_address = protocol_data.get("contract_address")?.as_str()?; + + if coin_platform == platform && coin_contract_address == contract_address { + coin.get("coin")?.as_str().map(|s| s.to_string()) + } else { + None + } + }) +} + +/// Finds an enabled ERC20 token by its contract address and returns it as `MmCoinEnum`. +pub async fn get_enabled_erc20_by_contract( + ctx: &MmArc, + contract_address: Address, +) -> MmResult, String> { + let cctx = CoinsContext::from_ctx(ctx)?; + let coins = cctx.coins.lock().await; + + Ok(coins + .iter() + .find(|(_, coin)| match &coin.inner { + MmCoinEnum::EthCoin(eth_coin) => eth_coin.erc20_token_address() == Some(contract_address), + _ => false, + }) + .map(|(_, coin)| coin.inner.clone())) +} diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 7dd722fe34..1140e54795 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -299,6 +299,7 @@ pub enum EthTokenActivationParams { /// Holds ERC-20 token-specific activation parameters, including optional confirmation requirements. #[derive(Clone, Deserialize)] pub struct Erc20TokenActivationRequest { + // Todo: default confirmations should be from platform coin instead of 1 for custom tokens (and all tokens as well) pub required_confirmations: Option, } @@ -385,6 +386,12 @@ impl EthCoin { .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; + // Todo: find a more suitable place for this check + if let Err(e) = get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { + // Todo: use a new error variant + return MmError::err(EthTokenActivationError::InternalError(e.to_string())); + } + let decimals = match token_conf["decimals"].as_u64() { None | Some(0) => get_token_decimals( &self diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 5e704db283..da1cb26d73 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -241,8 +241,8 @@ pub mod coins_tests; pub mod eth; use eth::GetValidEthWithdrawAddError; -use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, - GetEthAddressError, SignedEthTx}; +use eth::{eth_coin_from_conf_and_request, get_erc20_ticker_by_contract_address, get_eth_address, EthCoin, + EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; use ethereum_types::U256; pub mod hd_wallet; @@ -4287,6 +4287,54 @@ pub enum CoinProtocol { }, } +#[derive(Debug, Display)] +#[allow(clippy::large_enum_variant)] +pub enum CustomTokenError { + #[display( + fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", + ticker, + from_config, + from_request + )] + ProtocolMismatch { + ticker: String, + from_config: CoinProtocol, + from_request: CoinProtocol, + }, + DuplicateContractInConfig { + ticker_in_config: String, + }, +} + +impl CoinProtocol { + // Todo: Use this validation in the decimals/symbol RPC + /// Several checks to be preformed when a custom token is being activated to check uniqueness among other things. + #[allow(clippy::result_large_err)] + pub fn custom_token_validations(&self, ctx: &MmArc) -> MmResult<(), CustomTokenError> { + let CoinProtocol::ERC20 { + platform, + contract_address, + } = self + else { + return Ok(()); + }; + + // Check if there is a token with the same contract address in the config. + // If there is, return an error as the user should use this token instead of activating a custom one. + // This is necessary as we will create an orderbook for this custom token using the contract address, + // if it is duplicated in config, we will have two orderbooks one using the ticker and one using the contract address. + // Todo: We should use the contract address for orderbook topics instead of the ticker. + // If a coin is added to the config later, users who added it as a custom token and did not update will not see the orderbook. + if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(ctx, platform, contract_address) { + return Err(MmError::new(CustomTokenError::DuplicateContractInConfig { + ticker_in_config: existing_ticker, + })); + } + + Ok(()) + } +} + pub type RpcTransportEventHandlerShared = Arc; /// Common methods to measure the outgoing requests and incoming responses statistics. diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index 31a2bcc6a5..336b728ca1 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -38,6 +38,7 @@ pub type CancelInitTokenError = CancelRpcTaskError; #[derive(Debug, Deserialize, Clone)] pub struct InitTokenReq { ticker: String, + // Todo: should make this work for user entered contract addresses, should we allow both mixed and upper case for this? protocol: Option, activation_params: T, } @@ -93,7 +94,6 @@ where InitTokenError: From, (Token::ActivationError, InitTokenError): NotEqual, { - // Todo: we should check if the contract has been enabled before in case the user is re-enabling the coin using a different ticker if let Ok(Some(_)) = lp_coinfind(&ctx, &request.ticker).await { return MmError::err(InitTokenError::TokenIsAlreadyActivated { ticker: request.ticker }); } @@ -307,17 +307,6 @@ pub enum InitTokenError { TokenProtocolParseError { ticker: String, error: String }, #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, - #[display( - fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", - ticker, - from_config, - from_request - )] - ProtocolMismatch { - ticker: String, - from_config: CoinProtocol, - from_request: CoinProtocol, - }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] TokenCreationError { ticker: String, error: String }, #[display(fmt = "Could not fetch balance: {}", _0)] @@ -329,6 +318,8 @@ pub enum InitTokenError { platform_coin_ticker: String, token_ticker: String, }, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(String), #[display(fmt = "{}", _0)] HwError(HwRpcError), #[display(fmt = "Transport error: {}", _0)] @@ -350,15 +341,7 @@ impl From for InitTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokenError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMismatch { - ticker, - from_config, - from_request, - } => InitTokenError::ProtocolMismatch { - ticker, - from_config, - from_request, - }, + CoinConfWithProtocolError::CustomTokenError(e) => InitTokenError::CustomTokenError(e.to_string()), } } } @@ -381,9 +364,9 @@ impl HttpStatusCode for InitTokenError { | InitTokenError::TokenConfigIsNotFound { .. } | InitTokenError::TokenProtocolParseError { .. } | InitTokenError::UnexpectedTokenProtocol { .. } - | InitTokenError::ProtocolMismatch { .. } | InitTokenError::TokenCreationError { .. } - | InitTokenError::PlatformCoinIsNotActivated(_) => StatusCode::BAD_REQUEST, + | InitTokenError::PlatformCoinIsNotActivated(_) + | InitTokenError::CustomTokenError(_) => StatusCode::BAD_REQUEST, InitTokenError::TaskTimedOut { .. } => StatusCode::REQUEST_TIMEOUT, InitTokenError::HwError(_) => StatusCode::GONE, InitTokenError::CouldNotFetchBalance(_) diff --git a/mm2src/coins_activation/src/l2/init_l2_error.rs b/mm2src/coins_activation/src/l2/init_l2_error.rs index 2af37bb953..351d58a290 100644 --- a/mm2src/coins_activation/src/l2/init_l2_error.rs +++ b/mm2src/coins_activation/src/l2/init_l2_error.rs @@ -62,8 +62,8 @@ impl From for InitL2Error { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitL2Error::UnexpectedL2Protocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMismatch { ticker, .. } => { - InitL2Error::Internal(format!("Protocol from request is not supported for {}", ticker)) + CoinConfWithProtocolError::CustomTokenError(e) => { + InitL2Error::Internal(format!("Custom tokens are not supported for L2: {}", e)) }, } } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index f471753985..93915203cd 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -38,6 +38,7 @@ pub type InitPlatformCoinWithTokensTaskManagerShared = #[derive(Clone, Debug, Deserialize)] pub struct TokenActivationRequest { ticker: String, + // Todo: should make this work for user entered contract addresses, should we allow both mixed and upper case for this? protocol: Option, #[serde(flatten)] request: Req, @@ -96,21 +97,11 @@ pub enum InitTokensAsMmCoinsError { CouldNotFetchBalance(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), Internal(String), - TokenProtocolParseError { - ticker: String, - error: String, - }, - UnexpectedTokenProtocol { - ticker: String, - protocol: CoinProtocol, - }, - ProtocolMismatch { - ticker: String, - from_config: CoinProtocol, - from_request: CoinProtocol, - }, + TokenProtocolParseError { ticker: String, error: String }, + UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, Transport(String), InvalidPayload(String), + CustomTokenError(String), } impl From for InitTokensAsMmCoinsError { @@ -126,16 +117,7 @@ impl From for InitTokensAsMmCoinsError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokensAsMmCoinsError::UnexpectedTokenProtocol { ticker, protocol } }, - - CoinConfWithProtocolError::ProtocolMismatch { - ticker, - from_config, - from_request, - } => InitTokensAsMmCoinsError::ProtocolMismatch { - ticker, - from_config, - from_request, - }, + CoinConfWithProtocolError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e.to_string()), } } } @@ -286,17 +268,6 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, protocol: CoinProtocol, }, - #[display( - fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", - ticker, - from_config, - from_request - )] - ProtocolMismatch { - ticker: String, - from_config: CoinProtocol, - from_request: CoinProtocol, - }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] PlatformCoinCreationError { ticker: String, @@ -320,6 +291,8 @@ pub enum EnablePlatformCoinWithTokensError { }, #[display(fmt = "Hardware policy must be activated within task manager")] UnexpectedDeviceActivationPolicy, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(String), } impl From for EnablePlatformCoinWithTokensError { @@ -337,14 +310,8 @@ impl From for EnablePlatformCoinWithTokensError { error: err.to_string(), } }, - CoinConfWithProtocolError::ProtocolMismatch { - ticker, - from_config, - from_request, - } => EnablePlatformCoinWithTokensError::ProtocolMismatch { - ticker, - from_config, - from_request, + CoinConfWithProtocolError::CustomTokenError(e) => { + EnablePlatformCoinWithTokensError::CustomTokenError(e.to_string()) }, } } @@ -373,15 +340,7 @@ impl From for EnablePlatformCoinWithTokensError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) => { EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(e.to_string()) }, - InitTokensAsMmCoinsError::ProtocolMismatch { - ticker, - from_config, - from_request, - } => EnablePlatformCoinWithTokensError::ProtocolMismatch { - ticker, - from_config, - from_request, - }, + InitTokensAsMmCoinsError::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -417,7 +376,8 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::PrivKeyPolicyNotAllowed(_) | EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(_) | EnablePlatformCoinWithTokensError::Internal(_) - | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } => StatusCode::INTERNAL_SERVER_ERROR, + | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } + | EnablePlatformCoinWithTokensError::CustomTokenError(_) => StatusCode::INTERNAL_SERVER_ERROR, EnablePlatformCoinWithTokensError::PlatformIsAlreadyActivated(_) | EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(_) | EnablePlatformCoinWithTokensError::PlatformConfigIsNotFound(_) @@ -428,8 +388,7 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::NoSuchTask(_) | EnablePlatformCoinWithTokensError::UnexpectedDeviceActivationPolicy | EnablePlatformCoinWithTokensError::FailedSpawningBalanceEvents(_) - | EnablePlatformCoinWithTokensError::UnexpectedTokenProtocol { .. } - | EnablePlatformCoinWithTokensError::ProtocolMismatch { .. } => StatusCode::BAD_REQUEST, + | EnablePlatformCoinWithTokensError::UnexpectedTokenProtocol { .. } => StatusCode::BAD_REQUEST, EnablePlatformCoinWithTokensError::Transport(_) => StatusCode::BAD_GATEWAY, } } diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 28676ceb5f..5a372abd20 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -2,7 +2,7 @@ use coins::sia::SiaCoinActivationParams; use coins::utxo::UtxoActivationParams; use coins::z_coin::ZcoinActivationParams; -use coins::{coin_conf, CoinBalance, CoinProtocol, DerivationMethodResponse, MmCoinEnum}; +use coins::{coin_conf, CoinBalance, CoinProtocol, CustomTokenError, DerivationMethodResponse, MmCoinEnum}; use common::drop_mutability; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -64,19 +64,9 @@ pub trait TryFromCoinProtocol { #[derive(Debug)] pub enum CoinConfWithProtocolError { ConfigIsNotFound(String), - CoinProtocolParseError { - ticker: String, - err: json::Error, - }, - UnexpectedProtocol { - ticker: String, - protocol: CoinProtocol, - }, - ProtocolMismatch { - ticker: String, - from_config: CoinProtocol, - from_request: CoinProtocol, - }, + CoinProtocolParseError { ticker: String, err: json::Error }, + UnexpectedProtocol { ticker: String, protocol: CoinProtocol }, + CustomTokenError(CustomTokenError), } /// Determines the coin configuration and protocol information for a given coin or NFT ticker. @@ -86,6 +76,12 @@ pub fn coin_conf_with_protocol( coin: &str, protocol_from_request: Option, ) -> Result<(Json, T), MmError> { + if let Some(protocol_from_request) = &protocol_from_request { + protocol_from_request + .custom_token_validations(ctx) + .mm_err(CoinConfWithProtocolError::CustomTokenError)?; + } + let mut conf = coin_conf(ctx, coin); let coin_protocol = if conf.is_null() { // This means it's a custom token, and we should use protocol from request if it's not None @@ -97,6 +93,7 @@ pub fn coin_conf_with_protocol( }); protocol_from_request } else { + // Todo: should include ticker from config in the new decimals/symbol RPC let protocol_from_config = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { CoinConfWithProtocolError::CoinProtocolParseError { ticker: coin.into(), @@ -106,11 +103,13 @@ pub fn coin_conf_with_protocol( if let Some(protocol_from_request) = protocol_from_request { if protocol_from_request != protocol_from_config { - return MmError::err(CoinConfWithProtocolError::ProtocolMismatch { - ticker: coin.into(), - from_config: protocol_from_config, - from_request: protocol_from_request, - }); + return MmError::err(CoinConfWithProtocolError::CustomTokenError( + CustomTokenError::ProtocolMismatch { + ticker: coin.into(), + from_config: protocol_from_config, + from_request: protocol_from_request, + }, + )); } } diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs index ad45dfff9b..afbdc32486 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs @@ -51,9 +51,10 @@ impl From for InitStandaloneCoinError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitStandaloneCoinError::UnexpectedCoinProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMismatch { ticker, .. } => { - InitStandaloneCoinError::Internal(format!("Protocol from request is not supported for {}", ticker)) - }, + CoinConfWithProtocolError::CustomTokenError(e) => InitStandaloneCoinError::Internal(format!( + "Custom tokens are not supported for standalone coins: {}", + e + )), } } } diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index ce0461736d..78793bd2ee 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -51,17 +51,6 @@ pub enum EnableTokenError { ticker: String, protocol: CoinProtocol, }, - #[display( - fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", - ticker, - from_config, - from_request - )] - ProtocolMismatch { - ticker: String, - from_config: CoinProtocol, - from_request: CoinProtocol, - }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), #[display(fmt = "{} is not a platform coin for token {}", platform_coin_ticker, token_ticker)] @@ -69,6 +58,8 @@ pub enum EnableTokenError { platform_coin_ticker: String, token_ticker: String, }, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(String), #[display(fmt = "{}", _0)] UnexpectedDerivationMethod(UnexpectedDerivationMethod), CouldNotFetchBalance(String), @@ -101,15 +92,7 @@ impl From for EnableTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { EnableTokenError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::ProtocolMismatch { - ticker, - from_config, - from_request, - } => EnableTokenError::ProtocolMismatch { - ticker, - from_config, - from_request, - }, + CoinConfWithProtocolError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e.to_string()), } } } @@ -127,6 +110,7 @@ impl From for EnableTokenError { #[derive(Debug, Deserialize)] pub struct EnableTokenRequest { ticker: String, + // Todo: should make this work for user entered contract addresses, should we allow both mixed and upper case for this? protocol: Option, activation_params: T, } @@ -141,6 +125,7 @@ where (Token::ActivationError, EnableTokenError): NotEqual, { if let Ok(Some(_)) = lp_coinfind(&ctx, &req.ticker).await { + // Todo: this should not be token already activated error, but same ticker name is already used return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); } @@ -195,7 +180,7 @@ impl HttpStatusCode for EnableTokenError { | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } | EnableTokenError::InvalidPayload(_) - | EnableTokenError::ProtocolMismatch { .. } => StatusCode::BAD_REQUEST, + | EnableTokenError::CustomTokenError(_) => StatusCode::BAD_REQUEST, EnableTokenError::TokenProtocolParseError { .. } | EnableTokenError::UnsupportedPlatformCoin { .. } | EnableTokenError::UnexpectedDerivationMethod(_) From 8238ff8af66553e2c0562590364a32e91f714d73 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 14 Oct 2024 13:55:17 +0300 Subject: [PATCH 06/31] default `required_confirmations` should be from platform coin --- mm2src/coins/eth/v2_activation.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 7f5b918b89..dbbf0c789a 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -302,7 +302,6 @@ pub enum EthTokenActivationParams { /// Holds ERC-20 token-specific activation parameters, including optional confirmation requirements. #[derive(Clone, Deserialize)] pub struct Erc20TokenActivationRequest { - // Todo: default confirmations should be from platform coin instead of 1 for custom tokens (and all tokens as well) pub required_confirmations: Option, } @@ -410,7 +409,11 @@ impl EthCoin { let required_confirmations = activation_params .required_confirmations - .unwrap_or_else(|| token_conf["required_confirmations"].as_u64().unwrap_or(1)) + .unwrap_or_else(|| { + token_conf["required_confirmations"] + .as_u64() + .unwrap_or(self.required_confirmations()) + }) .into(); // Create an abortable system linked to the `MmCtx` so if the app is stopped on `MmArc::stop`, From 12a9461ab50cacf796dfe5125ba7fb634e2aabd8 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 18 Oct 2024 22:33:45 +0300 Subject: [PATCH 07/31] wip: add `get_custom_token_info` RPC --- mm2src/coins/custom_token.rs | 78 +++++++++++++++++++ mm2src/coins/eth.rs | 30 +------ mm2src/coins/eth/erc20.rs | 65 ++++++++++++++++ mm2src/coins/eth/v2_activation.rs | 49 +++++++++++- mm2src/coins/lp_coins.rs | 6 +- .../src/erc20_token_activation.rs | 3 + .../src/eth_with_token_activation.rs | 14 ++++ .../src/init_erc20_token_activation.rs | 7 +- .../src/platform_coin_with_tokens.rs | 22 ++++-- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 2 + 10 files changed, 238 insertions(+), 38 deletions(-) create mode 100644 mm2src/coins/custom_token.rs create mode 100644 mm2src/coins/eth/erc20.rs diff --git a/mm2src/coins/custom_token.rs b/mm2src/coins/custom_token.rs new file mode 100644 index 0000000000..4dc0c6da8c --- /dev/null +++ b/mm2src/coins/custom_token.rs @@ -0,0 +1,78 @@ +use crate::eth::erc20::{get_erc20_token_info, Erc20CustomTokenInfo}; +use crate::eth::valid_addr_from_str; +use crate::{lp_coinfind_or_err, CoinFindError, MmCoinEnum}; +use common::HttpStatusCode; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; + +// Todo: Instead of `contract_address` we should make this a generic field or an enum +#[derive(Deserialize)] +pub struct CustomTokenInfoRequest { + // Todo: maybe use protocol as request instead. + platform_coin: String, + contract_address: String, +} + +// Todo: Add balance to a new struct that includes the token info +#[derive(Serialize)] +#[serde(tag = "type", content = "info")] +pub enum CustomTokenInfoResponse { + ERC20(Erc20CustomTokenInfo), +} + +#[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum CustomTokenInfoError { + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, + #[display(fmt = "Unsupported protocol {}", protocol)] + UnsupportedProtocol { protocol: String }, + #[display(fmt = "Invalid request {}", _0)] + InvalidRequest(String), + #[display(fmt = "Error retrieving token info {}", _0)] + RetrieveInfoError(String), +} + +impl HttpStatusCode for CustomTokenInfoError { + fn status_code(&self) -> StatusCode { + match self { + CustomTokenInfoError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, + CustomTokenInfoError::UnsupportedProtocol { .. } | CustomTokenInfoError::InvalidRequest(_) => { + StatusCode::BAD_REQUEST + }, + CustomTokenInfoError::RetrieveInfoError(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +impl From for CustomTokenInfoError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => CustomTokenInfoError::NoSuchCoin { coin }, + } + } +} + +pub async fn get_custom_token_info( + ctx: MmArc, + req: CustomTokenInfoRequest, +) -> MmResult { + let platform_coin = lp_coinfind_or_err(&ctx, &req.platform_coin).await?; + match platform_coin { + MmCoinEnum::EthCoin(eth_coin) => { + // Todo: worth considering implementing serialize and deserialize for Address + let contract_address = valid_addr_from_str(&req.contract_address).map_to_mm(|e| { + let error = format!("Invalid contract address: {}", e); + CustomTokenInfoError::InvalidRequest(error) + })?; + let token_info = get_erc20_token_info(ð_coin, contract_address) + .await + .map_to_mm(CustomTokenInfoError::RetrieveInfoError)?; + Ok(CustomTokenInfoResponse::ERC20(token_info)) + }, + _ => MmError::err(CustomTokenInfoError::UnsupportedProtocol { + protocol: req.platform_coin, + }), + } +} diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 89f1362c70..60c9f8ffd4 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -157,6 +157,10 @@ mod eip1559_gas_fee; pub(crate) use eip1559_gas_fee::FeePerGasEstimated; use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, InfuraGasApiCaller}; + +pub(crate) mod erc20; +use erc20::get_token_decimals; + pub(crate) mod eth_swap_v2; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -6090,32 +6094,6 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result, token_addr: Address) -> Result { - let function = try_s!(ERC20_CONTRACT.function("decimals")); - let data = try_s!(function.encode_input(&[])); - let request = CallRequest { - from: Some(Address::default()), - to: Some(token_addr), - gas: None, - gas_price: None, - value: Some(0.into()), - data: Some(data.into()), - ..CallRequest::default() - }; - - let res = web3 - .eth() - .call(request, Some(BlockId::Number(BlockNumber::Latest))) - .map_err(|e| ERRL!("{}", e)) - .await?; - let tokens = try_s!(function.decode_output(&res.0)); - let decimals = match tokens[0] { - Token::Uint(dec) => dec.as_u64(), - _ => return ERR!("Invalid decimals type {:?}", tokens), - }; - Ok(decimals as u8) -} - pub fn valid_addr_from_str(addr_str: &str) -> Result { let addr = try_s!(addr_from_str(addr_str)); if !is_valid_checksum_addr(addr_str) { diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs new file mode 100644 index 0000000000..2b061589bc --- /dev/null +++ b/mm2src/coins/eth/erc20.rs @@ -0,0 +1,65 @@ +use crate::eth::web3_transport::Web3Transport; +use crate::eth::{EthCoin, ERC20_CONTRACT}; +use ethabi::Token; +use ethereum_types::Address; +use futures_util::TryFutureExt; +use web3::types::{BlockId, BlockNumber, CallRequest}; +use web3::{Transport, Web3}; + +async fn call_erc20_function( + web3: &Web3, + token_addr: Address, + function_name: &str, +) -> Result, String> { + let function = try_s!(ERC20_CONTRACT.function(function_name)); + let data = try_s!(function.encode_input(&[])); + let request = CallRequest { + from: Some(Address::default()), + to: Some(token_addr), + gas: None, + gas_price: None, + value: Some(0.into()), + data: Some(data.into()), + ..CallRequest::default() + }; + + let res = web3 + .eth() + .call(request, Some(BlockId::Number(BlockNumber::Latest))) + .map_err(|e| ERRL!("{}", e)) + .await?; + function.decode_output(&res.0).map_err(|e| ERRL!("{}", e)) +} + +pub(crate) async fn get_token_decimals(web3: &Web3, token_addr: Address) -> Result { + let tokens = call_erc20_function(web3, token_addr, "decimals").await?; + match tokens[0] { + Token::Uint(dec) => Ok(dec.as_u64() as u8), + _ => ERR!("Invalid decimals type {:?}", tokens), + } +} + +async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result { + let web3 = try_s!(coin.web3().await); + let tokens = call_erc20_function(&web3, token_addr, "symbol").await?; + match &tokens[0] { + Token::String(symbol) => Ok(symbol.clone()), + _ => ERR!("Invalid symbol type {:?}", tokens), + } +} + +#[derive(Serialize)] +pub struct Erc20CustomTokenInfo { + pub ticker: String, + pub decimals: u8, +} + +pub(crate) async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { + let symbol = get_token_symbol(coin, token_addr).await?; + let web3 = try_s!(coin.web3().await); + let decimals = get_token_decimals(&web3, token_addr).await?; + Ok(Erc20CustomTokenInfo { + ticker: symbol, + decimals, + }) +} diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index dbbf0c789a..24525ecb65 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,4 +1,5 @@ use super::*; +use crate::eth::erc20::get_token_decimals; use crate::eth::web3_transport::http_transport::HttpTransport; use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDPathAccountToAddressId, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; @@ -34,6 +35,15 @@ pub enum EthActivationV2Error { ticker: String, error: String, }, + #[display( + fmt = "Token is already activated, ticker: {}, contract address: {}", + ticker, + contract_address + )] + TokenAlreadyActivated { + ticker: String, + contract_address: String, + }, CouldNotFetchBalance(String), UnreachableNodes(String), #[display(fmt = "Enable request for ETH coin must have at least 1 node")] @@ -93,6 +103,13 @@ impl From for EthActivationV2Error { EthActivationV2Error::UnexpectedDerivationMethod(err) }, EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EthActivationV2Error::PrivKeyPolicyNotAllowed(e), + EthTokenActivationError::TokenAlreadyActivated { + ticker, + contract_address, + } => EthActivationV2Error::TokenAlreadyActivated { + ticker, + contract_address, + }, } } } @@ -211,6 +228,15 @@ pub enum EthTokenActivationError { Transport(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + #[display( + fmt = "Token is already activated, ticker: {}, contract address: {}", + ticker, + contract_address + )] + TokenAlreadyActivated { + ticker: String, + contract_address: String, + }, } impl From for EthTokenActivationError { @@ -303,6 +329,9 @@ pub enum EthTokenActivationParams { #[derive(Clone, Deserialize)] pub struct Erc20TokenActivationRequest { pub required_confirmations: Option, + /// `true` if the token is a custom token, meaning there is no coin config for it. + #[serde(default)] + pub is_custom: bool, } /// Holds ERC-20 token-specific activation parameters when using the task manager for activation. @@ -317,12 +346,16 @@ pub struct InitErc20TokenActivationRequest { /// If not specified, the first non-change address for the first account is used. #[serde(default)] pub path_to_address: HDPathAccountToAddressId, + /// `true` if the token is a custom token, meaning there is no coin config for it. + #[serde(default)] + is_custom: bool, } impl From for Erc20TokenActivationRequest { fn from(req: InitErc20TokenActivationRequest) -> Self { Erc20TokenActivationRequest { required_confirmations: req.required_confirmations, + is_custom: req.is_custom, } } } @@ -388,10 +421,18 @@ impl EthCoin { .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; - // Todo: find a more suitable place for this check - if let Err(e) = get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { - // Todo: use a new error variant - return MmError::err(EthTokenActivationError::InternalError(e.to_string())); + // `is_custom` was added to avoid this unnecessary check for non-custom tokens + if activation_params.is_custom { + match get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { + Ok(Some(_)) => { + return MmError::err(EthTokenActivationError::TokenAlreadyActivated { + ticker, + contract_address: display_eth_address(&protocol.token_addr), + }); + }, + Ok(None) => {}, + Err(e) => return MmError::err(EthTokenActivationError::InternalError(e.to_string())), + } } let decimals = match token_conf["decimals"].as_u64() { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 5edd9f3449..cad02ada12 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -54,7 +54,7 @@ use crypto::{derive_secp256k1_secret, Bip32Error, Bip44Chain, CryptoCtx, CryptoC Secp256k1ExtendedPublicKey, Secp256k1Secret, WithHwRpcError}; use derive_more::Display; use enum_derives::{EnumFromStringify, EnumFromTrait}; -use ethereum_types::H256; +use ethereum_types::{H256, U256}; use futures::compat::Future01CompatExt; use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use futures::{FutureExt, TryFutureExt}; @@ -206,6 +206,8 @@ macro_rules! ok_or_continue_after_sleep { pub mod coin_balance; use coin_balance::{AddressBalanceStatus, HDAddressBalance, HDWalletBalanceOps}; +pub mod custom_token; + pub mod lp_price; pub mod watcher_common; @@ -221,7 +223,6 @@ use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Er use eth::GetValidEthWithdrawAddError; use eth::{eth_coin_from_conf_and_request, get_erc20_ticker_by_contract_address, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; -use ethereum_types::U256; pub mod hd_wallet; use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress, @@ -4291,6 +4292,7 @@ impl CoinProtocol { // if it is duplicated in config, we will have two orderbooks one using the ticker and one using the contract address. // Todo: We should use the contract address for orderbook topics instead of the ticker. // If a coin is added to the config later, users who added it as a custom token and did not update will not see the orderbook. + // Todo: GUI should be responsible for enabling the coin from config instead, I should leave a To Test or docs note about this if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(ctx, platform, contract_address) { return Err(MmError::new(CustomTokenError::DuplicateContractInConfig { ticker_in_config: existing_ticker, diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 9ca240930c..c81cf0a715 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -44,6 +44,9 @@ impl From for EnableTokenError { EthTokenActivationError::InvalidPayload(e) => EnableTokenError::InvalidPayload(e), EthTokenActivationError::UnexpectedDerivationMethod(e) => EnableTokenError::UnexpectedDerivationMethod(e), EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EnableTokenError::PrivKeyPolicyNotAllowed(e), + EthTokenActivationError::TokenAlreadyActivated { ticker, .. } => { + EnableTokenError::TokenIsAlreadyActivated(ticker) + }, } } } diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index 3964c60185..004c059cbe 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -85,6 +85,13 @@ impl From for EnablePlatformCoinWithTokensError { EthActivationV2Error::InvalidHardwareWalletCall => EnablePlatformCoinWithTokensError::Internal( "Hardware wallet must be used within rpc task manager".to_string(), ), + EthActivationV2Error::TokenAlreadyActivated { + ticker, + contract_address, + } => EnablePlatformCoinWithTokensError::TokenAlreadyActivated { + ticker, + contract_address, + }, } } } @@ -118,6 +125,13 @@ impl From for InitTokensAsMmCoinsError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) }, EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => InitTokensAsMmCoinsError::Internal(e.to_string()), + EthTokenActivationError::TokenAlreadyActivated { + ticker, + contract_address, + } => InitTokensAsMmCoinsError::TokenAlreadyActivated { + ticker, + contract_address, + }, } } } diff --git a/mm2src/coins_activation/src/init_erc20_token_activation.rs b/mm2src/coins_activation/src/init_erc20_token_activation.rs index e336d2b4b7..c430fe12ba 100644 --- a/mm2src/coins_activation/src/init_erc20_token_activation.rs +++ b/mm2src/coins_activation/src/init_erc20_token_activation.rs @@ -46,7 +46,9 @@ impl From for InitTokenError { match e { InitErc20Error::HwError(hw) => InitTokenError::HwError(hw), InitErc20Error::TaskTimedOut { duration } => InitTokenError::TaskTimedOut { duration }, - InitErc20Error::TokenIsAlreadyActivated { ticker } => InitTokenError::TokenIsAlreadyActivated { ticker }, + InitErc20Error::TokenIsAlreadyActivated { ticker, .. } => { + InitTokenError::TokenIsAlreadyActivated { ticker } + }, InitErc20Error::TokenCreationError { ticker, error } => { InitTokenError::TokenCreationError { ticker, error } }, @@ -67,6 +69,9 @@ impl From for InitErc20Error { | EthTokenActivationError::CouldNotFetchBalance(_) | EthTokenActivationError::InvalidPayload(_) | EthTokenActivationError::Transport(_) => InitErc20Error::Transport(e.to_string()), + EthTokenActivationError::TokenAlreadyActivated { ticker, .. } => { + InitErc20Error::TokenIsAlreadyActivated { ticker } + }, } } } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 93915203cd..5d9f8806c6 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -92,7 +92,7 @@ pub trait TokenAsMmCoinInitializer: Send + Sync { } pub enum InitTokensAsMmCoinsError { - TokenAlreadyActivated(String), + TokenAlreadyActivated { ticker: String, contract_address: String }, TokenConfigIsNotFound(String), CouldNotFetchBalance(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), @@ -243,7 +243,15 @@ pub struct EnablePlatformCoinWithTokensReq { #[serde(tag = "error_type", content = "error_data")] pub enum EnablePlatformCoinWithTokensError { PlatformIsAlreadyActivated(String), - TokenIsAlreadyActivated(String), + #[display( + fmt = "Token is already activated, ticker: {}, contract address: {}", + ticker, + contract_address + )] + TokenAlreadyActivated { + ticker: String, + contract_address: String, + }, #[display(fmt = "Platform {} config is not found", _0)] PlatformConfigIsNotFound(String), #[display(fmt = "Platform coin {} protocol parsing failed: {}", ticker, error)] @@ -320,8 +328,12 @@ impl From for EnablePlatformCoinWithTokensError { impl From for EnablePlatformCoinWithTokensError { fn from(err: InitTokensAsMmCoinsError) -> Self { match err { - InitTokensAsMmCoinsError::TokenAlreadyActivated(ticker) => { - EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(ticker) + InitTokensAsMmCoinsError::TokenAlreadyActivated { + ticker, + contract_address, + } => EnablePlatformCoinWithTokensError::TokenAlreadyActivated { + ticker, + contract_address, }, InitTokensAsMmCoinsError::TokenConfigIsNotFound(ticker) => { EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(ticker) @@ -379,7 +391,7 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } | EnablePlatformCoinWithTokensError::CustomTokenError(_) => StatusCode::INTERNAL_SERVER_ERROR, EnablePlatformCoinWithTokensError::PlatformIsAlreadyActivated(_) - | EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(_) + | EnablePlatformCoinWithTokensError::TokenAlreadyActivated { .. } | EnablePlatformCoinWithTokensError::PlatformConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::UnexpectedPlatformProtocol { .. } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index ba99379892..16650382b6 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -12,6 +12,7 @@ use crate::{lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, stop_version_stat_collection, update_version_stat_collection}, lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, rpc::lp_commands::{get_public_key, get_public_key_hash, get_shared_db_id, trezor_connection_status}}; +use coins::custom_token::get_custom_token_info; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels}; @@ -171,6 +172,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, enable_token::).await, "get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await, + "get_custom_token_info" => handle_mmrpc(ctx, request, get_custom_token_info).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, From c888f8a3114975aaf97d9ac53ce4b881af56db2f Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 25 Oct 2024 21:19:41 +0300 Subject: [PATCH 08/31] use `CoinProtocol` in `CustomTokenInfoRequest` --- mm2src/coins/custom_token.rs | 39 +++++++++++++++++++++----------- mm2src/coins/lp_coins.rs | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/mm2src/coins/custom_token.rs b/mm2src/coins/custom_token.rs index 4dc0c6da8c..d60512c24a 100644 --- a/mm2src/coins/custom_token.rs +++ b/mm2src/coins/custom_token.rs @@ -1,17 +1,14 @@ use crate::eth::erc20::{get_erc20_token_info, Erc20CustomTokenInfo}; use crate::eth::valid_addr_from_str; -use crate::{lp_coinfind_or_err, CoinFindError, MmCoinEnum}; +use crate::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; use common::HttpStatusCode; use http::StatusCode; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -// Todo: Instead of `contract_address` we should make this a generic field or an enum #[derive(Deserialize)] pub struct CustomTokenInfoRequest { - // Todo: maybe use protocol as request instead. - platform_coin: String, - contract_address: String, + protocol: CoinProtocol, } // Todo: Add balance to a new struct that includes the token info @@ -26,8 +23,8 @@ pub enum CustomTokenInfoResponse { pub enum CustomTokenInfoError { #[display(fmt = "No such coin {}", coin)] NoSuchCoin { coin: String }, - #[display(fmt = "Unsupported protocol {}", protocol)] - UnsupportedProtocol { protocol: String }, + #[display(fmt = "Custom tokens are not supported for {} protocol yet!", protocol)] + UnsupportedTokenProtocol { protocol: String }, #[display(fmt = "Invalid request {}", _0)] InvalidRequest(String), #[display(fmt = "Error retrieving token info {}", _0)] @@ -38,7 +35,7 @@ impl HttpStatusCode for CustomTokenInfoError { fn status_code(&self) -> StatusCode { match self { CustomTokenInfoError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, - CustomTokenInfoError::UnsupportedProtocol { .. } | CustomTokenInfoError::InvalidRequest(_) => { + CustomTokenInfoError::UnsupportedTokenProtocol { .. } | CustomTokenInfoError::InvalidRequest(_) => { StatusCode::BAD_REQUEST }, CustomTokenInfoError::RetrieveInfoError(_) => StatusCode::INTERNAL_SERVER_ERROR, @@ -58,21 +55,37 @@ pub async fn get_custom_token_info( ctx: MmArc, req: CustomTokenInfoRequest, ) -> MmResult { - let platform_coin = lp_coinfind_or_err(&ctx, &req.platform_coin).await?; + // Check that the protocol is a token protocol + let platform = req + .protocol + .platform() + .ok_or(CustomTokenInfoError::InvalidRequest(format!( + "Protocol '{:?}' is not a token protocol", + req.protocol + )))?; + // Platform coin should be activated + let platform_coin = lp_coinfind_or_err(&ctx, platform).await?; match platform_coin { MmCoinEnum::EthCoin(eth_coin) => { - // Todo: worth considering implementing serialize and deserialize for Address - let contract_address = valid_addr_from_str(&req.contract_address).map_to_mm(|e| { + let contract_address = + req.protocol + .contract_address() + .ok_or(CustomTokenInfoError::UnsupportedTokenProtocol { + protocol: platform.to_string(), + })?; + + let contract_address = valid_addr_from_str(contract_address).map_to_mm(|e| { let error = format!("Invalid contract address: {}", e); CustomTokenInfoError::InvalidRequest(error) })?; + let token_info = get_erc20_token_info(ð_coin, contract_address) .await .map_to_mm(CustomTokenInfoError::RetrieveInfoError)?; Ok(CustomTokenInfoResponse::ERC20(token_info)) }, - _ => MmError::err(CustomTokenInfoError::UnsupportedProtocol { - protocol: req.platform_coin, + _ => MmError::err(CustomTokenInfoError::UnsupportedTokenProtocol { + protocol: platform.to_string(), }), } } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index cad02ada12..b096a843c7 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4274,6 +4274,49 @@ pub enum CustomTokenError { } impl CoinProtocol { + /// Returns the platform coin associated with the coin protocol, if any. + fn platform(&self) -> Option<&str> { + match self { + CoinProtocol::QRC20 { platform, .. } + | CoinProtocol::ERC20 { platform, .. } + | CoinProtocol::SLPTOKEN { platform, .. } + | CoinProtocol::NFT { platform, .. } => Some(platform), + CoinProtocol::TENDERMINTTOKEN(info) => Some(&info.platform), + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { platform, .. } => Some(platform), + CoinProtocol::UTXO + | CoinProtocol::QTUM + | CoinProtocol::ETH + | CoinProtocol::BCH { .. } + | CoinProtocol::TENDERMINT(_) + | CoinProtocol::ZHTLC(_) => None, + #[cfg(feature = "enable-sia")] + CoinProtocol::SIA => None, + } + } + + /// Returns the contract address associated with the coin, if any. + fn contract_address(&self) -> Option<&str> { + match self { + CoinProtocol::QRC20 { contract_address, .. } | CoinProtocol::ERC20 { contract_address, .. } => { + Some(contract_address) + }, + CoinProtocol::SLPTOKEN { .. } + | CoinProtocol::UTXO + | CoinProtocol::QTUM + | CoinProtocol::ETH + | CoinProtocol::BCH { .. } + | CoinProtocol::TENDERMINT(_) + | CoinProtocol::TENDERMINTTOKEN(_) + | CoinProtocol::ZHTLC(_) + | CoinProtocol::NFT { .. } => None, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { .. } => None, + #[cfg(feature = "enable-sia")] + CoinProtocol::SIA => None, + } + } + // Todo: Use this validation in the decimals/symbol RPC /// Several checks to be preformed when a custom token is being activated to check uniqueness among other things. #[allow(clippy::result_large_err)] From 245c106c2bb0ecc22b7971f7c220170ec167c88b Mon Sep 17 00:00:00 2001 From: shamardy Date: Sat, 26 Oct 2024 01:14:23 +0300 Subject: [PATCH 09/31] remove some todos that are not relevant anymore to the PR --- mm2src/coins/custom_token.rs | 1 - mm2src/coins/tendermint/tendermint_token.rs | 1 - mm2src/coins_activation/src/init_token.rs | 1 - mm2src/coins_activation/src/platform_coin_with_tokens.rs | 1 - mm2src/coins_activation/src/token.rs | 1 - 5 files changed, 5 deletions(-) diff --git a/mm2src/coins/custom_token.rs b/mm2src/coins/custom_token.rs index d60512c24a..509facca88 100644 --- a/mm2src/coins/custom_token.rs +++ b/mm2src/coins/custom_token.rs @@ -11,7 +11,6 @@ pub struct CustomTokenInfoRequest { protocol: CoinProtocol, } -// Todo: Add balance to a new struct that includes the token info #[derive(Serialize)] #[serde(tag = "type", content = "info")] pub enum CustomTokenInfoResponse { diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index d2886f04d8..6ea7bd9442 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -60,7 +60,6 @@ impl Deref for TendermintToken { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct TendermintTokenProtocolInfo { pub platform: String, - // Todo: can decimals be gotten from rpc call for custom tokens? pub decimals: u8, pub denom: String, } diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index 336b728ca1..d7a22f06f2 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -38,7 +38,6 @@ pub type CancelInitTokenError = CancelRpcTaskError; #[derive(Debug, Deserialize, Clone)] pub struct InitTokenReq { ticker: String, - // Todo: should make this work for user entered contract addresses, should we allow both mixed and upper case for this? protocol: Option, activation_params: T, } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 5d9f8806c6..6a692abfdd 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -38,7 +38,6 @@ pub type InitPlatformCoinWithTokensTaskManagerShared = #[derive(Clone, Debug, Deserialize)] pub struct TokenActivationRequest { ticker: String, - // Todo: should make this work for user entered contract addresses, should we allow both mixed and upper case for this? protocol: Option, #[serde(flatten)] request: Req, diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index 78793bd2ee..b9bca3188a 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -110,7 +110,6 @@ impl From for EnableTokenError { #[derive(Debug, Deserialize)] pub struct EnableTokenRequest { ticker: String, - // Todo: should make this work for user entered contract addresses, should we allow both mixed and upper case for this? protocol: Option, activation_params: T, } From 38a010eb240e3e6d156bb033717fd25a2befc741 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 29 Oct 2024 00:08:13 +0300 Subject: [PATCH 10/31] Enhance `get_custom_token_info` with config lookup + move some erc20 functions * `get_custom_token_info` RPC now returns `config_ticker` when token exists in config. This allows GUI to detect tokens in config and use them for activation instead of the token being wallet-only. * Move new ERC20 functions to erc20.rs for better organization --- mm2src/coins/custom_token.rs | 23 +++++++++++----- mm2src/coins/eth.rs | 37 ------------------------- mm2src/coins/eth/erc20.rs | 45 ++++++++++++++++++++++++++++--- mm2src/coins/eth/v2_activation.rs | 3 ++- mm2src/coins/lp_coins.rs | 6 ++--- 5 files changed, 63 insertions(+), 51 deletions(-) diff --git a/mm2src/coins/custom_token.rs b/mm2src/coins/custom_token.rs index 509facca88..a61a7735e6 100644 --- a/mm2src/coins/custom_token.rs +++ b/mm2src/coins/custom_token.rs @@ -1,4 +1,4 @@ -use crate::eth::erc20::{get_erc20_token_info, Erc20CustomTokenInfo}; +use crate::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20CustomTokenInfo}; use crate::eth::valid_addr_from_str; use crate::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; use common::HttpStatusCode; @@ -13,10 +13,18 @@ pub struct CustomTokenInfoRequest { #[derive(Serialize)] #[serde(tag = "type", content = "info")] -pub enum CustomTokenInfoResponse { +pub enum CustomTokenInfo { ERC20(Erc20CustomTokenInfo), } +#[derive(Serialize)] +pub struct CustomTokenInfoResponse { + #[serde(skip_serializing_if = "Option::is_none")] + config_ticker: Option, + #[serde(flatten)] + info: CustomTokenInfo, +} + #[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum CustomTokenInfoError { @@ -66,22 +74,25 @@ pub async fn get_custom_token_info( let platform_coin = lp_coinfind_or_err(&ctx, platform).await?; match platform_coin { MmCoinEnum::EthCoin(eth_coin) => { - let contract_address = + let contract_address_str = req.protocol .contract_address() .ok_or(CustomTokenInfoError::UnsupportedTokenProtocol { protocol: platform.to_string(), })?; - - let contract_address = valid_addr_from_str(contract_address).map_to_mm(|e| { + let contract_address = valid_addr_from_str(contract_address_str).map_to_mm(|e| { let error = format!("Invalid contract address: {}", e); CustomTokenInfoError::InvalidRequest(error) })?; + let config_ticker = get_erc20_ticker_by_contract_address(&ctx, platform, contract_address_str); let token_info = get_erc20_token_info(ð_coin, contract_address) .await .map_to_mm(CustomTokenInfoError::RetrieveInfoError)?; - Ok(CustomTokenInfoResponse::ERC20(token_info)) + Ok(CustomTokenInfoResponse { + config_ticker, + info: CustomTokenInfo::ERC20(token_info), + }) }, _ => MmError::err(CustomTokenInfoError::UnsupportedTokenProtocol { protocol: platform.to_string(), diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9de820bc23..fda099ca45 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -7165,43 +7165,6 @@ impl CommonSwapOpsV2 for EthCoin { } } -/// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. -pub fn get_erc20_ticker_by_contract_address(ctx: &MmArc, platform: &str, contract_address: &str) -> Option { - ctx.conf["coins"].as_array()?.iter().find_map(|coin| { - let protocol = coin.get("protocol")?; - let protocol_type = protocol.get("type")?.as_str()?; - if protocol_type != "ERC20" { - return None; - } - let protocol_data = protocol.get("protocol_data")?; - let coin_platform = protocol_data.get("platform")?.as_str()?; - let coin_contract_address = protocol_data.get("contract_address")?.as_str()?; - - if coin_platform == platform && coin_contract_address == contract_address { - coin.get("coin")?.as_str().map(|s| s.to_string()) - } else { - None - } - }) -} - -/// Finds an enabled ERC20 token by its contract address and returns it as `MmCoinEnum`. -pub async fn get_enabled_erc20_by_contract( - ctx: &MmArc, - contract_address: Address, -) -> MmResult, String> { - let cctx = CoinsContext::from_ctx(ctx)?; - let coins = cctx.coins.lock().await; - - Ok(coins - .iter() - .find(|(_, coin)| match &coin.inner { - MmCoinEnum::EthCoin(eth_coin) => eth_coin.erc20_token_address() == Some(contract_address), - _ => false, - }) - .map(|(_, coin)| coin.inner.clone())) -} - #[cfg(all(feature = "for-tests", not(target_arch = "wasm32")))] impl EthCoin { pub async fn set_coin_type(&self, new_coin_type: EthCoinType) -> EthCoin { diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs index 2b061589bc..8ac517f5e9 100644 --- a/mm2src/coins/eth/erc20.rs +++ b/mm2src/coins/eth/erc20.rs @@ -1,8 +1,11 @@ use crate::eth::web3_transport::Web3Transport; use crate::eth::{EthCoin, ERC20_CONTRACT}; +use crate::{CoinsContext, MmCoinEnum}; use ethabi::Token; use ethereum_types::Address; use futures_util::TryFutureExt; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmResult; use web3::types::{BlockId, BlockNumber, CallRequest}; use web3::{Transport, Web3}; @@ -50,7 +53,7 @@ async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result let symbol = get_token_symbol(coin, token_addr).await?; let web3 = try_s!(coin.web3().await); let decimals = get_token_decimals(&web3, token_addr).await?; - Ok(Erc20CustomTokenInfo { - ticker: symbol, - decimals, + Ok(Erc20CustomTokenInfo { symbol, decimals }) +} + +/// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. +pub fn get_erc20_ticker_by_contract_address(ctx: &MmArc, platform: &str, contract_address: &str) -> Option { + ctx.conf["coins"].as_array()?.iter().find_map(|coin| { + let protocol = coin.get("protocol")?; + let protocol_type = protocol.get("type")?.as_str()?; + if protocol_type != "ERC20" { + return None; + } + let protocol_data = protocol.get("protocol_data")?; + let coin_platform = protocol_data.get("platform")?.as_str()?; + let coin_contract_address = protocol_data.get("contract_address")?.as_str()?; + + if coin_platform == platform && coin_contract_address == contract_address { + coin.get("coin")?.as_str().map(|s| s.to_string()) + } else { + None + } }) } + +/// Finds an enabled ERC20 token by its contract address and returns it as `MmCoinEnum`. +pub async fn get_enabled_erc20_by_contract( + ctx: &MmArc, + contract_address: Address, +) -> MmResult, String> { + let cctx = CoinsContext::from_ctx(ctx)?; + let coins = cctx.coins.lock().await; + + Ok(coins + .iter() + .find(|(_, coin)| match &coin.inner { + MmCoinEnum::EthCoin(eth_coin) => eth_coin.erc20_token_address() == Some(contract_address), + _ => false, + }) + .map(|(_, coin)| coin.inner.clone())) +} diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 24525ecb65..823344723b 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,5 +1,5 @@ use super::*; -use crate::eth::erc20::get_token_decimals; +use crate::eth::erc20::{get_enabled_erc20_by_contract, get_token_decimals}; use crate::eth::web3_transport::http_transport::HttpTransport; use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDPathAccountToAddressId, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; @@ -421,6 +421,7 @@ impl EthCoin { .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; + // Todo: when custom token config storage is added, this might not be needed // `is_custom` was added to avoid this unnecessary check for non-custom tokens if activation_params.is_custom { match get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index d23f9dd580..9690ab9068 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -219,10 +219,11 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut, Vali pub mod coins_tests; pub mod eth; +use eth::erc20::get_erc20_ticker_by_contract_address; use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err}; use eth::GetValidEthWithdrawAddError; -use eth::{eth_coin_from_conf_and_request, get_erc20_ticker_by_contract_address, get_eth_address, EthCoin, - EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; +use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, + GetEthAddressError, SignedEthTx}; pub mod hd_wallet; use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress, @@ -4316,7 +4317,6 @@ impl CoinProtocol { } } - // Todo: Use this validation in the decimals/symbol RPC /// Several checks to be preformed when a custom token is being activated to check uniqueness among other things. #[allow(clippy::result_large_err)] pub fn custom_token_validations(&self, ctx: &MmArc) -> MmResult<(), CustomTokenError> { From 5fb0bdd25100accf0b535ce0851263a1d6ba5a65 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 29 Oct 2024 00:16:01 +0300 Subject: [PATCH 11/31] Rename `EnableTokenError::TokenIsAlreadyActivated` to `TickerAlreadyInUse` --- mm2src/coins/lp_coins.rs | 3 +-- mm2src/coins_activation/src/erc20_token_activation.rs | 2 +- mm2src/coins_activation/src/prelude.rs | 1 - mm2src/coins_activation/src/token.rs | 11 +++++------ 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 9690ab9068..30bb0aea04 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4332,9 +4332,8 @@ impl CoinProtocol { // If there is, return an error as the user should use this token instead of activating a custom one. // This is necessary as we will create an orderbook for this custom token using the contract address, // if it is duplicated in config, we will have two orderbooks one using the ticker and one using the contract address. - // Todo: We should use the contract address for orderbook topics instead of the ticker. + // Todo: We should use the contract address for orderbook topics instead of the ticker once we make custom tokens non-wallet only. // If a coin is added to the config later, users who added it as a custom token and did not update will not see the orderbook. - // Todo: GUI should be responsible for enabling the coin from config instead, I should leave a To Test or docs note about this if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(ctx, platform, contract_address) { return Err(MmError::new(CustomTokenError::DuplicateContractInConfig { ticker_in_config: existing_ticker, diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index c81cf0a715..75e553ff30 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -45,7 +45,7 @@ impl From for EnableTokenError { EthTokenActivationError::UnexpectedDerivationMethod(e) => EnableTokenError::UnexpectedDerivationMethod(e), EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EnableTokenError::PrivKeyPolicyNotAllowed(e), EthTokenActivationError::TokenAlreadyActivated { ticker, .. } => { - EnableTokenError::TokenIsAlreadyActivated(ticker) + EnableTokenError::TickerAlreadyInUse(ticker) }, } } diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index ceb9e488d3..99467c334d 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -93,7 +93,6 @@ pub fn coin_conf_with_protocol( }); protocol_from_request } else { - // Todo: should include ticker from config in the new decimals/symbol RPC let protocol_from_config = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { CoinConfWithProtocolError::CoinProtocolParseError { ticker: coin.into(), diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index b9bca3188a..e338d4638f 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -37,8 +37,8 @@ pub trait TokenActivationOps: Into + platform_coin_with_tokens::Toke #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum EnableTokenError { - #[display(fmt = "Token {} is already activated", _0)] - TokenIsAlreadyActivated(String), + #[display(fmt = "Ticker {} is already in use", _0)] + TickerAlreadyInUse(String), #[display(fmt = "Token {} config is not found", _0)] TokenConfigIsNotFound(String), #[display(fmt = "Token {} protocol parsing failed: {}", ticker, error)] @@ -73,7 +73,7 @@ pub enum EnableTokenError { impl From for EnableTokenError { fn from(err: RegisterCoinError) -> Self { match err { - RegisterCoinError::CoinIsInitializedAlready { coin } => Self::TokenIsAlreadyActivated(coin), + RegisterCoinError::CoinIsInitializedAlready { coin } => Self::TickerAlreadyInUse(coin), RegisterCoinError::Internal(err) => Self::Internal(err), } } @@ -124,8 +124,7 @@ where (Token::ActivationError, EnableTokenError): NotEqual, { if let Ok(Some(_)) = lp_coinfind(&ctx, &req.ticker).await { - // Todo: this should not be token already activated error, but same ticker name is already used - return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); + return MmError::err(EnableTokenError::TickerAlreadyInUse(req.ticker)); } let (token_conf, token_protocol): (_, Token::ProtocolInfo) = @@ -174,7 +173,7 @@ impl From for EnableTokenError { impl HttpStatusCode for EnableTokenError { fn status_code(&self) -> StatusCode { match self { - EnableTokenError::TokenIsAlreadyActivated(_) + EnableTokenError::TickerAlreadyInUse(_) | EnableTokenError::PlatformCoinIsNotActivated(_) | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } From f67621229a12013b2ddbc74c0e93a615061125a4 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 29 Oct 2024 00:41:59 +0300 Subject: [PATCH 12/31] move custom_token.rs to lp_commands Also split lp_commands.rs file to multiple files --- mm2src/coins/eth.rs | 2 +- mm2src/coins/eth/erc20.rs | 2 +- mm2src/coins/lp_coins.rs | 6 +- mm2src/mm2_bin_lib/src/mm2_native_lib.rs | 2 +- mm2src/mm2_main/src/rpc.rs | 4 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 12 ++-- .../src/rpc/dispatcher/dispatcher_legacy.rs | 2 +- .../src/rpc/lp_commands}/custom_token.rs | 6 +- mm2src/mm2_main/src/rpc/lp_commands/db_id.rs | 18 ++++++ .../{lp_commands_legacy.rs => legacy.rs} | 0 mm2src/mm2_main/src/rpc/lp_commands/mod.rs | 5 ++ mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs | 48 +++++++++++++++ .../lp_commands/{lp_commands.rs => trezor.rs} | 58 +------------------ 13 files changed, 90 insertions(+), 75 deletions(-) rename mm2src/{coins => mm2_main/src/rpc/lp_commands}/custom_token.rs (95%) create mode 100644 mm2src/mm2_main/src/rpc/lp_commands/db_id.rs rename mm2src/mm2_main/src/rpc/lp_commands/{lp_commands_legacy.rs => legacy.rs} (100%) create mode 100644 mm2src/mm2_main/src/rpc/lp_commands/mod.rs create mode 100644 mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs rename mm2src/mm2_main/src/rpc/lp_commands/{lp_commands.rs => trezor.rs} (52%) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index fda099ca45..1e83724f2b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -158,7 +158,7 @@ pub(crate) use eip1559_gas_fee::FeePerGasEstimated; use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, InfuraGasApiCaller}; -pub(crate) mod erc20; +pub mod erc20; use erc20::get_token_decimals; pub(crate) mod eth_swap_v2; diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs index 8ac517f5e9..55b8269643 100644 --- a/mm2src/coins/eth/erc20.rs +++ b/mm2src/coins/eth/erc20.rs @@ -57,7 +57,7 @@ pub struct Erc20CustomTokenInfo { pub decimals: u8, } -pub(crate) async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { +pub async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { let symbol = get_token_symbol(coin, token_addr).await?; let web3 = try_s!(coin.web3().await); let decimals = get_token_decimals(&web3, token_addr).await?; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 30bb0aea04..319ae61240 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -206,8 +206,6 @@ macro_rules! ok_or_continue_after_sleep { pub mod coin_balance; use coin_balance::{AddressBalanceStatus, HDAddressBalance, HDWalletBalanceOps}; -pub mod custom_token; - pub mod lp_price; pub mod watcher_common; @@ -4275,7 +4273,7 @@ pub enum CustomTokenError { impl CoinProtocol { /// Returns the platform coin associated with the coin protocol, if any. - fn platform(&self) -> Option<&str> { + pub fn platform(&self) -> Option<&str> { match self { CoinProtocol::QRC20 { platform, .. } | CoinProtocol::ERC20 { platform, .. } @@ -4296,7 +4294,7 @@ impl CoinProtocol { } /// Returns the contract address associated with the coin, if any. - fn contract_address(&self) -> Option<&str> { + pub fn contract_address(&self) -> Option<&str> { match self { CoinProtocol::QRC20 { contract_address, .. } | CoinProtocol::ERC20 { contract_address, .. } => { Some(contract_address) diff --git a/mm2src/mm2_bin_lib/src/mm2_native_lib.rs b/mm2src/mm2_bin_lib/src/mm2_native_lib.rs index 94ed6daf62..17d7a839bc 100644 --- a/mm2src/mm2_bin_lib/src/mm2_native_lib.rs +++ b/mm2src/mm2_bin_lib/src/mm2_native_lib.rs @@ -123,7 +123,7 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char }, }; let conf = json::to_string(&ctx.conf).unwrap(); - let hy_res = mm2_main::rpc::lp_commands_legacy::stop(ctx); + let hy_res = mm2_main::rpc::lp_commands::legacy::stop(ctx); let r = match block_on(hy_res) { Ok(r) => r, Err(err) => { diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index 85b61db612..1f0afd3234 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -44,9 +44,7 @@ cfg_native! { #[path = "rpc/dispatcher/dispatcher.rs"] mod dispatcher; #[path = "rpc/dispatcher/dispatcher_legacy.rs"] mod dispatcher_legacy; -#[path = "rpc/lp_commands/lp_commands.rs"] pub mod lp_commands; -#[path = "rpc/lp_commands/lp_commands_legacy.rs"] -pub mod lp_commands_legacy; +pub mod lp_commands; mod rate_limiter; /// Lists the RPC method not requiring the "userpass" authentication. diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 16650382b6..c76feca33a 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -5,14 +5,16 @@ use crate::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_trezor use crate::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; use crate::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, stop_simple_market_maker_bot}; +use crate::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, + stop_version_stat_collection, update_version_stat_collection}; use crate::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; +use crate::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}; use crate::lp_wallet::{get_mnemonic_rpc, get_wallet_names_rpc}; +use crate::rpc::lp_commands::custom_token::get_custom_token_info; +use crate::rpc::lp_commands::db_id::get_shared_db_id; +use crate::rpc::lp_commands::pubkey::*; +use crate::rpc::lp_commands::trezor::trezor_connection_status; use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; -use crate::{lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, - stop_version_stat_collection, update_version_stat_collection}, - lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, - rpc::lp_commands::{get_public_key, get_public_key_hash, get_shared_db_id, trezor_connection_status}}; -use coins::custom_token::get_custom_token_info; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels}; diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs index 2415bc31ef..0f803cc7ba 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs @@ -7,7 +7,7 @@ use mm2_core::mm_ctx::MmArc; use serde_json::{self as json, Value as Json}; use std::net::SocketAddr; -use super::lp_commands_legacy::*; +use super::lp_commands::legacy::*; use crate::lp_ordermatch::{best_orders_rpc, buy, cancel_all_orders_rpc, cancel_order_rpc, my_orders, order_status, orderbook_depth_rpc, orderbook_rpc, orders_history_by_filter, sell, set_price, update_maker_order_rpc}; diff --git a/mm2src/coins/custom_token.rs b/mm2src/mm2_main/src/rpc/lp_commands/custom_token.rs similarity index 95% rename from mm2src/coins/custom_token.rs rename to mm2src/mm2_main/src/rpc/lp_commands/custom_token.rs index a61a7735e6..b8be5210d3 100644 --- a/mm2src/coins/custom_token.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/custom_token.rs @@ -1,6 +1,6 @@ -use crate::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20CustomTokenInfo}; -use crate::eth::valid_addr_from_str; -use crate::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; +use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20CustomTokenInfo}; +use coins::eth::valid_addr_from_str; +use coins::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; use common::HttpStatusCode; use http::StatusCode; use mm2_core::mm_ctx::MmArc; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs b/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs new file mode 100644 index 0000000000..29fa399bd0 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs @@ -0,0 +1,18 @@ +use crate::rpc::lp_commands::pubkey::GetPublicKeyError; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmError; +use rpc::v1::types::H160 as H160Json; +use serde_json::Value as Json; + +pub type GetSharedDbIdResult = Result>; +pub type GetSharedDbIdError = GetPublicKeyError; + +#[derive(Serialize)] +pub struct GetSharedDbIdResponse { + shared_db_id: H160Json, +} + +pub async fn get_shared_db_id(ctx: MmArc, _req: Json) -> GetSharedDbIdResult { + let shared_db_id = ctx.shared_db_id().to_owned().into(); + Ok(GetSharedDbIdResponse { shared_db_id }) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/legacy.rs similarity index 100% rename from mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs rename to mm2src/mm2_main/src/rpc/lp_commands/legacy.rs diff --git a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs new file mode 100644 index 0000000000..846485ddfa --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod custom_token; +pub(crate) mod db_id; +pub mod legacy; +pub(crate) mod pubkey; +pub(crate) mod trezor; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs new file mode 100644 index 0000000000..f5a5a95063 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs @@ -0,0 +1,48 @@ +use common::HttpStatusCode; +use crypto::{CryptoCtx, CryptoCtxError}; +use derive_more::Display; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use rpc::v1::types::H160 as H160Json; +use serde_json::Value as Json; + +pub type GetPublicKeyRpcResult = Result>; + +#[derive(Serialize, Display, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetPublicKeyError { + Internal(String), +} + +impl From for GetPublicKeyError { + fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } +} + +#[derive(Serialize)] +pub struct GetPublicKeyResponse { + public_key: String, +} + +impl HttpStatusCode for GetPublicKeyError { + fn status_code(&self) -> StatusCode { + match self { + GetPublicKeyError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { + let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); + Ok(GetPublicKeyResponse { public_key }) +} + +#[derive(Serialize)] +pub struct GetPublicKeyHashResponse { + public_key_hash: H160Json, +} + +pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { + let public_key_hash = ctx.rmd160().to_owned().into(); + Ok(GetPublicKeyHashResponse { public_key_hash }) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs b/mm2src/mm2_main/src/rpc/lp_commands/trezor.rs similarity index 52% rename from mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs rename to mm2src/mm2_main/src/rpc/lp_commands/trezor.rs index ae992c6d3e..16698eb3cc 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/trezor.rs @@ -1,63 +1,9 @@ use common::HttpStatusCode; use crypto::{CryptoCtx, CryptoCtxError, HwConnectionStatus, HwPubkey}; -use derive_more::Display; use http::StatusCode; use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use rpc::v1::types::H160 as H160Json; -use serde_json::Value as Json; - -pub type GetPublicKeyRpcResult = Result>; -pub type GetSharedDbIdResult = Result>; -pub type GetSharedDbIdError = GetPublicKeyError; - -#[derive(Serialize, Display, SerializeErrorType)] -#[serde(tag = "error_type", content = "error_data")] -pub enum GetPublicKeyError { - Internal(String), -} - -impl From for GetPublicKeyError { - fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } -} - -#[derive(Serialize)] -pub struct GetPublicKeyResponse { - public_key: String, -} - -impl HttpStatusCode for GetPublicKeyError { - fn status_code(&self) -> StatusCode { - match self { - GetPublicKeyError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); - Ok(GetPublicKeyResponse { public_key }) -} - -#[derive(Serialize)] -pub struct GetPublicKeyHashResponse { - public_key_hash: H160Json, -} - -pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key_hash = ctx.rmd160().to_owned().into(); - Ok(GetPublicKeyHashResponse { public_key_hash }) -} - -#[derive(Serialize)] -pub struct GetSharedDbIdResponse { - shared_db_id: H160Json, -} - -pub async fn get_shared_db_id(ctx: MmArc, _req: Json) -> GetSharedDbIdResult { - let shared_db_id = ctx.shared_db_id().to_owned().into(); - Ok(GetSharedDbIdResponse { shared_db_id }) -} +use mm2_err_handle::mm_error::{MmError, MmResult}; +use mm2_err_handle::or_mm_error::OrMmError; #[derive(Serialize, Display, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] From f05b672193b6e0da62833f9087055bb428b08a44 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 29 Oct 2024 17:23:30 +0300 Subject: [PATCH 13/31] Add test for enabling and disabling custom erc20 tokens --- .../tests/docker_tests/docker_tests_inner.rs | 51 ++++++++++-- mm2src/mm2_test_helpers/src/for_tests.rs | 80 +++++++++++++++++++ mm2src/mm2_test_helpers/src/structs.rs | 20 +++++ 3 files changed, 146 insertions(+), 5 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index d89b456874..1ec7976b14 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -17,11 +17,11 @@ use crypto::privkey::key_pair_from_seed; use crypto::{CryptoCtx, DerivationPath, KeyPairPolicy}; use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; -use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_eth_coin, - enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, get_locked_amount, - kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, - wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, - MarketMakerIt, Mm2TestConf}; +use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_erc20_token_v2, + enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, + get_locked_amount, kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, + set_price, start_swaps, wait_for_swap_contract_negotiation, + wait_for_swap_negotiation_failure, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::{get_passphrase, structs::*}; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; @@ -5378,6 +5378,47 @@ fn test_enable_eth_erc20_coins_with_enable_hd() { block_on(mm_hd.stop()).unwrap(); } +// Todo: move this and others to an appropriate place +// Todo: Test is wallet only +#[test] +fn test_enable_disable_custom_erc20() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf()]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + // Enable ERC20DEV custom token in HD mode + let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); + block_on(enable_erc20_token_v2( + &mm_hd, + "ERC20DEV", + Some(protocol), + true, + 60, + Some(path_to_address), + )); + + // Disable ERC20DEV custom token in HD mode + block_on(disable_coin(&mm_hd, "ERC20DEV", true)); +} + fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 25ebef2df5..61ca0099a8 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3253,6 +3253,86 @@ pub async fn enable_eth_with_tokens_v2( } } +async fn init_erc20_token( + mm: &MarketMakerIt, + token: &str, + protocol: Option, + is_custom: bool, + path_to_address: Option, +) -> Json { + let response = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::init", + "mmrpc": "2.0", + "params": { + "ticker": token, + "protocol": protocol, + "activation_params": { + "is_custom": is_custom, + "path_to_address": path_to_address.unwrap_or_default(), + } + } + })) + .await + .unwrap(); + assert_eq!( + response.0, + StatusCode::OK, + "'task::enable_erc20::init' failed: {}", + response.1 + ); + json::from_str(&response.1).unwrap() +} + +async fn init_erc20_token_status(mm: &MarketMakerIt, task_id: u64) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::status", + "mmrpc": "2.0", + "params": { + "task_id": task_id, + } + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'task::enable_erc20::status' failed: {}", + request.1 + ); + json::from_str(&request.1).unwrap() +} + +pub async fn enable_erc20_token_v2( + mm: &MarketMakerIt, + token: &str, + protocol: Option, + is_custom: bool, + timeout: u64, + path_to_address: Option, +) -> InitTokenActivationResult { + let init = init_erc20_token(mm, token, protocol, is_custom, path_to_address).await; + let init: RpcV2Response = json::from_value(init).unwrap(); + let timeout = wait_until_ms(timeout * 1000); + + loop { + if now_ms() > timeout { + panic!("{} initialization timed out", token); + } + + let status = init_erc20_token_status(mm, init.result.task_id).await; + let status: RpcV2Response = json::from_value(status).unwrap(); + match status.result { + InitErc20TokenStatus::Ok(result) => break result, + InitErc20TokenStatus::Error(e) => panic!("{} initialization error {:?}", token, e), + _ => Timer::sleep(1.).await, + } + } +} + /// Note that mm2 ignores `volume` if `max` is true. pub async fn set_price( mm: &MarketMakerIt, diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index 2dd72a8435..6a510a1880 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -714,6 +714,15 @@ pub enum InitEthWithTokensStatus { UserActionRequired(Json), } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields, tag = "status", content = "details")] +pub enum InitErc20TokenStatus { + Ok(InitTokenActivationResult), + Error(Json), + InProgress(Json), + UserActionRequired(Json), +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields, tag = "status", content = "details")] pub enum InitLightningStatus { @@ -911,6 +920,17 @@ pub enum EthWithTokensActivationResult { HD(HDEthWithTokensActivationResult), } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct InitTokenActivationResult { + pub ticker: String, + pub platform_coin: String, + pub token_contract_address: String, + pub current_block: u64, + pub required_confirmations: u64, + pub wallet_balance: EnableCoinBalanceMap, +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct EnableBchWithTokensResponse { From eef94b24f1de89248bda660494435734b326f8ba Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 29 Oct 2024 17:51:24 +0300 Subject: [PATCH 14/31] Make a coin not in config as wallet only and add test for it --- mm2src/coins/lp_coins.rs | 16 +++++++++++++- mm2src/coins/tendermint/tendermint_coin.rs | 4 ++++ mm2src/coins/tendermint/tendermint_token.rs | 4 ++++ .../tests/docker_tests/docker_tests_inner.rs | 21 +++++++++++++++++-- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 319ae61240..336ef4fa67 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3276,6 +3276,10 @@ pub trait MmCoin: /// The coin can be initialized, but it cannot participate in the swaps. fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } coin_conf["wallet_only"].as_bool().unwrap_or(false) } @@ -4514,10 +4518,20 @@ pub fn coin_conf(ctx: &MmArc, ticker: &str) -> Json { } } -pub fn is_wallet_only_conf(conf: &Json) -> bool { conf["wallet_only"].as_bool().unwrap_or(false) } +pub fn is_wallet_only_conf(conf: &Json) -> bool { + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if conf.is_null() { + return true; + } + conf["wallet_only"].as_bool().unwrap_or(false) +} pub fn is_wallet_only_ticker(ctx: &MmArc, ticker: &str) -> bool { let coin_conf = coin_conf(ctx, ticker); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } coin_conf["wallet_only"].as_bool().unwrap_or(false) } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 680f0a1782..a7c4550674 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2162,6 +2162,10 @@ impl MmCoin for TendermintCoin { fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = crate::coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false); wallet_only_conf || self.is_keplr_from_ledger diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 2f363a6b3c..ea9d12e439 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -489,6 +489,10 @@ impl MmCoin for TendermintToken { fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = crate::coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false); wallet_only_conf || self.platform_coin.is_keplr_from_ledger diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 1ec7976b14..b6c9810ddf 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -5379,7 +5379,6 @@ fn test_enable_eth_erc20_coins_with_enable_hd() { } // Todo: move this and others to an appropriate place -// Todo: Test is wallet only #[test] fn test_enable_disable_custom_erc20() { const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; @@ -5405,16 +5404,34 @@ fn test_enable_disable_custom_erc20() { )); // Enable ERC20DEV custom token in HD mode + let token = "ERC20DEV"; let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); block_on(enable_erc20_token_v2( &mm_hd, - "ERC20DEV", + token, Some(protocol), true, 60, Some(path_to_address), )); + // Test that the custom token is wallet only by using it in a swap + let buy = block_on(mm_hd.rpc(&json!({ + "userpass": mm_hd.userpass, + "method": "buy", + "base": "ETH", + "rel": "ERC20DEV", + "price": "1", + "volume": "1", + }))) + .unwrap(); + assert!(!buy.0.is_success(), "buy success, but should fail: {}", buy.1); + assert!( + buy.1.contains(&format!("Rel coin {} is wallet only", token)), + "Expected error message indicating that the token is wallet only, but got: {}", + buy.1 + ); + // Disable ERC20DEV custom token in HD mode block_on(disable_coin(&mm_hd, "ERC20DEV", true)); } From c62f4a749ec37a56a6ba0dc67f2c26f0f3252e5d Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 29 Oct 2024 19:08:17 +0300 Subject: [PATCH 15/31] rename `test_enable_disable_custom_erc20` to `test_custom_erc20` and add test coverage for `get_custom_token_info` RPC in it --- .../tests/docker_tests/docker_tests_inner.rs | 28 +++++++++------ mm2src/mm2_test_helpers/src/for_tests.rs | 34 +++++++++++++++---- mm2src/mm2_test_helpers/src/structs.rs | 22 ++++++++++++ 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index b6c9810ddf..d8a53b953c 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -19,9 +19,10 @@ use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_erc20_token_v2, enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, - get_locked_amount, kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, - set_price, start_swaps, wait_for_swap_contract_negotiation, - wait_for_swap_negotiation_failure, MarketMakerIt, Mm2TestConf}; + get_custom_token_info, get_locked_amount, kmd_conf, max_maker_vol, mm_dump, + mycoin1_conf, mycoin_conf, set_price, start_swaps, + wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, + MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::{get_passphrase, structs::*}; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; @@ -5380,7 +5381,7 @@ fn test_enable_eth_erc20_coins_with_enable_hd() { // Todo: move this and others to an appropriate place #[test] -fn test_enable_disable_custom_erc20() { +fn test_custom_erc20() { const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; let coins = json!([eth_dev_conf()]); @@ -5403,12 +5404,17 @@ fn test_enable_disable_custom_erc20() { Some(path_to_address.clone()), )); - // Enable ERC20DEV custom token in HD mode - let token = "ERC20DEV"; + // Test `get_custom_token_info` rpc, we also use it to get the token symbol to use it as the ticker let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); + let CustomTokenInfo::ERC20(custom_token_info) = block_on(get_custom_token_info(&mm_hd, protocol.clone())).info; + let ticker = custom_token_info.symbol; + assert_eq!(ticker, "QTC"); + assert_eq!(custom_token_info.decimals, 8); + + // Enable the custom token in HD mode block_on(enable_erc20_token_v2( &mm_hd, - token, + &ticker, Some(protocol), true, 60, @@ -5420,20 +5426,20 @@ fn test_enable_disable_custom_erc20() { "userpass": mm_hd.userpass, "method": "buy", "base": "ETH", - "rel": "ERC20DEV", + "rel": ticker, "price": "1", "volume": "1", }))) .unwrap(); assert!(!buy.0.is_success(), "buy success, but should fail: {}", buy.1); assert!( - buy.1.contains(&format!("Rel coin {} is wallet only", token)), + buy.1.contains(&format!("Rel coin {} is wallet only", ticker)), "Expected error message indicating that the token is wallet only, but got: {}", buy.1 ); - // Disable ERC20DEV custom token in HD mode - block_on(disable_coin(&mm_hd, "ERC20DEV", true)); + // Disable the custom token in HD mode + block_on(disable_coin(&mm_hd, &ticker, true)); } fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 61ca0099a8..0f1a0ccc12 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3255,7 +3255,7 @@ pub async fn enable_eth_with_tokens_v2( async fn init_erc20_token( mm: &MarketMakerIt, - token: &str, + ticker: &str, protocol: Option, is_custom: bool, path_to_address: Option, @@ -3266,7 +3266,7 @@ async fn init_erc20_token( "method": "task::enable_erc20::init", "mmrpc": "2.0", "params": { - "ticker": token, + "ticker": ticker, "protocol": protocol, "activation_params": { "is_custom": is_custom, @@ -3308,31 +3308,53 @@ async fn init_erc20_token_status(mm: &MarketMakerIt, task_id: u64) -> Json { pub async fn enable_erc20_token_v2( mm: &MarketMakerIt, - token: &str, + ticker: &str, protocol: Option, is_custom: bool, timeout: u64, path_to_address: Option, ) -> InitTokenActivationResult { - let init = init_erc20_token(mm, token, protocol, is_custom, path_to_address).await; + let init = init_erc20_token(mm, ticker, protocol, is_custom, path_to_address).await; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(timeout * 1000); loop { if now_ms() > timeout { - panic!("{} initialization timed out", token); + panic!("{} initialization timed out", ticker); } let status = init_erc20_token_status(mm, init.result.task_id).await; let status: RpcV2Response = json::from_value(status).unwrap(); match status.result { InitErc20TokenStatus::Ok(result) => break result, - InitErc20TokenStatus::Error(e) => panic!("{} initialization error {:?}", token, e), + InitErc20TokenStatus::Error(e) => panic!("{} initialization error {:?}", ticker, e), _ => Timer::sleep(1.).await, } } } +pub async fn get_custom_token_info(mm: &MarketMakerIt, protocol: Json) -> CustomTokenInfoResponse { + let response = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "get_custom_token_info", + "mmrpc": "2.0", + "params": { + "protocol": protocol, + } + })) + .await + .unwrap(); + assert_eq!( + response.0, + StatusCode::OK, + "'get_custom_token_info' failed: {}", + response.1 + ); + let response_json: Json = json::from_str(&response.1).unwrap(); + json::from_value(response_json["result"].clone()).unwrap() +} + /// Note that mm2 ignores `volume` if `max` is true. pub async fn set_price( mm: &MarketMakerIt, diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index 6a510a1880..6693d15577 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -1206,3 +1206,25 @@ pub struct ActiveSwapsResponse { pub uuids: Vec, pub statuses: Option>, } + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Erc20CustomTokenInfo { + pub symbol: String, + pub decimals: u8, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(tag = "type", content = "info")] +pub enum CustomTokenInfo { + ERC20(Erc20CustomTokenInfo), +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct CustomTokenInfoResponse { + pub config_ticker: Option, + #[serde(flatten)] + pub info: CustomTokenInfo, +} From 551b6a7823e7c1ea71b1709c01718575be6557f2 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 30 Oct 2024 00:01:03 +0300 Subject: [PATCH 16/31] Add test to verify error handling for enabling a custom token with a duplicate contract in the coins config --- mm2src/coins/lp_coins.rs | 2 +- mm2src/coins_activation/src/init_token.rs | 7 +-- mm2src/coins_activation/src/token.rs | 6 +-- .../tests/docker_tests/docker_tests_inner.rs | 46 ++++++++++++++++++- mm2src/mm2_test_helpers/src/for_tests.rs | 41 +++++++++++------ 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 336ef4fa67..527cd08db6 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4256,7 +4256,7 @@ pub enum CoinProtocol { }, } -#[derive(Debug, Display)] +#[derive(Clone, Debug, Display, Serialize)] #[allow(clippy::large_enum_variant)] pub enum CustomTokenError { #[display( diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index d7a22f06f2..27c6621daa 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -5,7 +5,8 @@ use crate::prelude::{coin_conf_with_protocol, CoinConfWithProtocolError, Current use crate::token::TokenProtocolParams; use async_trait::async_trait; use coins::coin_balance::CoinBalanceReport; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinBalanceMap, CoinProtocol, CoinsContext, MmCoinEnum, RegisterCoinError}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinBalanceMap, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + RegisterCoinError}; use common::{log, HttpStatusCode, StatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use crypto::HwRpcError; @@ -318,7 +319,7 @@ pub enum InitTokenError { token_ticker: String, }, #[display(fmt = "Custom token error: {}", _0)] - CustomTokenError(String), + CustomTokenError(CustomTokenError), #[display(fmt = "{}", _0)] HwError(HwRpcError), #[display(fmt = "Transport error: {}", _0)] @@ -340,7 +341,7 @@ impl From for InitTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokenError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::CustomTokenError(e) => InitTokenError::CustomTokenError(e.to_string()), + CoinConfWithProtocolError::CustomTokenError(e) => InitTokenError::CustomTokenError(e), } } } diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index e338d4638f..ce703e209b 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -4,7 +4,7 @@ use crate::platform_coin_with_tokens::{self, RegisterTokenInfo}; use crate::prelude::*; use async_trait::async_trait; use coins::utxo::rpc_clients::UtxoRpcError; -use coins::{lp_coinfind, lp_coinfind_or_err, BalanceError, CoinProtocol, CoinsContext, MmCoinEnum, +use coins::{lp_coinfind, lp_coinfind_or_err, BalanceError, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, PrivKeyPolicyNotAllowed, RegisterCoinError, UnexpectedDerivationMethod}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; @@ -59,7 +59,7 @@ pub enum EnableTokenError { token_ticker: String, }, #[display(fmt = "Custom token error: {}", _0)] - CustomTokenError(String), + CustomTokenError(CustomTokenError), #[display(fmt = "{}", _0)] UnexpectedDerivationMethod(UnexpectedDerivationMethod), CouldNotFetchBalance(String), @@ -92,7 +92,7 @@ impl From for EnableTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { EnableTokenError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e.to_string()), + CoinConfWithProtocolError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e), } } } diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index d8a53b953c..31466fc322 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -19,8 +19,8 @@ use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_erc20_token_v2, enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, - get_custom_token_info, get_locked_amount, kmd_conf, max_maker_vol, mm_dump, - mycoin1_conf, mycoin_conf, set_price, start_swaps, + get_custom_token_info, get_locked_amount, init_erc20_token, kmd_conf, max_maker_vol, + mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::{get_passphrase, structs::*}; @@ -5442,6 +5442,48 @@ fn test_custom_erc20() { block_on(disable_coin(&mm_hd, &ticker, true)); } +#[test] +fn test_enable_custom_token_with_duplicate_contract_in_config() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let erc20_dev_conf = erc20_dev_conf(&erc20_contract_checksum()); + let coins = json!([eth_dev_conf(), erc20_dev_conf]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + // Enable the custom token in HD mode. + // Since the contract is already in the coins config, this should fail with an error + // that specifies the ticker in config so that the user can enable the right coin. + let err = block_on(init_erc20_token( + &mm_hd, + "QTC", + Some(erc20_dev_conf["protocol"].clone()), + true, + Some(path_to_address), + )) + .unwrap_err(); + assert_eq!( + err["error_data"], + json!({"DuplicateContractInConfig":{"ticker_in_config":"ERC20DEV"}}) + ); +} + fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 0f1a0ccc12..155f73d350 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3253,36 +3253,51 @@ pub async fn enable_eth_with_tokens_v2( } } -async fn init_erc20_token( +pub async fn init_erc20_token( mm: &MarketMakerIt, ticker: &str, protocol: Option, is_custom: bool, path_to_address: Option, -) -> Json { - let response = mm - .rpc(&json!({ +) -> Result<(StatusCode, Json), Json> { + let (status, response, _) = mm.rpc(&json!({ "userpass": mm.userpass, "method": "task::enable_erc20::init", "mmrpc": "2.0", "params": { - "ticker": ticker, - "protocol": protocol, - "activation_params": { - "is_custom": is_custom, - "path_to_address": path_to_address.unwrap_or_default(), - } + "ticker": ticker, + "protocol": protocol, + "activation_params": { + "is_custom": is_custom, + "path_to_address": path_to_address.unwrap_or_default(), } - })) + } + })) .await .unwrap(); + + if status.is_success() { + Ok((status, json::from_str(&response).unwrap())) + } else { + Err(json::from_str(&response).unwrap()) + } +} + +async fn init_erc20_token_and_assert_response( + mm: &MarketMakerIt, + ticker: &str, + protocol: Option, + is_custom: bool, + path_to_address: Option, +) -> Json { + let response = init_erc20_token(mm, ticker, protocol, is_custom, path_to_address).await.unwrap(); assert_eq!( response.0, StatusCode::OK, "'task::enable_erc20::init' failed: {}", response.1 ); - json::from_str(&response.1).unwrap() + response.1 } async fn init_erc20_token_status(mm: &MarketMakerIt, task_id: u64) -> Json { @@ -3314,7 +3329,7 @@ pub async fn enable_erc20_token_v2( timeout: u64, path_to_address: Option, ) -> InitTokenActivationResult { - let init = init_erc20_token(mm, ticker, protocol, is_custom, path_to_address).await; + let init = init_erc20_token_and_assert_response(mm, ticker, protocol, is_custom, path_to_address).await; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(timeout * 1000); From a8396b0021302171e337b29d196641910d439361 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 30 Oct 2024 00:15:52 +0300 Subject: [PATCH 17/31] Add test coverage for `get_custom_token_info` RPC if the same contract is in coins config --- .../tests/docker_tests/docker_tests_inner.rs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 31466fc322..a5db3ce4cd 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -5438,7 +5438,7 @@ fn test_custom_erc20() { buy.1 ); - // Disable the custom token in HD mode + // Disable the custom token block_on(disable_coin(&mm_hd, &ticker, true)); } @@ -5467,21 +5467,40 @@ fn test_enable_custom_token_with_duplicate_contract_in_config() { Some(path_to_address.clone()), )); + let protocol = erc20_dev_conf["protocol"].clone(); // Enable the custom token in HD mode. // Since the contract is already in the coins config, this should fail with an error // that specifies the ticker in config so that the user can enable the right coin. let err = block_on(init_erc20_token( &mm_hd, "QTC", - Some(erc20_dev_conf["protocol"].clone()), + Some(protocol.clone()), true, - Some(path_to_address), + Some(path_to_address.clone()), )) .unwrap_err(); assert_eq!( err["error_data"], json!({"DuplicateContractInConfig":{"ticker_in_config":"ERC20DEV"}}) ); + + // Another way is to use the `get_custom_token_info` RPC and use the config ticker to enable the token. + let custom_token_info = block_on(get_custom_token_info(&mm_hd, protocol)); + assert!(custom_token_info.config_ticker.is_some()); + let config_ticker = custom_token_info.config_ticker.unwrap(); + assert_eq!(config_ticker, "ERC20DEV"); + // Parameters passed here are for normal enabling of a coin in config and not for a custom token + block_on(enable_erc20_token_v2( + &mm_hd, + &config_ticker, + None, + false, + 60, + Some(path_to_address), + )); + + // Disable the custom token, this to check that it was enabled correctly + block_on(disable_coin(&mm_hd, &config_ticker, true)); } fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { From d2a678f3e5a3320c644fc8b8d9a3fe8fcf41f6e4 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 30 Oct 2024 14:25:50 +0300 Subject: [PATCH 18/31] Add test coverage for enabling a custom token with a contract that has another ticker enabled --- mm2src/coins/eth/v2_activation.rs | 41 +++++----------- mm2src/coins/lp_coins.rs | 16 ++++-- .../src/erc20_token_activation.rs | 4 +- .../src/eth_with_token_activation.rs | 16 +----- .../src/init_erc20_token_activation.rs | 9 ++-- .../src/platform_coin_with_tokens.rs | 14 +++--- mm2src/coins_activation/src/prelude.rs | 8 +-- mm2src/coins_activation/src/token.rs | 14 +++--- .../tests/docker_tests/docker_tests_inner.rs | 49 ++++++++++++++----- mm2src/mm2_test_helpers/src/for_tests.rs | 27 ++-------- 10 files changed, 91 insertions(+), 107 deletions(-) diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 823344723b..3312d854a0 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -35,15 +35,6 @@ pub enum EthActivationV2Error { ticker: String, error: String, }, - #[display( - fmt = "Token is already activated, ticker: {}, contract address: {}", - ticker, - contract_address - )] - TokenAlreadyActivated { - ticker: String, - contract_address: String, - }, CouldNotFetchBalance(String), UnreachableNodes(String), #[display(fmt = "Enable request for ETH coin must have at least 1 node")] @@ -72,6 +63,8 @@ pub enum EthActivationV2Error { HwError(HwRpcError), #[display(fmt = "Hardware wallet must be called within rpc task framework")] InvalidHardwareWalletCall, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EthActivationV2Error { @@ -103,13 +96,7 @@ impl From for EthActivationV2Error { EthActivationV2Error::UnexpectedDerivationMethod(err) }, EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EthActivationV2Error::PrivKeyPolicyNotAllowed(e), - EthTokenActivationError::TokenAlreadyActivated { - ticker, - contract_address, - } => EthActivationV2Error::TokenAlreadyActivated { - ticker, - contract_address, - }, + EthTokenActivationError::CustomTokenError(e) => EthActivationV2Error::CustomTokenError(e), } } } @@ -228,15 +215,7 @@ pub enum EthTokenActivationError { Transport(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), - #[display( - fmt = "Token is already activated, ticker: {}, contract address: {}", - ticker, - contract_address - )] - TokenAlreadyActivated { - ticker: String, - contract_address: String, - }, + CustomTokenError(CustomTokenError), } impl From for EthTokenActivationError { @@ -425,11 +404,13 @@ impl EthCoin { // `is_custom` was added to avoid this unnecessary check for non-custom tokens if activation_params.is_custom { match get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { - Ok(Some(_)) => { - return MmError::err(EthTokenActivationError::TokenAlreadyActivated { - ticker, - contract_address: display_eth_address(&protocol.token_addr), - }); + Ok(Some(token)) => { + return MmError::err(EthTokenActivationError::CustomTokenError( + CustomTokenError::TokenWithSameContractAlreadyActivated { + ticker: token.ticker().to_string(), + contract_address: display_eth_address(&protocol.token_addr), + }, + )); }, Ok(None) => {}, Err(e) => return MmError::err(EthTokenActivationError::InternalError(e.to_string())), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 527cd08db6..f331ec323c 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4256,8 +4256,7 @@ pub enum CoinProtocol { }, } -#[derive(Clone, Debug, Display, Serialize)] -#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum CustomTokenError { #[display( fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", @@ -4267,12 +4266,21 @@ pub enum CustomTokenError { )] ProtocolMismatch { ticker: String, - from_config: CoinProtocol, - from_request: CoinProtocol, + from_config: Json, + from_request: Json, }, DuplicateContractInConfig { ticker_in_config: String, }, + #[display( + fmt = "Token is already activated, ticker: {}, contract address: {}", + ticker, + contract_address + )] + TokenWithSameContractAlreadyActivated { + ticker: String, + contract_address: String, + }, } impl CoinProtocol { diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 75e553ff30..8b882f8df0 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -44,9 +44,7 @@ impl From for EnableTokenError { EthTokenActivationError::InvalidPayload(e) => EnableTokenError::InvalidPayload(e), EthTokenActivationError::UnexpectedDerivationMethod(e) => EnableTokenError::UnexpectedDerivationMethod(e), EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EnableTokenError::PrivKeyPolicyNotAllowed(e), - EthTokenActivationError::TokenAlreadyActivated { ticker, .. } => { - EnableTokenError::TickerAlreadyInUse(ticker) - }, + EthTokenActivationError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e), } } } diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index ef155aad67..f1d541a5be 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -85,13 +85,7 @@ impl From for EnablePlatformCoinWithTokensError { EthActivationV2Error::InvalidHardwareWalletCall => EnablePlatformCoinWithTokensError::Internal( "Hardware wallet must be used within rpc task manager".to_string(), ), - EthActivationV2Error::TokenAlreadyActivated { - ticker, - contract_address, - } => EnablePlatformCoinWithTokensError::TokenAlreadyActivated { - ticker, - contract_address, - }, + EthActivationV2Error::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -125,13 +119,7 @@ impl From for InitTokensAsMmCoinsError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) }, EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => InitTokensAsMmCoinsError::Internal(e.to_string()), - EthTokenActivationError::TokenAlreadyActivated { - ticker, - contract_address, - } => InitTokensAsMmCoinsError::TokenAlreadyActivated { - ticker, - contract_address, - }, + EthTokenActivationError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e), } } } diff --git a/mm2src/coins_activation/src/init_erc20_token_activation.rs b/mm2src/coins_activation/src/init_erc20_token_activation.rs index c430fe12ba..f87a939352 100644 --- a/mm2src/coins_activation/src/init_erc20_token_activation.rs +++ b/mm2src/coins_activation/src/init_erc20_token_activation.rs @@ -7,7 +7,7 @@ use coins::coin_balance::{EnableCoinBalanceError, EnableCoinBalanceOps}; use coins::eth::v2_activation::{Erc20Protocol, EthTokenActivationError, InitErc20TokenActivationRequest}; use coins::eth::EthCoin; use coins::hd_wallet::RpcTaskXPubExtractor; -use coins::{MarketCoinOps, MmCoin, RegisterCoinError}; +use coins::{CustomTokenError, MarketCoinOps, MmCoin, RegisterCoinError}; use common::Future01CompatExt; use crypto::HwRpcError; use derive_more::Display; @@ -39,6 +39,8 @@ pub enum InitErc20Error { Transport(String), #[display(fmt = "Internal error: {}", _0)] Internal(String), + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for InitTokenError { @@ -55,6 +57,7 @@ impl From for InitTokenError { InitErc20Error::CouldNotFetchBalance(error) => InitTokenError::CouldNotFetchBalance(error), InitErc20Error::Transport(transport) => InitTokenError::Transport(transport), InitErc20Error::Internal(internal) => InitTokenError::Internal(internal), + InitErc20Error::CustomTokenError(error) => InitTokenError::CustomTokenError(error), } } } @@ -69,9 +72,7 @@ impl From for InitErc20Error { | EthTokenActivationError::CouldNotFetchBalance(_) | EthTokenActivationError::InvalidPayload(_) | EthTokenActivationError::Transport(_) => InitErc20Error::Transport(e.to_string()), - EthTokenActivationError::TokenAlreadyActivated { ticker, .. } => { - InitErc20Error::TokenIsAlreadyActivated { ticker } - }, + EthTokenActivationError::CustomTokenError(e) => InitErc20Error::CustomTokenError(e), } } } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 6a692abfdd..efbee899b3 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -5,8 +5,8 @@ use crate::prelude::*; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tx_history_storage::{CreateTxHistoryStorageError, TxHistoryStorageBuilder}; -use coins::{lp_coinfind, lp_coinfind_any, CoinProtocol, CoinsContext, MmCoinEnum, PrivKeyPolicyNotAllowed, - UnexpectedDerivationMethod}; +use coins::{lp_coinfind, lp_coinfind_any, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + PrivKeyPolicyNotAllowed, UnexpectedDerivationMethod}; use common::{log, HttpStatusCode, StatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use crypto::CryptoCtxError; @@ -100,7 +100,7 @@ pub enum InitTokensAsMmCoinsError { UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, Transport(String), InvalidPayload(String), - CustomTokenError(String), + CustomTokenError(CustomTokenError), } impl From for InitTokensAsMmCoinsError { @@ -116,7 +116,7 @@ impl From for InitTokensAsMmCoinsError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokensAsMmCoinsError::UnexpectedTokenProtocol { ticker, protocol } }, - CoinConfWithProtocolError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e.to_string()), + CoinConfWithProtocolError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e), } } } @@ -299,7 +299,7 @@ pub enum EnablePlatformCoinWithTokensError { #[display(fmt = "Hardware policy must be activated within task manager")] UnexpectedDeviceActivationPolicy, #[display(fmt = "Custom token error: {}", _0)] - CustomTokenError(String), + CustomTokenError(CustomTokenError), } impl From for EnablePlatformCoinWithTokensError { @@ -317,9 +317,7 @@ impl From for EnablePlatformCoinWithTokensError { error: err.to_string(), } }, - CoinConfWithProtocolError::CustomTokenError(e) => { - EnablePlatformCoinWithTokensError::CustomTokenError(e.to_string()) - }, + CoinConfWithProtocolError::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 99467c334d..8b4cdf3fe2 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -8,7 +8,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; use serde_derive::Serialize; -use serde_json::{self as json, Value as Json}; +use serde_json::{self as json, json, Value as Json}; use std::collections::{HashMap, HashSet}; pub trait CurrentBlock { @@ -87,7 +87,7 @@ pub fn coin_conf_with_protocol( // This means it's a custom token, and we should use protocol from request if it's not None let protocol_from_request = protocol_from_request.ok_or_else(|| CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))?; - conf = json::json!({ + conf = json!({ "protocol": protocol_from_request, "wallet_only": true }); @@ -105,8 +105,8 @@ pub fn coin_conf_with_protocol( return MmError::err(CoinConfWithProtocolError::CustomTokenError( CustomTokenError::ProtocolMismatch { ticker: coin.into(), - from_config: protocol_from_config, - from_request: protocol_from_request, + from_config: json!(protocol_from_config), + from_request: json!(protocol_from_request), }, )); } diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index ce703e209b..cf1731b503 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -37,8 +37,8 @@ pub trait TokenActivationOps: Into + platform_coin_with_tokens::Toke #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum EnableTokenError { - #[display(fmt = "Ticker {} is already in use", _0)] - TickerAlreadyInUse(String), + #[display(fmt = "Token {} is already activated", _0)] + TokenIsAlreadyActivated(String), #[display(fmt = "Token {} config is not found", _0)] TokenConfigIsNotFound(String), #[display(fmt = "Token {} protocol parsing failed: {}", ticker, error)] @@ -58,8 +58,6 @@ pub enum EnableTokenError { platform_coin_ticker: String, token_ticker: String, }, - #[display(fmt = "Custom token error: {}", _0)] - CustomTokenError(CustomTokenError), #[display(fmt = "{}", _0)] UnexpectedDerivationMethod(UnexpectedDerivationMethod), CouldNotFetchBalance(String), @@ -68,12 +66,14 @@ pub enum EnableTokenError { Internal(String), InvalidPayload(String), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EnableTokenError { fn from(err: RegisterCoinError) -> Self { match err { - RegisterCoinError::CoinIsInitializedAlready { coin } => Self::TickerAlreadyInUse(coin), + RegisterCoinError::CoinIsInitializedAlready { coin } => Self::TokenIsAlreadyActivated(coin), RegisterCoinError::Internal(err) => Self::Internal(err), } } @@ -124,7 +124,7 @@ where (Token::ActivationError, EnableTokenError): NotEqual, { if let Ok(Some(_)) = lp_coinfind(&ctx, &req.ticker).await { - return MmError::err(EnableTokenError::TickerAlreadyInUse(req.ticker)); + return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); } let (token_conf, token_protocol): (_, Token::ProtocolInfo) = @@ -173,7 +173,7 @@ impl From for EnableTokenError { impl HttpStatusCode for EnableTokenError { fn status_code(&self) -> StatusCode { match self { - EnableTokenError::TickerAlreadyInUse(_) + EnableTokenError::TokenIsAlreadyActivated(_) | EnableTokenError::PlatformCoinIsNotActivated(_) | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index a5db3ce4cd..6ddbaa805c 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -19,8 +19,8 @@ use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_erc20_token_v2, enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, - get_custom_token_info, get_locked_amount, init_erc20_token, kmd_conf, max_maker_vol, - mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, + get_custom_token_info, get_locked_amount, kmd_conf, max_maker_vol, mm_dump, + mycoin1_conf, mycoin_conf, set_price, start_swaps, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::{get_passphrase, structs::*}; @@ -5415,11 +5415,12 @@ fn test_custom_erc20() { block_on(enable_erc20_token_v2( &mm_hd, &ticker, - Some(protocol), + Some(protocol.clone()), true, 60, - Some(path_to_address), - )); + Some(path_to_address.clone()), + )) + .unwrap(); // Test that the custom token is wallet only by using it in a swap let buy = block_on(mm_hd.rpc(&json!({ @@ -5438,6 +5439,26 @@ fn test_custom_erc20() { buy.1 ); + // Enabling the same custom token using a different ticker should fail + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "ERC20DEV", + Some(protocol.clone()), + true, + 60, + Some(path_to_address), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "TokenWithSameContractAlreadyActivated": { + "ticker": ticker, + "contract_address": protocol["protocol_data"]["contract_address"] + } + }); + assert_eq!(err["error_data"], expected_error_data); + // Disable the custom token block_on(disable_coin(&mm_hd, &ticker, true)); } @@ -5471,18 +5492,23 @@ fn test_enable_custom_token_with_duplicate_contract_in_config() { // Enable the custom token in HD mode. // Since the contract is already in the coins config, this should fail with an error // that specifies the ticker in config so that the user can enable the right coin. - let err = block_on(init_erc20_token( + let err = block_on(enable_erc20_token_v2( &mm_hd, "QTC", Some(protocol.clone()), true, + 60, Some(path_to_address.clone()), )) .unwrap_err(); - assert_eq!( - err["error_data"], - json!({"DuplicateContractInConfig":{"ticker_in_config":"ERC20DEV"}}) - ); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "DuplicateContractInConfig": { + "ticker_in_config": "ERC20DEV" + } + }); + assert_eq!(err["error_data"], expected_error_data); // Another way is to use the `get_custom_token_info` RPC and use the config ticker to enable the token. let custom_token_info = block_on(get_custom_token_info(&mm_hd, protocol)); @@ -5497,7 +5523,8 @@ fn test_enable_custom_token_with_duplicate_contract_in_config() { false, 60, Some(path_to_address), - )); + )) + .unwrap(); // Disable the custom token, this to check that it was enabled correctly block_on(disable_coin(&mm_hd, &config_ticker, true)); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 155f73d350..10e2a30e0e 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3253,7 +3253,7 @@ pub async fn enable_eth_with_tokens_v2( } } -pub async fn init_erc20_token( +async fn init_erc20_token( mm: &MarketMakerIt, ticker: &str, protocol: Option, @@ -3283,23 +3283,6 @@ pub async fn init_erc20_token( } } -async fn init_erc20_token_and_assert_response( - mm: &MarketMakerIt, - ticker: &str, - protocol: Option, - is_custom: bool, - path_to_address: Option, -) -> Json { - let response = init_erc20_token(mm, ticker, protocol, is_custom, path_to_address).await.unwrap(); - assert_eq!( - response.0, - StatusCode::OK, - "'task::enable_erc20::init' failed: {}", - response.1 - ); - response.1 -} - async fn init_erc20_token_status(mm: &MarketMakerIt, task_id: u64) -> Json { let request = mm .rpc(&json!({ @@ -3328,8 +3311,8 @@ pub async fn enable_erc20_token_v2( is_custom: bool, timeout: u64, path_to_address: Option, -) -> InitTokenActivationResult { - let init = init_erc20_token_and_assert_response(mm, ticker, protocol, is_custom, path_to_address).await; +) -> Result { + let init = init_erc20_token(mm, ticker, protocol, is_custom, path_to_address).await?.1; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(timeout * 1000); @@ -3341,8 +3324,8 @@ pub async fn enable_erc20_token_v2( let status = init_erc20_token_status(mm, init.result.task_id).await; let status: RpcV2Response = json::from_value(status).unwrap(); match status.result { - InitErc20TokenStatus::Ok(result) => break result, - InitErc20TokenStatus::Error(e) => panic!("{} initialization error {:?}", ticker, e), + InitErc20TokenStatus::Ok(result) => break Ok(result), + InitErc20TokenStatus::Error(e) => break Err(e), _ => Timer::sleep(1.).await, } } From 05949db26123454edabdcb0bc1bec73ad6b0985e Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 30 Oct 2024 14:29:19 +0300 Subject: [PATCH 19/31] move new custom erc20 tests to eth_docker_tests.rs --- .../tests/docker_tests/docker_tests_inner.rs | 279 +----------------- .../tests/docker_tests/eth_docker_tests.rs | 277 ++++++++++++++++- 2 files changed, 279 insertions(+), 277 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 6ddbaa805c..1757f97d36 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -17,10 +17,9 @@ use crypto::privkey::key_pair_from_seed; use crypto::{CryptoCtx, DerivationPath, KeyPairPolicy}; use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; -use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_erc20_token_v2, - enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, - get_custom_token_info, get_locked_amount, kmd_conf, max_maker_vol, mm_dump, - mycoin1_conf, mycoin_conf, set_price, start_swaps, +use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_eth_coin, + enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, get_locked_amount, + kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::{get_passphrase, structs::*}; @@ -5258,278 +5257,6 @@ fn test_sell_min_volume_dust() { assert!(!rc.0.is_success(), "!sell: {}", rc.1); } -#[test] -fn test_enable_eth_erc20_coins_with_enable_hd() { - const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - - let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); - let swap_contract = format!("0x{}", hex::encode(swap_contract())); - - // Withdraw from HD account 0, change address 0, index 0 - let path_to_address = HDAccountAddressId::default(); - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[0].address, - "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); - - // Enable HD account 0, change address 0, index 1 - let path_to_address = HDAccountAddressId { - account_id: 0, - chain: Bip44Chain::External, - address_id: 1, - }; - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[1].address, - "0xDe841899aB4A22E23dB21634e54920aDec402397" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); - - // Enable HD account 77, change address 0, index 7 - let path_to_address = HDAccountAddressId { - account_id: 77, - chain: Bip44Chain::External, - address_id: 7, - }; - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[7].address, - "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); -} - -// Todo: move this and others to an appropriate place -#[test] -fn test_custom_erc20() { - const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - - let coins = json!([eth_dev_conf()]); - let swap_contract = format!("0x{}", hex::encode(swap_contract())); - - let path_to_address = HDAccountAddressId::default(); - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - // Enable platform coin in HD mode - block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &[], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address.clone()), - )); - - // Test `get_custom_token_info` rpc, we also use it to get the token symbol to use it as the ticker - let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); - let CustomTokenInfo::ERC20(custom_token_info) = block_on(get_custom_token_info(&mm_hd, protocol.clone())).info; - let ticker = custom_token_info.symbol; - assert_eq!(ticker, "QTC"); - assert_eq!(custom_token_info.decimals, 8); - - // Enable the custom token in HD mode - block_on(enable_erc20_token_v2( - &mm_hd, - &ticker, - Some(protocol.clone()), - true, - 60, - Some(path_to_address.clone()), - )) - .unwrap(); - - // Test that the custom token is wallet only by using it in a swap - let buy = block_on(mm_hd.rpc(&json!({ - "userpass": mm_hd.userpass, - "method": "buy", - "base": "ETH", - "rel": ticker, - "price": "1", - "volume": "1", - }))) - .unwrap(); - assert!(!buy.0.is_success(), "buy success, but should fail: {}", buy.1); - assert!( - buy.1.contains(&format!("Rel coin {} is wallet only", ticker)), - "Expected error message indicating that the token is wallet only, but got: {}", - buy.1 - ); - - // Enabling the same custom token using a different ticker should fail - let err = block_on(enable_erc20_token_v2( - &mm_hd, - "ERC20DEV", - Some(protocol.clone()), - true, - 60, - Some(path_to_address), - )) - .unwrap_err(); - let expected_error_type = "CustomTokenError"; - assert_eq!(err["error_type"], expected_error_type); - let expected_error_data = json!({ - "TokenWithSameContractAlreadyActivated": { - "ticker": ticker, - "contract_address": protocol["protocol_data"]["contract_address"] - } - }); - assert_eq!(err["error_data"], expected_error_data); - - // Disable the custom token - block_on(disable_coin(&mm_hd, &ticker, true)); -} - -#[test] -fn test_enable_custom_token_with_duplicate_contract_in_config() { - const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - - let erc20_dev_conf = erc20_dev_conf(&erc20_contract_checksum()); - let coins = json!([eth_dev_conf(), erc20_dev_conf]); - let swap_contract = format!("0x{}", hex::encode(swap_contract())); - - let path_to_address = HDAccountAddressId::default(); - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - // Enable platform coin in HD mode - block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &[], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address.clone()), - )); - - let protocol = erc20_dev_conf["protocol"].clone(); - // Enable the custom token in HD mode. - // Since the contract is already in the coins config, this should fail with an error - // that specifies the ticker in config so that the user can enable the right coin. - let err = block_on(enable_erc20_token_v2( - &mm_hd, - "QTC", - Some(protocol.clone()), - true, - 60, - Some(path_to_address.clone()), - )) - .unwrap_err(); - let expected_error_type = "CustomTokenError"; - assert_eq!(err["error_type"], expected_error_type); - let expected_error_data = json!({ - "DuplicateContractInConfig": { - "ticker_in_config": "ERC20DEV" - } - }); - assert_eq!(err["error_data"], expected_error_data); - - // Another way is to use the `get_custom_token_info` RPC and use the config ticker to enable the token. - let custom_token_info = block_on(get_custom_token_info(&mm_hd, protocol)); - assert!(custom_token_info.config_ticker.is_some()); - let config_ticker = custom_token_info.config_ticker.unwrap(); - assert_eq!(config_ticker, "ERC20DEV"); - // Parameters passed here are for normal enabling of a coin in config and not for a custom token - block_on(enable_erc20_token_v2( - &mm_hd, - &config_ticker, - None, - false, - 60, - Some(path_to_address), - )) - .unwrap(); - - // Disable the custom token, this to check that it was enabled correctly - block_on(disable_coin(&mm_hd, &config_ticker, true)); -} - fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 293610e102..c6a56d9417 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -24,7 +24,11 @@ use crypto::Secp256k1Secret; use ethereum_types::U256; use mm2_core::mm_ctx::MmArc; use mm2_number::{BigDecimal, BigUint}; -use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf, eth_sepolia_conf, nft_dev_conf, sepolia_erc20_dev_conf}; +use mm2_test_helpers::for_tests::{disable_coin, enable_erc20_token_v2, enable_eth_with_tokens_v2, erc20_dev_conf, + eth_dev_conf, eth_sepolia_conf, get_custom_token_info, nft_dev_conf, + sepolia_erc20_dev_conf, MarketMakerIt, Mm2TestConf}; +use mm2_test_helpers::structs::{Bip44Chain, CustomTokenInfo, EnableCoinBalanceMap, EthWithTokensActivationResult, + HDAccountAddressId}; use serde_json::Value as Json; use std::str::FromStr; use std::thread; @@ -2093,3 +2097,274 @@ fn send_and_refund_taker_funding_exceed_pre_approve_timelock_erc20() { ); wait_for_confirmations(&taker_coin, &funding_tx_refund, 150); } + +#[test] +fn test_enable_eth_erc20_coins_with_enable_hd() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + // Withdraw from HD account 0, change address 0, index 0 + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[0].address, + "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); + + // Enable HD account 0, change address 0, index 1 + let path_to_address = HDAccountAddressId { + account_id: 0, + chain: Bip44Chain::External, + address_id: 1, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[1].address, + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); + + // Enable HD account 77, change address 0, index 7 + let path_to_address = HDAccountAddressId { + account_id: 77, + chain: Bip44Chain::External, + address_id: 7, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[7].address, + "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); +} + +#[test] +fn test_custom_erc20() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf()]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + // Test `get_custom_token_info` rpc, we also use it to get the token symbol to use it as the ticker + let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); + let CustomTokenInfo::ERC20(custom_token_info) = block_on(get_custom_token_info(&mm_hd, protocol.clone())).info; + let ticker = custom_token_info.symbol; + assert_eq!(ticker, "QTC"); + assert_eq!(custom_token_info.decimals, 8); + + // Enable the custom token in HD mode + block_on(enable_erc20_token_v2( + &mm_hd, + &ticker, + Some(protocol.clone()), + true, + 60, + Some(path_to_address.clone()), + )) + .unwrap(); + + // Test that the custom token is wallet only by using it in a swap + let buy = block_on(mm_hd.rpc(&json!({ + "userpass": mm_hd.userpass, + "method": "buy", + "base": "ETH", + "rel": ticker, + "price": "1", + "volume": "1", + }))) + .unwrap(); + assert!(!buy.0.is_success(), "buy success, but should fail: {}", buy.1); + assert!( + buy.1.contains(&format!("Rel coin {} is wallet only", ticker)), + "Expected error message indicating that the token is wallet only, but got: {}", + buy.1 + ); + + // Enabling the same custom token using a different ticker should fail + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "ERC20DEV", + Some(protocol.clone()), + true, + 60, + Some(path_to_address), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "TokenWithSameContractAlreadyActivated": { + "ticker": ticker, + "contract_address": protocol["protocol_data"]["contract_address"] + } + }); + assert_eq!(err["error_data"], expected_error_data); + + // Disable the custom token + block_on(disable_coin(&mm_hd, &ticker, true)); +} + +#[test] +fn test_enable_custom_er20_with_duplicate_contract_in_config() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let erc20_dev_conf = erc20_dev_conf(&erc20_contract_checksum()); + let coins = json!([eth_dev_conf(), erc20_dev_conf]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + let protocol = erc20_dev_conf["protocol"].clone(); + // Enable the custom token in HD mode. + // Since the contract is already in the coins config, this should fail with an error + // that specifies the ticker in config so that the user can enable the right coin. + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "QTC", + Some(protocol.clone()), + true, + 60, + Some(path_to_address.clone()), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "DuplicateContractInConfig": { + "ticker_in_config": "ERC20DEV" + } + }); + assert_eq!(err["error_data"], expected_error_data); + + // Another way is to use the `get_custom_token_info` RPC and use the config ticker to enable the token. + let custom_token_info = block_on(get_custom_token_info(&mm_hd, protocol)); + assert!(custom_token_info.config_ticker.is_some()); + let config_ticker = custom_token_info.config_ticker.unwrap(); + assert_eq!(config_ticker, "ERC20DEV"); + // Parameters passed here are for normal enabling of a coin in config and not for a custom token + block_on(enable_erc20_token_v2( + &mm_hd, + &config_ticker, + None, + false, + 60, + Some(path_to_address), + )) + .unwrap(); + + // Disable the custom token, this to check that it was enabled correctly + block_on(disable_coin(&mm_hd, &config_ticker, true)); +} From c6a4ea86904a375872142c3abce0cd2324d9e1db Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 4 Nov 2024 23:51:27 +0200 Subject: [PATCH 20/31] review fix: remove unused TokenAlreadyActivated error variants --- .../src/platform_coin_with_tokens.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index efbee899b3..55da61a42b 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -91,7 +91,6 @@ pub trait TokenAsMmCoinInitializer: Send + Sync { } pub enum InitTokensAsMmCoinsError { - TokenAlreadyActivated { ticker: String, contract_address: String }, TokenConfigIsNotFound(String), CouldNotFetchBalance(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), @@ -242,15 +241,6 @@ pub struct EnablePlatformCoinWithTokensReq { #[serde(tag = "error_type", content = "error_data")] pub enum EnablePlatformCoinWithTokensError { PlatformIsAlreadyActivated(String), - #[display( - fmt = "Token is already activated, ticker: {}, contract address: {}", - ticker, - contract_address - )] - TokenAlreadyActivated { - ticker: String, - contract_address: String, - }, #[display(fmt = "Platform {} config is not found", _0)] PlatformConfigIsNotFound(String), #[display(fmt = "Platform coin {} protocol parsing failed: {}", ticker, error)] @@ -325,13 +315,6 @@ impl From for EnablePlatformCoinWithTokensError { impl From for EnablePlatformCoinWithTokensError { fn from(err: InitTokensAsMmCoinsError) -> Self { match err { - InitTokensAsMmCoinsError::TokenAlreadyActivated { - ticker, - contract_address, - } => EnablePlatformCoinWithTokensError::TokenAlreadyActivated { - ticker, - contract_address, - }, InitTokensAsMmCoinsError::TokenConfigIsNotFound(ticker) => { EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(ticker) }, @@ -388,7 +371,6 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } | EnablePlatformCoinWithTokensError::CustomTokenError(_) => StatusCode::INTERNAL_SERVER_ERROR, EnablePlatformCoinWithTokensError::PlatformIsAlreadyActivated(_) - | EnablePlatformCoinWithTokensError::TokenAlreadyActivated { .. } | EnablePlatformCoinWithTokensError::PlatformConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::UnexpectedPlatformProtocol { .. } From a20c3f046b078d549bab62a0f2bedfa56a8f2078 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 4 Nov 2024 23:57:08 +0200 Subject: [PATCH 21/31] review fixes: rename `test_custom_erc20` to `test_enable_custom_erc20` --- mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index c6a56d9417..12545b019a 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -2220,7 +2220,7 @@ fn test_enable_eth_erc20_coins_with_enable_hd() { } #[test] -fn test_custom_erc20() { +fn test_enable_custom_erc20() { const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; let coins = json!([eth_dev_conf()]); @@ -2303,7 +2303,7 @@ fn test_custom_erc20() { } #[test] -fn test_enable_custom_er20_with_duplicate_contract_in_config() { +fn test_enable_custom_erc20_with_duplicate_contract_in_config() { const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; let erc20_dev_conf = erc20_dev_conf(&erc20_contract_checksum()); From c721a504d7a0a42b49fe10d2690ae3f8aea837dd Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 5 Nov 2024 00:07:46 +0200 Subject: [PATCH 22/31] review fixes: move coin conf inside `TokenActivationParams` --- .../src/bch_with_tokens_activation.rs | 4 ++-- .../coins_activation/src/eth_with_token_activation.rs | 6 +++--- .../coins_activation/src/platform_coin_with_tokens.rs | 11 +++++------ .../src/tendermint_with_assets_activation.rs | 7 ++----- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/mm2src/coins_activation/src/bch_with_tokens_activation.rs b/mm2src/coins_activation/src/bch_with_tokens_activation.rs index 426e6b43a1..ebda8efcba 100644 --- a/mm2src/coins_activation/src/bch_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/bch_with_tokens_activation.rs @@ -63,11 +63,11 @@ impl TokenInitializer for SlpTokenInitializer { async fn enable_tokens( &self, - activation_params: Vec<(Json, TokenActivationParams)>, + activation_params: Vec>, ) -> Result, MmError> { let tokens = activation_params .into_iter() - .map(|(_, params)| { + .map(|params| { // confirmation settings from RPC request have the highest priority let required_confirmations = params.activation_request.required_confirmations.unwrap_or_else(|| { params diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index f1d541a5be..01a2d85318 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -139,13 +139,13 @@ impl TokenInitializer for Erc20Initializer { async fn enable_tokens( &self, - activation_params: Vec<(Json, TokenActivationParams)>, + activation_params: Vec>, ) -> Result, MmError> { let mut tokens = Vec::with_capacity(activation_params.len()); - for (token_conf, param) in activation_params { + for param in activation_params { let token: EthCoin = self .platform_coin - .initialize_erc20_token(param.ticker, param.activation_request, token_conf, param.protocol) + .initialize_erc20_token(param.ticker, param.activation_request, param.conf, param.protocol) .await?; tokens.push(token); } diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 55da61a42b..d731e70998 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -52,6 +52,7 @@ pub trait TokenOf: Into { pub struct TokenActivationParams { pub(crate) ticker: String, + pub(crate) conf: Json, pub(crate) activation_request: Req, pub(crate) protocol: Protocol, } @@ -69,10 +70,7 @@ pub trait TokenInitializer { async fn enable_tokens( &self, - params: Vec<( - Json, - TokenActivationParams, - )>, + params: Vec>, ) -> Result, MmError>; fn platform_coin(&self) -> &::PlatformCoin; @@ -145,11 +143,12 @@ where .map(|req| -> Result<_, MmError> { let (token_conf, protocol): (_, T::TokenProtocol) = coin_conf_with_protocol(&ctx, &req.ticker, req.protocol)?; - Ok((token_conf, TokenActivationParams { + Ok(TokenActivationParams { ticker: req.ticker, + conf: token_conf, activation_request: req.request, protocol, - })) + }) }) .collect::, _>>()?; diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index ae9174d85a..349e37b23d 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -132,14 +132,11 @@ impl TokenInitializer for TendermintTokenInitializer { async fn enable_tokens( &self, - params: Vec<( - Json, - TokenActivationParams, - )>, + params: Vec>, ) -> Result, MmError> { params .into_iter() - .map(|(_, param)| { + .map(|param| { let ticker = param.ticker.clone(); TendermintToken::new( param.ticker, From bb2146f998a768ec68285def9912d52a93d985ba Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 5 Nov 2024 01:09:47 +0200 Subject: [PATCH 23/31] review fixes: refactor `coin_conf_with_protocol` --- mm2src/coins/lp_coins.rs | 14 +-- mm2src/coins_activation/src/init_token.rs | 4 +- .../coins_activation/src/l2/init_l2_error.rs | 6 +- .../src/platform_coin_with_tokens.rs | 10 +- mm2src/coins_activation/src/prelude.rs | 92 +++++++++++-------- .../init_standalone_coin_error.rs | 6 +- mm2src/coins_activation/src/token.rs | 4 +- 7 files changed, 71 insertions(+), 65 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f331ec323c..cc2a248486 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4217,7 +4217,7 @@ pub trait IguanaBalanceOps { } #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "type", content = "protocol_data")] pub enum CoinProtocol { UTXO, @@ -4258,16 +4258,8 @@ pub enum CoinProtocol { #[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum CustomTokenError { - #[display( - fmt = "Protocol mismatch for token {}: from config {:?}, from request {:?}", - ticker, - from_config, - from_request - )] - ProtocolMismatch { - ticker: String, - from_config: Json, - from_request: Json, + DuplicateTickerInConfig { + ticker_in_config: String, }, DuplicateContractInConfig { ticker_in_config: String, diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index 27c6621daa..10c9fb2062 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -305,8 +305,8 @@ pub enum InitTokenError { TokenConfigIsNotFound(String), #[display(fmt = "Token {} protocol parsing failed: {}", ticker, error)] TokenProtocolParseError { ticker: String, error: String }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] + UnexpectedTokenProtocol { ticker: String, protocol: Json }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] TokenCreationError { ticker: String, error: String }, #[display(fmt = "Could not fetch balance: {}", _0)] diff --git a/mm2src/coins_activation/src/l2/init_l2_error.rs b/mm2src/coins_activation/src/l2/init_l2_error.rs index 351d58a290..71eb57fd23 100644 --- a/mm2src/coins_activation/src/l2/init_l2_error.rs +++ b/mm2src/coins_activation/src/l2/init_l2_error.rs @@ -1,11 +1,11 @@ use crate::prelude::CoinConfWithProtocolError; -use coins::CoinProtocol; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; use rpc_task::rpc_common::{CancelRpcTaskError, RpcTaskStatusError, RpcTaskUserActionError}; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type InitL2StatusError = RpcTaskStatusError; @@ -24,10 +24,10 @@ pub enum InitL2Error { ticker: String, error: String, }, - #[display(fmt = "Unexpected layer 2 protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected layer 2 protocol {} for {}", protocol, ticker)] UnexpectedL2Protocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index d731e70998..e0f14eba70 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -94,7 +94,7 @@ pub enum InitTokensAsMmCoinsError { UnexpectedDerivationMethod(UnexpectedDerivationMethod), Internal(String), TokenProtocolParseError { ticker: String, error: String }, - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + UnexpectedTokenProtocol { ticker: String, protocol: Json }, Transport(String), InvalidPayload(String), CustomTokenError(CustomTokenError), @@ -247,10 +247,10 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, error: String, }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] UnexpectedPlatformProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Token {} config is not found", _0)] TokenConfigIsNotFound(String), @@ -259,10 +259,10 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, error: String, }, - #[display(fmt = "Unexpected token protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected token protocol {} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] PlatformCoinCreationError { diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 8b4cdf3fe2..885a02645c 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -3,7 +3,6 @@ use coins::siacoin::SiaCoinActivationParams; use coins::utxo::UtxoActivationParams; use coins::z_coin::ZcoinActivationParams; use coins::{coin_conf, CoinBalance, CoinProtocol, CustomTokenError, DerivationMethodResponse, MmCoinEnum}; -use common::drop_mutability; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; @@ -65,62 +64,77 @@ pub trait TryFromCoinProtocol { pub enum CoinConfWithProtocolError { ConfigIsNotFound(String), CoinProtocolParseError { ticker: String, err: json::Error }, - UnexpectedProtocol { ticker: String, protocol: CoinProtocol }, + UnexpectedProtocol { ticker: String, protocol: Json }, CustomTokenError(CustomTokenError), } /// Determines the coin configuration and protocol information for a given coin or NFT ticker. -#[allow(clippy::result_large_err)] pub fn coin_conf_with_protocol( ctx: &MmArc, coin: &str, protocol_from_request: Option, ) -> Result<(Json, T), MmError> { - if let Some(protocol_from_request) = &protocol_from_request { - protocol_from_request - .custom_token_validations(ctx) - .mm_err(CoinConfWithProtocolError::CustomTokenError)?; + let conf = coin_conf(ctx, coin); + let has_conf = !conf.is_null(); + + match (has_conf, protocol_from_request) { + // Config-based activation requested with an existing configuration + // Proceed with parsing protocol info from config + (false, None) => parse_coin_protocol_from_config(conf, coin), + // Custom token activation requested and no matching ticker found in config + // Proceed with custom token config creation from protocol info + (true, Some(protocol)) => create_custom_token_config(ctx, coin, protocol), + // Custom token activation requested but a coin with the same ticker already exists in config + (false, Some(_)) => Err(MmError::new(CoinConfWithProtocolError::CustomTokenError( + CustomTokenError::DuplicateTickerInConfig { + ticker_in_config: coin.to_string(), + }, + ))), + // Config-based activation requested but coin not found in config + (true, None) => Err(MmError::new(CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))), } +} + +fn parse_coin_protocol_from_config( + conf: Json, + coin: &str, +) -> Result<(Json, T), MmError> { + let protocol = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { + CoinConfWithProtocolError::CoinProtocolParseError { + ticker: coin.into(), + err, + } + })?; - let mut conf = coin_conf(ctx, coin); - let coin_protocol = if conf.is_null() { - // This means it's a custom token, and we should use protocol from request if it's not None - let protocol_from_request = - protocol_from_request.ok_or_else(|| CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))?; - conf = json!({ - "protocol": protocol_from_request, - "wallet_only": true - }); - protocol_from_request - } else { - let protocol_from_config = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { - CoinConfWithProtocolError::CoinProtocolParseError { - ticker: coin.into(), - err, - } + let coin_protocol = + T::try_from_coin_protocol(protocol).mm_err(|p| CoinConfWithProtocolError::UnexpectedProtocol { + ticker: coin.into(), + protocol: json!(p), })?; - if let Some(protocol_from_request) = protocol_from_request { - if protocol_from_request != protocol_from_config { - return MmError::err(CoinConfWithProtocolError::CustomTokenError( - CustomTokenError::ProtocolMismatch { - ticker: coin.into(), - from_config: json!(protocol_from_config), - from_request: json!(protocol_from_request), - }, - )); - } - } + Ok((conf, coin_protocol)) +} + +fn create_custom_token_config( + ctx: &MmArc, + coin: &str, + protocol: CoinProtocol, +) -> Result<(Json, T), MmError> { + protocol + .custom_token_validations(ctx) + .mm_err(CoinConfWithProtocolError::CustomTokenError)?; - protocol_from_config - }; - drop_mutability!(conf); + let conf = json!({ + "protocol": protocol, + "wallet_only": true + }); let coin_protocol = - T::try_from_coin_protocol(coin_protocol).mm_err(|protocol| CoinConfWithProtocolError::UnexpectedProtocol { + T::try_from_coin_protocol(protocol).mm_err(|p| CoinConfWithProtocolError::UnexpectedProtocol { ticker: coin.into(), - protocol, + protocol: json!(p), })?; + Ok((conf, coin_protocol)) } diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs index afbdc32486..21b1d696a9 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs @@ -1,5 +1,4 @@ use crate::prelude::CoinConfWithProtocolError; -use coins::CoinProtocol; use common::{HttpStatusCode, StatusCode}; use crypto::HwRpcError; use derive_more::Display; @@ -7,6 +6,7 @@ use rpc_task::rpc_common::{CancelRpcTaskError, RpcTaskStatusError, RpcTaskUserAc use rpc_task::{RpcTaskError, TaskId}; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type InitStandaloneCoinStatusError = RpcTaskStatusError; @@ -26,8 +26,8 @@ pub enum InitStandaloneCoinError { CoinConfigIsNotFound(String), #[display(fmt = "Coin {} protocol parsing failed: {}", ticker, error)] CoinProtocolParseError { ticker: String, error: String }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] - UnexpectedCoinProtocol { ticker: String, protocol: CoinProtocol }, + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] + UnexpectedCoinProtocol { ticker: String, protocol: Json }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] CoinCreationError { ticker: String, error: String }, #[display(fmt = "{}", _0)] diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index cf1731b503..80965f181d 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -46,10 +46,10 @@ pub enum EnableTokenError { ticker: String, error: String, }, - #[display(fmt = "Unexpected token protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected token protocol {} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), From 1c0770358025693b3601169a343a4867daae2c38 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 5 Nov 2024 01:20:20 +0200 Subject: [PATCH 24/31] review fixes: rename `get_custom_token_info` to `get_token_info` alongside other renames --- mm2src/coins/eth.rs | 8 +-- mm2src/coins/eth/erc20.rs | 6 +-- mm2src/coins/eth/eth_balance_events.rs | 8 +-- .../src/eth_with_token_activation.rs | 4 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 4 +- mm2src/mm2_main/src/rpc/lp_commands/mod.rs | 2 +- .../{custom_token.rs => tokens.rs} | 54 +++++++++---------- .../tests/docker_tests/eth_docker_tests.rs | 10 ++-- mm2src/mm2_test_helpers/src/for_tests.rs | 6 +-- mm2src/mm2_test_helpers/src/structs.rs | 6 +-- 10 files changed, 51 insertions(+), 57 deletions(-) rename mm2src/mm2_main/src/rpc/lp_commands/{custom_token.rs => tokens.rs} (60%) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 1e83724f2b..c45bf52ee5 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -676,7 +676,7 @@ pub struct EthCoinImpl { /// and unlocked once the transaction is confirmed. This prevents nonce conflicts when multiple transactions /// are initiated concurrently from the same address. address_nonce_locks: Arc>>>>, - erc20_tokens_infos: Arc>>, + erc20_tokens_infos: Arc>>, /// Stores information about NFTs owned by the user. Each entry in the HashMap is uniquely identified by a composite key /// consisting of the token address and token ID, separated by a comma. This field is essential for tracking the NFT assets /// information (chain & contract type, amount etc.), where ownership and amount, in ERC1155 case, might change over time. @@ -698,7 +698,7 @@ pub struct Web3Instance { /// Information about a token that follows the ERC20 protocol on an EVM-based network. #[derive(Clone, Debug)] -pub struct Erc20TokenInfo { +pub struct Erc20TokenDetails { /// The contract address of the token on the EVM-based network. pub token_address: Address, /// The number of decimal places the token uses. @@ -859,14 +859,14 @@ impl EthCoinImpl { } } - pub fn add_erc_token_info(&self, ticker: String, info: Erc20TokenInfo) { + pub fn add_erc_token_info(&self, ticker: String, info: Erc20TokenDetails) { self.erc20_tokens_infos.lock().unwrap().insert(ticker, info); } /// # Warning /// Be very careful using this function since it returns dereferenced clone /// of value behind the MutexGuard and makes it non-thread-safe. - pub fn get_erc_tokens_infos(&self) -> HashMap { + pub fn get_erc_tokens_infos(&self) -> HashMap { let guard = self.erc20_tokens_infos.lock().unwrap(); (*guard).clone() } diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs index 55b8269643..8a8d974c11 100644 --- a/mm2src/coins/eth/erc20.rs +++ b/mm2src/coins/eth/erc20.rs @@ -52,16 +52,16 @@ async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result Result { +pub async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { let symbol = get_token_symbol(coin, token_addr).await?; let web3 = try_s!(coin.web3().await); let decimals = get_token_decimals(&web3, token_addr).await?; - Ok(Erc20CustomTokenInfo { symbol, decimals }) + Ok(Erc20TokenBasicInfo { symbol, decimals }) } /// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. diff --git a/mm2src/coins/eth/eth_balance_events.rs b/mm2src/coins/eth/eth_balance_events.rs index 231aa68507..0cc798ad7e 100644 --- a/mm2src/coins/eth/eth_balance_events.rs +++ b/mm2src/coins/eth/eth_balance_events.rs @@ -14,7 +14,7 @@ use mm2_number::BigDecimal; use std::collections::{HashMap, HashSet}; use super::EthCoin; -use crate::{eth::{u256_to_big_decimal, Erc20TokenInfo}, +use crate::{eth::{u256_to_big_decimal, Erc20TokenDetails}, BalanceError, CoinWithDerivationMethod, MmCoin}; struct BalanceData { @@ -40,9 +40,9 @@ async fn get_all_balance_results_concurrently(coin: &EthCoin, addresses: HashSet // // Unlike tokens, the platform coin length is constant (=1). Instead of creating a generic // type and mapping the platform coin and the entire token list (which can grow at any time), we map - // the platform coin to Erc20TokenInfo so that we can use the token list right away without + // the platform coin to Erc20TokenDetails so that we can use the token list right away without // additional mapping. - tokens.insert(coin.ticker.clone(), Erc20TokenInfo { + tokens.insert(coin.ticker.clone(), Erc20TokenDetails { // This is a dummy value, since there is no token address for the platform coin. // In the fetch_balance function, we check if the token_ticker is equal to this // coin's ticker to avoid using token_address to fetch the balance @@ -72,7 +72,7 @@ async fn fetch_balance( coin: &EthCoin, address: Address, token_ticker: String, - info: &Erc20TokenInfo, + info: &Erc20TokenDetails, ) -> Result { let (balance_as_u256, decimals) = if token_ticker == coin.ticker { ( diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index 01a2d85318..d2f94a047a 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -12,7 +12,7 @@ use coins::coin_balance::{CoinBalanceReport, EnableCoinBalanceOps}; use coins::eth::v2_activation::{eth_coin_from_conf_and_request_v2, Erc20Protocol, Erc20TokenActivationRequest, EthActivationV2Error, EthActivationV2Request, EthPrivKeyActivationPolicy}; use coins::eth::v2_activation::{EthTokenActivationError, NftActivationRequest, NftProviderEnum}; -use coins::eth::{display_eth_address, Erc20TokenInfo, EthCoin, EthCoinType, EthPrivKeyBuildPolicy}; +use coins::eth::{display_eth_address, Erc20TokenDetails, EthCoin, EthCoinType, EthPrivKeyBuildPolicy}; use coins::hd_wallet::RpcTaskXPubExtractor; use coins::my_tx_history_v2::TxHistoryStorage; use coins::nft::nft_structs::NftInfo; @@ -185,7 +185,7 @@ impl RegisterTokenInfo for EthCoin { return; } - self.add_erc_token_info(token.ticker().to_string(), Erc20TokenInfo { + self.add_erc_token_info(token.ticker().to_string(), Erc20TokenDetails { token_address: token.erc20_token_address().unwrap(), decimals: token.decimals(), }); diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index c76feca33a..6b68666d6b 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -10,9 +10,9 @@ use crate::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, s use crate::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; use crate::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}; use crate::lp_wallet::{get_mnemonic_rpc, get_wallet_names_rpc}; -use crate::rpc::lp_commands::custom_token::get_custom_token_info; use crate::rpc::lp_commands::db_id::get_shared_db_id; use crate::rpc::lp_commands::pubkey::*; +use crate::rpc::lp_commands::tokens::get_token_info; use crate::rpc::lp_commands::trezor::trezor_connection_status; use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::eth::EthCoin; @@ -174,7 +174,6 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, enable_token::).await, "get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await, - "get_custom_token_info" => handle_mmrpc(ctx, request, get_custom_token_info).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, @@ -188,6 +187,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_raw_transaction).await, "get_shared_db_id" => handle_mmrpc(ctx, request, get_shared_db_id).await, "get_staking_infos" => handle_mmrpc(ctx, request, get_staking_infos).await, + "get_token_info" => handle_mmrpc(ctx, request, get_token_info).await, "get_wallet_names" => handle_mmrpc(ctx, request, get_wallet_names_rpc).await, "max_maker_vol" => handle_mmrpc(ctx, request, max_maker_vol).await, "my_recent_swaps" => handle_mmrpc(ctx, request, my_recent_swaps_rpc).await, diff --git a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs index 846485ddfa..002066c836 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs @@ -1,5 +1,5 @@ -pub(crate) mod custom_token; pub(crate) mod db_id; pub mod legacy; pub(crate) mod pubkey; +pub(crate) mod tokens; pub(crate) mod trezor; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/custom_token.rs b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs similarity index 60% rename from mm2src/mm2_main/src/rpc/lp_commands/custom_token.rs rename to mm2src/mm2_main/src/rpc/lp_commands/tokens.rs index b8be5210d3..7d19bf2a64 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/custom_token.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs @@ -1,4 +1,4 @@ -use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20CustomTokenInfo}; +use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20TokenBasicInfo}; use coins::eth::valid_addr_from_str; use coins::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; use common::HttpStatusCode; @@ -7,27 +7,27 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; #[derive(Deserialize)] -pub struct CustomTokenInfoRequest { +pub struct TokenInfoRequest { protocol: CoinProtocol, } #[derive(Serialize)] #[serde(tag = "type", content = "info")] -pub enum CustomTokenInfo { - ERC20(Erc20CustomTokenInfo), +pub enum TokenInfo { + ERC20(Erc20TokenBasicInfo), } #[derive(Serialize)] -pub struct CustomTokenInfoResponse { +pub struct TokenInfoResponse { #[serde(skip_serializing_if = "Option::is_none")] config_ticker: Option, #[serde(flatten)] - info: CustomTokenInfo, + info: TokenInfo, } #[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] -pub enum CustomTokenInfoError { +pub enum TokenInfoError { #[display(fmt = "No such coin {}", coin)] NoSuchCoin { coin: String }, #[display(fmt = "Custom tokens are not supported for {} protocol yet!", protocol)] @@ -38,38 +38,32 @@ pub enum CustomTokenInfoError { RetrieveInfoError(String), } -impl HttpStatusCode for CustomTokenInfoError { +impl HttpStatusCode for TokenInfoError { fn status_code(&self) -> StatusCode { match self { - CustomTokenInfoError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, - CustomTokenInfoError::UnsupportedTokenProtocol { .. } | CustomTokenInfoError::InvalidRequest(_) => { + TokenInfoError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, + TokenInfoError::UnsupportedTokenProtocol { .. } | TokenInfoError::InvalidRequest(_) => { StatusCode::BAD_REQUEST }, - CustomTokenInfoError::RetrieveInfoError(_) => StatusCode::INTERNAL_SERVER_ERROR, + TokenInfoError::RetrieveInfoError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } -impl From for CustomTokenInfoError { +impl From for TokenInfoError { fn from(e: CoinFindError) -> Self { match e { - CoinFindError::NoSuchCoin { coin } => CustomTokenInfoError::NoSuchCoin { coin }, + CoinFindError::NoSuchCoin { coin } => TokenInfoError::NoSuchCoin { coin }, } } } -pub async fn get_custom_token_info( - ctx: MmArc, - req: CustomTokenInfoRequest, -) -> MmResult { +pub async fn get_token_info(ctx: MmArc, req: TokenInfoRequest) -> MmResult { // Check that the protocol is a token protocol - let platform = req - .protocol - .platform() - .ok_or(CustomTokenInfoError::InvalidRequest(format!( - "Protocol '{:?}' is not a token protocol", - req.protocol - )))?; + let platform = req.protocol.platform().ok_or(TokenInfoError::InvalidRequest(format!( + "Protocol '{:?}' is not a token protocol", + req.protocol + )))?; // Platform coin should be activated let platform_coin = lp_coinfind_or_err(&ctx, platform).await?; match platform_coin { @@ -77,24 +71,24 @@ pub async fn get_custom_token_info( let contract_address_str = req.protocol .contract_address() - .ok_or(CustomTokenInfoError::UnsupportedTokenProtocol { + .ok_or(TokenInfoError::UnsupportedTokenProtocol { protocol: platform.to_string(), })?; let contract_address = valid_addr_from_str(contract_address_str).map_to_mm(|e| { let error = format!("Invalid contract address: {}", e); - CustomTokenInfoError::InvalidRequest(error) + TokenInfoError::InvalidRequest(error) })?; let config_ticker = get_erc20_ticker_by_contract_address(&ctx, platform, contract_address_str); let token_info = get_erc20_token_info(ð_coin, contract_address) .await - .map_to_mm(CustomTokenInfoError::RetrieveInfoError)?; - Ok(CustomTokenInfoResponse { + .map_to_mm(TokenInfoError::RetrieveInfoError)?; + Ok(TokenInfoResponse { config_ticker, - info: CustomTokenInfo::ERC20(token_info), + info: TokenInfo::ERC20(token_info), }) }, - _ => MmError::err(CustomTokenInfoError::UnsupportedTokenProtocol { + _ => MmError::err(TokenInfoError::UnsupportedTokenProtocol { protocol: platform.to_string(), }), } diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 12545b019a..ab3a5ce8d7 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -25,7 +25,7 @@ use ethereum_types::U256; use mm2_core::mm_ctx::MmArc; use mm2_number::{BigDecimal, BigUint}; use mm2_test_helpers::for_tests::{disable_coin, enable_erc20_token_v2, enable_eth_with_tokens_v2, erc20_dev_conf, - eth_dev_conf, eth_sepolia_conf, get_custom_token_info, nft_dev_conf, + eth_dev_conf, eth_sepolia_conf, get_token_info, nft_dev_conf, sepolia_erc20_dev_conf, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::structs::{Bip44Chain, CustomTokenInfo, EnableCoinBalanceMap, EthWithTokensActivationResult, HDAccountAddressId}; @@ -2243,9 +2243,9 @@ fn test_enable_custom_erc20() { Some(path_to_address.clone()), )); - // Test `get_custom_token_info` rpc, we also use it to get the token symbol to use it as the ticker + // Test `get_token_info` rpc, we also use it to get the token symbol to use it as the ticker let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); - let CustomTokenInfo::ERC20(custom_token_info) = block_on(get_custom_token_info(&mm_hd, protocol.clone())).info; + let CustomTokenInfo::ERC20(custom_token_info) = block_on(get_token_info(&mm_hd, protocol.clone())).info; let ticker = custom_token_info.symbol; assert_eq!(ticker, "QTC"); assert_eq!(custom_token_info.decimals, 8); @@ -2349,8 +2349,8 @@ fn test_enable_custom_erc20_with_duplicate_contract_in_config() { }); assert_eq!(err["error_data"], expected_error_data); - // Another way is to use the `get_custom_token_info` RPC and use the config ticker to enable the token. - let custom_token_info = block_on(get_custom_token_info(&mm_hd, protocol)); + // Another way is to use the `get_token_info` RPC and use the config ticker to enable the token. + let custom_token_info = block_on(get_token_info(&mm_hd, protocol)); assert!(custom_token_info.config_ticker.is_some()); let config_ticker = custom_token_info.config_ticker.unwrap(); assert_eq!(config_ticker, "ERC20DEV"); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 10e2a30e0e..86c73a98bb 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3331,11 +3331,11 @@ pub async fn enable_erc20_token_v2( } } -pub async fn get_custom_token_info(mm: &MarketMakerIt, protocol: Json) -> CustomTokenInfoResponse { +pub async fn get_token_info(mm: &MarketMakerIt, protocol: Json) -> TokenInfoResponse { let response = mm .rpc(&json!({ "userpass": mm.userpass, - "method": "get_custom_token_info", + "method": "get_token_info", "mmrpc": "2.0", "params": { "protocol": protocol, @@ -3346,7 +3346,7 @@ pub async fn get_custom_token_info(mm: &MarketMakerIt, protocol: Json) -> Custom assert_eq!( response.0, StatusCode::OK, - "'get_custom_token_info' failed: {}", + "'get_token_info' failed: {}", response.1 ); let response_json: Json = json::from_str(&response.1).unwrap(); diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index 6693d15577..bf09abc2f8 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -1209,7 +1209,7 @@ pub struct ActiveSwapsResponse { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Erc20CustomTokenInfo { +pub struct Erc20TokenBasicInfo { pub symbol: String, pub decimals: u8, } @@ -1218,12 +1218,12 @@ pub struct Erc20CustomTokenInfo { #[serde(deny_unknown_fields)] #[serde(tag = "type", content = "info")] pub enum CustomTokenInfo { - ERC20(Erc20CustomTokenInfo), + ERC20(Erc20TokenBasicInfo), } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct CustomTokenInfoResponse { +pub struct TokenInfoResponse { pub config_ticker: Option, #[serde(flatten)] pub info: CustomTokenInfo, From d647242fdb103d4dee6bd629e0d649c0dfa89129 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 5 Nov 2024 02:12:36 +0200 Subject: [PATCH 25/31] - fix `coin_conf_with_protocol` - rename `has_conf` to `has_matching_ticker` - rename `CustomTokenInfo` to `TokenInfo` in tests --- mm2src/coins_activation/src/prelude.rs | 17 +++++++++-------- .../tests/docker_tests/eth_docker_tests.rs | 6 +++--- mm2src/mm2_test_helpers/src/structs.rs | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 885a02645c..42c93c1377 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -75,23 +75,24 @@ pub fn coin_conf_with_protocol( protocol_from_request: Option, ) -> Result<(Json, T), MmError> { let conf = coin_conf(ctx, coin); - let has_conf = !conf.is_null(); + let is_ticker_in_config = !conf.is_null(); - match (has_conf, protocol_from_request) { + // For `protocol_from_request`: None = config-based activation, Some = custom token activation + match (protocol_from_request, is_ticker_in_config) { // Config-based activation requested with an existing configuration // Proceed with parsing protocol info from config - (false, None) => parse_coin_protocol_from_config(conf, coin), - // Custom token activation requested and no matching ticker found in config + (None, true) => parse_coin_protocol_from_config(conf, coin), + // Custom token activation requested and no matching ticker in config // Proceed with custom token config creation from protocol info - (true, Some(protocol)) => create_custom_token_config(ctx, coin, protocol), + (Some(protocol), false) => create_custom_token_config(ctx, coin, protocol), // Custom token activation requested but a coin with the same ticker already exists in config - (false, Some(_)) => Err(MmError::new(CoinConfWithProtocolError::CustomTokenError( + (Some(_), true) => Err(MmError::new(CoinConfWithProtocolError::CustomTokenError( CustomTokenError::DuplicateTickerInConfig { ticker_in_config: coin.to_string(), }, ))), - // Config-based activation requested but coin not found in config - (true, None) => Err(MmError::new(CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))), + // Config-based activation requested but ticker not found in config + (None, false) => Err(MmError::new(CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))), } } diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index ab3a5ce8d7..342b607894 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -27,8 +27,8 @@ use mm2_number::{BigDecimal, BigUint}; use mm2_test_helpers::for_tests::{disable_coin, enable_erc20_token_v2, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, eth_sepolia_conf, get_token_info, nft_dev_conf, sepolia_erc20_dev_conf, MarketMakerIt, Mm2TestConf}; -use mm2_test_helpers::structs::{Bip44Chain, CustomTokenInfo, EnableCoinBalanceMap, EthWithTokensActivationResult, - HDAccountAddressId}; +use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalanceMap, EthWithTokensActivationResult, HDAccountAddressId, + TokenInfo}; use serde_json::Value as Json; use std::str::FromStr; use std::thread; @@ -2245,7 +2245,7 @@ fn test_enable_custom_erc20() { // Test `get_token_info` rpc, we also use it to get the token symbol to use it as the ticker let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); - let CustomTokenInfo::ERC20(custom_token_info) = block_on(get_token_info(&mm_hd, protocol.clone())).info; + let TokenInfo::ERC20(custom_token_info) = block_on(get_token_info(&mm_hd, protocol.clone())).info; let ticker = custom_token_info.symbol; assert_eq!(ticker, "QTC"); assert_eq!(custom_token_info.decimals, 8); diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index bf09abc2f8..6a7ec394cc 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -1217,7 +1217,7 @@ pub struct Erc20TokenBasicInfo { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] #[serde(tag = "type", content = "info")] -pub enum CustomTokenInfo { +pub enum TokenInfo { ERC20(Erc20TokenBasicInfo), } @@ -1226,5 +1226,5 @@ pub enum CustomTokenInfo { pub struct TokenInfoResponse { pub config_ticker: Option, #[serde(flatten)] - pub info: CustomTokenInfo, + pub info: TokenInfo, } From e9b6dc79b20d65710a28818bcce618bb56aac716 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 5 Nov 2024 02:37:04 +0200 Subject: [PATCH 26/31] review fix: remove `is_custom` from requests --- mm2src/coins/eth/v2_activation.rs | 10 ++-------- mm2src/coins_activation/src/erc20_token_activation.rs | 9 ++++++++- .../coins_activation/src/eth_with_token_activation.rs | 8 +++++++- .../src/init_erc20_token_activation.rs | 9 ++++++++- mm2src/coins_activation/src/init_token.rs | 2 ++ .../coins_activation/src/platform_coin_with_tokens.rs | 4 +++- mm2src/coins_activation/src/slp_token_activation.rs | 1 + .../src/tendermint_token_activation.rs | 1 + mm2src/coins_activation/src/token.rs | 2 ++ mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs | 4 ---- mm2src/mm2_test_helpers/src/for_tests.rs | 5 +---- 11 files changed, 35 insertions(+), 20 deletions(-) diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index f95f352301..a96cddf131 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -308,9 +308,6 @@ pub enum EthTokenActivationParams { #[derive(Clone, Deserialize)] pub struct Erc20TokenActivationRequest { pub required_confirmations: Option, - /// `true` if the token is a custom token, meaning there is no coin config for it. - #[serde(default)] - pub is_custom: bool, } /// Holds ERC-20 token-specific activation parameters when using the task manager for activation. @@ -325,16 +322,12 @@ pub struct InitErc20TokenActivationRequest { /// If not specified, the first non-change address for the first account is used. #[serde(default)] pub path_to_address: HDPathAccountToAddressId, - /// `true` if the token is a custom token, meaning there is no coin config for it. - #[serde(default)] - is_custom: bool, } impl From for Erc20TokenActivationRequest { fn from(req: InitErc20TokenActivationRequest) -> Self { Erc20TokenActivationRequest { required_confirmations: req.required_confirmations, - is_custom: req.is_custom, } } } @@ -392,6 +385,7 @@ impl EthCoin { activation_params: Erc20TokenActivationRequest, token_conf: Json, protocol: Erc20Protocol, + is_custom: bool, ) -> MmResult { // TODO // Check if ctx is required. @@ -402,7 +396,7 @@ impl EthCoin { // Todo: when custom token config storage is added, this might not be needed // `is_custom` was added to avoid this unnecessary check for non-custom tokens - if activation_params.is_custom { + if is_custom { match get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { Ok(Some(token)) => { return MmError::err(EthTokenActivationError::CustomTokenError( diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 8b882f8df0..77970284b4 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -137,12 +137,19 @@ impl TokenActivationOps for EthCoin { activation_params: Self::ActivationParams, token_conf: Json, protocol_conf: Self::ProtocolInfo, + is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { match activation_params { EthTokenActivationParams::Erc20(erc20_init_params) => match protocol_conf { EthTokenProtocol::Erc20(erc20_protocol) => { let token = platform_coin - .initialize_erc20_token(ticker.clone(), erc20_init_params, token_conf, erc20_protocol) + .initialize_erc20_token( + ticker.clone(), + erc20_init_params, + token_conf, + erc20_protocol, + is_custom, + ) .await?; let address = display_eth_address(&token.derivation_method().single_addr_or_err().await?); diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index d2f94a047a..7bc62b444a 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -145,7 +145,13 @@ impl TokenInitializer for Erc20Initializer { for param in activation_params { let token: EthCoin = self .platform_coin - .initialize_erc20_token(param.ticker, param.activation_request, param.conf, param.protocol) + .initialize_erc20_token( + param.ticker, + param.activation_request, + param.conf, + param.protocol, + param.is_custom, + ) .await?; tokens.push(token); } diff --git a/mm2src/coins_activation/src/init_erc20_token_activation.rs b/mm2src/coins_activation/src/init_erc20_token_activation.rs index f87a939352..f162cb1754 100644 --- a/mm2src/coins_activation/src/init_erc20_token_activation.rs +++ b/mm2src/coins_activation/src/init_erc20_token_activation.rs @@ -128,9 +128,16 @@ impl InitTokenActivationOps for EthCoin { token_conf: Json, protocol_conf: Self::ProtocolInfo, _task_handle: InitTokenTaskHandleShared, + is_custom: bool, ) -> Result> { let token = platform_coin - .initialize_erc20_token(ticker, activation_request.clone().into(), token_conf, protocol_conf) + .initialize_erc20_token( + ticker, + activation_request.clone().into(), + token_conf, + protocol_conf, + is_custom, + ) .await?; Ok(token) diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index 10c9fb2062..01d47b3656 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -71,6 +71,7 @@ pub trait InitTokenActivationOps: Into + TokenOf + Clone + Send + Sy token_conf: Json, protocol_conf: Self::ProtocolInfo, task_handle: InitTokenTaskHandleShared, + is_custom: bool, ) -> Result>; /// Returns the result of the token activation. @@ -220,6 +221,7 @@ where self.token_conf.clone(), self.token_protocol.clone(), task_handle.clone(), + self.request.protocol.is_some(), ) .await?; diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index e0f14eba70..d5ee5cfbf0 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -55,6 +55,7 @@ pub struct TokenActivationParams { pub(crate) conf: Json, pub(crate) activation_request: Req, pub(crate) protocol: Protocol, + pub(crate) is_custom: bool, } #[async_trait] @@ -142,12 +143,13 @@ where .into_iter() .map(|req| -> Result<_, MmError> { let (token_conf, protocol): (_, T::TokenProtocol) = - coin_conf_with_protocol(&ctx, &req.ticker, req.protocol)?; + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; Ok(TokenActivationParams { ticker: req.ticker, conf: token_conf, activation_request: req.request, protocol, + is_custom: req.protocol.is_some(), }) }) .collect::, _>>()?; diff --git a/mm2src/coins_activation/src/slp_token_activation.rs b/mm2src/coins_activation/src/slp_token_activation.rs index 30331494dd..91dbf95ea7 100644 --- a/mm2src/coins_activation/src/slp_token_activation.rs +++ b/mm2src/coins_activation/src/slp_token_activation.rs @@ -85,6 +85,7 @@ impl TokenActivationOps for SlpToken { activation_params: Self::ActivationParams, _token_conf: Json, protocol_conf: Self::ProtocolInfo, + _is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { // confirmation settings from activation params have the highest priority let required_confirmations = activation_params.required_confirmations.unwrap_or_else(|| { diff --git a/mm2src/coins_activation/src/tendermint_token_activation.rs b/mm2src/coins_activation/src/tendermint_token_activation.rs index 31b7c64192..12808505a9 100644 --- a/mm2src/coins_activation/src/tendermint_token_activation.rs +++ b/mm2src/coins_activation/src/tendermint_token_activation.rs @@ -57,6 +57,7 @@ impl TokenActivationOps for TendermintToken { _activation_params: Self::ActivationParams, _token_conf: Json, protocol_conf: Self::ProtocolInfo, + _is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { let token = TendermintToken::new(ticker, platform_coin, protocol_conf.decimals, protocol_conf.denom)?; diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index 80965f181d..5001a0f3de 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -31,6 +31,7 @@ pub trait TokenActivationOps: Into + platform_coin_with_tokens::Toke activation_params: Self::ActivationParams, token_conf: Json, protocol_conf: Self::ProtocolInfo, + is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError>; } @@ -147,6 +148,7 @@ where req.activation_params, token_conf, token_protocol, + req.protocol.is_some(), ) .await?; diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 342b607894..a19ece2ded 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -2255,7 +2255,6 @@ fn test_enable_custom_erc20() { &mm_hd, &ticker, Some(protocol.clone()), - true, 60, Some(path_to_address.clone()), )) @@ -2283,7 +2282,6 @@ fn test_enable_custom_erc20() { &mm_hd, "ERC20DEV", Some(protocol.clone()), - true, 60, Some(path_to_address), )) @@ -2335,7 +2333,6 @@ fn test_enable_custom_erc20_with_duplicate_contract_in_config() { &mm_hd, "QTC", Some(protocol.clone()), - true, 60, Some(path_to_address.clone()), )) @@ -2359,7 +2356,6 @@ fn test_enable_custom_erc20_with_duplicate_contract_in_config() { &mm_hd, &config_ticker, None, - false, 60, Some(path_to_address), )) diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 86c73a98bb..bbdba8f15b 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3257,7 +3257,6 @@ async fn init_erc20_token( mm: &MarketMakerIt, ticker: &str, protocol: Option, - is_custom: bool, path_to_address: Option, ) -> Result<(StatusCode, Json), Json> { let (status, response, _) = mm.rpc(&json!({ @@ -3268,7 +3267,6 @@ async fn init_erc20_token( "ticker": ticker, "protocol": protocol, "activation_params": { - "is_custom": is_custom, "path_to_address": path_to_address.unwrap_or_default(), } } @@ -3308,11 +3306,10 @@ pub async fn enable_erc20_token_v2( mm: &MarketMakerIt, ticker: &str, protocol: Option, - is_custom: bool, timeout: u64, path_to_address: Option, ) -> Result { - let init = init_erc20_token(mm, ticker, protocol, is_custom, path_to_address).await?.1; + let init = init_erc20_token(mm, ticker, protocol, path_to_address).await?.1; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(timeout * 1000); From a362e5c1baa7d31371ace6f95964ed4d22b1eb95 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 11 Nov 2024 15:28:03 +0200 Subject: [PATCH 27/31] review fixes: remove redundant `PartialEq`s from `CoinProtocol` nested structs --- mm2src/coins/lightning/ln_conf.rs | 2 +- mm2src/coins/tendermint/tendermint_coin.rs | 2 +- mm2src/coins/tendermint/tendermint_token.rs | 2 +- mm2src/coins/utxo.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/lightning/ln_conf.rs b/mm2src/coins/lightning/ln_conf.rs index 0b155b488b..80c5047d62 100644 --- a/mm2src/coins/lightning/ln_conf.rs +++ b/mm2src/coins/lightning/ln_conf.rs @@ -1,7 +1,7 @@ use crate::utxo::BlockchainNetwork; use lightning::util::config::{ChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct PlatformCoinConfirmationTargets { pub background: u32, pub normal: u32, diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 048f24a546..6608d6514b 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -173,7 +173,7 @@ pub struct TendermintFeeDetails { pub gas_limit: u64, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct TendermintProtocolInfo { decimals: u8, denom: String, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index ea9d12e439..1139a973e5 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -57,7 +57,7 @@ impl Deref for TendermintToken { fn deref(&self) -> &Self::Target { &self.0 } } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct TendermintTokenProtocolInfo { pub platform: String, pub decimals: u8, diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 3795fc924f..6d98451c7f 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -409,7 +409,7 @@ impl RecentlySpentOutPoints { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub enum BlockchainNetwork { #[serde(rename = "mainnet")] Mainnet, From 36e28b587f3b8b3e755f50e4e6488625e61275f7 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 11 Nov 2024 15:55:44 +0200 Subject: [PATCH 28/31] review fix: add display to all variants of `CustomTokenError` --- mm2src/coins/lp_coins.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 85ca97d6ed..5e40eb9221 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4259,21 +4259,22 @@ pub enum CoinProtocol { #[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum CustomTokenError { - DuplicateTickerInConfig { - ticker_in_config: String, - }, - DuplicateContractInConfig { - ticker_in_config: String, - }, + #[display( + fmt = "Token with the same ticker already exists in coins configs, ticker in config: {}", + ticker_in_config + )] + DuplicateTickerInConfig { ticker_in_config: String }, + #[display( + fmt = "Token with the same contract address already exists in coins configs, ticker in config: {}", + ticker_in_config + )] + DuplicateContractInConfig { ticker_in_config: String }, #[display( fmt = "Token is already activated, ticker: {}, contract address: {}", ticker, contract_address )] - TokenWithSameContractAlreadyActivated { - ticker: String, - contract_address: String, - }, + TokenWithSameContractAlreadyActivated { ticker: String, contract_address: String }, } impl CoinProtocol { From 84847ed57343cd356c72860737465bba91f4c416 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 11 Nov 2024 16:04:36 +0200 Subject: [PATCH 29/31] review fix: rename `Erc20TokenBasicInfo` to `Erc20TokenInfo` --- mm2src/coins/eth/erc20.rs | 6 +++--- mm2src/mm2_main/src/rpc/lp_commands/tokens.rs | 4 ++-- mm2src/mm2_test_helpers/src/structs.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs index 8a8d974c11..262ed08673 100644 --- a/mm2src/coins/eth/erc20.rs +++ b/mm2src/coins/eth/erc20.rs @@ -52,16 +52,16 @@ async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result Result { +pub async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { let symbol = get_token_symbol(coin, token_addr).await?; let web3 = try_s!(coin.web3().await); let decimals = get_token_decimals(&web3, token_addr).await?; - Ok(Erc20TokenBasicInfo { symbol, decimals }) + Ok(Erc20TokenInfo { symbol, decimals }) } /// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. diff --git a/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs index 7d19bf2a64..c72e772a81 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs @@ -1,4 +1,4 @@ -use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20TokenBasicInfo}; +use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20TokenInfo}; use coins::eth::valid_addr_from_str; use coins::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; use common::HttpStatusCode; @@ -14,7 +14,7 @@ pub struct TokenInfoRequest { #[derive(Serialize)] #[serde(tag = "type", content = "info")] pub enum TokenInfo { - ERC20(Erc20TokenBasicInfo), + ERC20(Erc20TokenInfo), } #[derive(Serialize)] diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index 0dc731b35b..baba173461 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -1209,7 +1209,7 @@ pub struct ActiveSwapsResponse { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct Erc20TokenBasicInfo { +pub struct Erc20TokenInfo { pub symbol: String, pub decimals: u8, } @@ -1218,7 +1218,7 @@ pub struct Erc20TokenBasicInfo { #[serde(deny_unknown_fields)] #[serde(tag = "type", content = "info")] pub enum TokenInfo { - ERC20(Erc20TokenBasicInfo), + ERC20(Erc20TokenInfo), } #[derive(Debug, Deserialize)] From f38fc3fd457a56a2613ed26df686d922d6954a04 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 13 Nov 2024 17:38:28 +0200 Subject: [PATCH 30/31] review fix: avoid direct access of index `0` in `get_token_decimals` and `get_token_symbol` --- mm2src/coins/eth/erc20.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs index 262ed08673..9fd45cb51a 100644 --- a/mm2src/coins/eth/erc20.rs +++ b/mm2src/coins/eth/erc20.rs @@ -36,18 +36,20 @@ async fn call_erc20_function( pub(crate) async fn get_token_decimals(web3: &Web3, token_addr: Address) -> Result { let tokens = call_erc20_function(web3, token_addr, "decimals").await?; - match tokens[0] { - Token::Uint(dec) => Ok(dec.as_u64() as u8), - _ => ERR!("Invalid decimals type {:?}", tokens), + match tokens.get(0) { + Some(Token::Uint(dec)) => Ok(dec.as_u64() as u8), + None => ERR!("No value returned from decimals() call"), + Some(other) => Err(format!("Expected Uint token for decimals, got {:?}", other)), } } async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result { let web3 = try_s!(coin.web3().await); let tokens = call_erc20_function(&web3, token_addr, "symbol").await?; - match &tokens[0] { - Token::String(symbol) => Ok(symbol.clone()), - _ => ERR!("Invalid symbol type {:?}", tokens), + match tokens.get(0) { + Some(Token::String(symbol)) => Ok(symbol.clone()), + None => ERR!("No value returned from symbol() call"), + Some(other) => Err(format!("Expected String token for symbol, got {:?}", other)), } } From 8b11a6c187194a4a3c1a7e9ec239b7129a807809 Mon Sep 17 00:00:00 2001 From: shamardy Date: Thu, 14 Nov 2024 02:07:01 +0200 Subject: [PATCH 31/31] review fix: minor refactors --- mm2src/coins/eth/erc20.rs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs index 9fd45cb51a..75f7033fda 100644 --- a/mm2src/coins/eth/erc20.rs +++ b/mm2src/coins/eth/erc20.rs @@ -36,21 +36,25 @@ async fn call_erc20_function( pub(crate) async fn get_token_decimals(web3: &Web3, token_addr: Address) -> Result { let tokens = call_erc20_function(web3, token_addr, "decimals").await?; - match tokens.get(0) { - Some(Token::Uint(dec)) => Ok(dec.as_u64() as u8), - None => ERR!("No value returned from decimals() call"), - Some(other) => Err(format!("Expected Uint token for decimals, got {:?}", other)), - } + let Some(token) = tokens.into_iter().next() else { + return ERR!("No value returned from decimals() call"); + }; + let Token::Uint(dec) = token else { + return ERR!("Expected Uint token for decimals, got {:?}", token); + }; + Ok(dec.as_u64() as u8) } async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result { let web3 = try_s!(coin.web3().await); let tokens = call_erc20_function(&web3, token_addr, "symbol").await?; - match tokens.get(0) { - Some(Token::String(symbol)) => Ok(symbol.clone()), - None => ERR!("No value returned from symbol() call"), - Some(other) => Err(format!("Expected String token for symbol, got {:?}", other)), - } + let Some(token) = tokens.into_iter().next() else { + return ERR!("No value returned from symbol() call"); + }; + let Token::String(symbol) = token else { + return ERR!("Expected String token for symbol, got {:?}", token); + }; + Ok(symbol) } #[derive(Serialize)] @@ -94,11 +98,10 @@ pub async fn get_enabled_erc20_by_contract( let cctx = CoinsContext::from_ctx(ctx)?; let coins = cctx.coins.lock().await; - Ok(coins - .iter() - .find(|(_, coin)| match &coin.inner { - MmCoinEnum::EthCoin(eth_coin) => eth_coin.erc20_token_address() == Some(contract_address), - _ => false, - }) - .map(|(_, coin)| coin.inner.clone())) + Ok(coins.values().find_map(|coin| match &coin.inner { + MmCoinEnum::EthCoin(eth_coin) if eth_coin.erc20_token_address() == Some(contract_address) => { + Some(coin.inner.clone()) + }, + _ => None, + })) }