Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(ETH): add eth access list (experimental) #2170

Open
wants to merge 22 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
08c9d64
add access list to swap eth transactions
dimxy Jul 15, 2024
a28547b
allow 0x in data in sign raw eth tx
dimxy Jul 19, 2024
8b0c8cb
add access list to sign raw eth tx
dimxy Jul 21, 2024
1304d85
more access lists: fix web3 tx build with access list, add list for m…
dimxy Jul 21, 2024
b85d22f
add printing of gasUsed to docker test to show access list effect
dimxy Jul 21, 2024
1722ec6
Merge branch 'dev' into add-eth-access-list
dimxy Jul 21, 2024
35f98b7
Merge branch 'dev' into add-eth-access-list
dimxy Aug 15, 2024
9aa5422
fix after merge
dimxy Aug 15, 2024
eb53960
fixed getting max_eth_tx_type from platform coin for token activation
dimxy Aug 16, 2024
df99cc5
fix clippy
dimxy Aug 16, 2024
a7ec766
fix getting gas fee estimator state from platform coin
dimxy Aug 16, 2024
9688ec9
add swap negotiation protocol version support and eth tx type activat…
dimxy Aug 16, 2024
d7c80c3
fix negotiation version as dedicate p2p topic (prev solution to exten…
dimxy Aug 17, 2024
91078a6
fix SwapMsg back to make it compatible with old nodes, add SwapExt an…
dimxy Aug 18, 2024
fc78c9e
fix comment
dimxy Aug 18, 2024
95fa60c
fix subscribe to new topic
dimxy Aug 18, 2024
8705845
fix clippy
dimxy Aug 18, 2024
1ee0949
add check of remote version for spending htlc tx (no type tx if versi…
dimxy Aug 18, 2024
35de663
fix creating access_list as None when not configured
dimxy Aug 19, 2024
e73722e
Merge branch 'dev' into add-eth-access-list
dimxy Sep 13, 2024
8175093
fix recreate taker swap started event (correct maker version)
dimxy Sep 13, 2024
92fc0f3
fix maker/taker version in test data (for recreate swap tests to work)
dimxy Sep 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
446 changes: 372 additions & 74 deletions mm2src/coins/eth.rs

Large diffs are not rendered by default.

30 changes: 26 additions & 4 deletions mm2src/coins/eth/eth_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@ use super::{web3_transport::Web3Transport, EthCoin};
use common::{custom_futures::timeout::FutureTimerExt, log::debug};
use instant::Duration;
use serde_json::Value;
use web3::types::{Address, Block, BlockId, BlockNumber, Bytes, CallRequest, FeeHistory, Filter, Log, Proof, SyncState,
Trace, TraceFilter, Transaction, TransactionId, TransactionReceipt, TransactionRequest, Work, H256,
H520, H64, U256, U64};
use web3::types::{AccessList, Address, Block, BlockId, BlockNumber, Bytes, CallRequest, FeeHistory, Filter, Log,
Proof, SyncState, Trace, TraceFilter, Transaction, TransactionId, TransactionReceipt,
TransactionRequest, Work, H256, H520, H64, U256, U64};
use web3::{helpers, Transport};

pub(crate) const ETH_RPC_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);

/// Result of eth_createAccessList (apparently missed in rust web3 lib).
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateAccessListResult {
#[serde(rename = "gasUsed")]
pub gas_used: U256,
#[serde(rename = "accessList")]
pub access_list: AccessList,
}

impl EthCoin {
async fn try_rpc_send(&self, method: &str, params: Vec<jsonrpc_core::Value>) -> Result<Value, web3::Error> {
let mut clients = self.web3_instances.lock().await;
Expand Down Expand Up @@ -310,7 +319,7 @@ impl EthCoin {
}

/// Get transaction receipt
pub(crate) async fn transaction_receipt(&self, hash: H256) -> Result<Option<TransactionReceipt>, web3::Error> {
pub async fn transaction_receipt(&self, hash: H256) -> Result<Option<TransactionReceipt>, web3::Error> {
let hash = helpers::serialize(&hash);

self.try_rpc_send("eth_getTransactionReceipt", vec![hash])
Expand Down Expand Up @@ -452,4 +461,17 @@ impl EthCoin {
.await
.and_then(|t| serde_json::from_value(t).map_err(Into::into))
}

/// Call eth_createAccessList for a transaction
pub(crate) async fn eth_create_access_list(
&self,
tx: TransactionRequest,
block: Option<BlockNumber>,
) -> Result<CreateAccessListResult, web3::Error> {
let req = helpers::serialize(&tx);
let block = helpers::serialize(&block.unwrap_or(BlockNumber::Pending));
self.try_rpc_send("eth_createAccessList", vec![req, block])
.await
.and_then(|t| serde_json::from_value(t).map_err(Into::into))
}
}
12 changes: 12 additions & 0 deletions mm2src/coins/eth/eth_swap_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ impl EthCoin {
data,
// TODO need new consts and params for v2 calls. now it uses v1
U256::from(self.gas_limit.eth_payment),
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -123,6 +125,8 @@ impl EthCoin {
data,
// TODO need new consts and params for v2 calls. now it uses v1
U256::from(self.gas_limit.erc20_payment),
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -249,6 +253,8 @@ impl EthCoin {
Action::Call(taker_swap_v2_contract),
data,
gas_limit,
None,
true,
)
.compat()
.await?;
Expand Down Expand Up @@ -316,6 +322,8 @@ impl EthCoin {
Action::Call(taker_swap_v2_contract),
data,
U256::from(gas_limit),
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -373,6 +381,8 @@ impl EthCoin {
Action::Call(taker_swap_v2_contract),
data,
U256::from(gas_limit),
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -451,6 +461,8 @@ impl EthCoin {
Action::Call(taker_swap_v2_contract),
data,
gas_limit,
None,
true,
)
.compat()
.await?;
Expand Down
10 changes: 7 additions & 3 deletions mm2src/coins/eth/eth_withdraw.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{checksum_address, u256_to_big_decimal, wei_from_big_decimal, EthCoinType, EthDerivationMethod,
EthPrivKeyPolicy, Public, WithdrawError, WithdrawRequest, WithdrawResult, ERC20_CONTRACT, H160, H256};
use crate::eth::{calc_total_fee, get_eth_gas_details_from_withdraw_fee, tx_builder_with_pay_for_gas_option,
tx_type_from_pay_for_gas_option, Action, Address, EthTxFeeDetails, KeyPair, PayForGasOption,
use crate::eth::{calc_total_fee, get_eth_gas_details_from_withdraw_fee, set_tx_type_from_pay_for_gas_option,
tx_builder_with_pay_for_gas_option, Action, Address, EthTxFeeDetails, KeyPair, PayForGasOption,
SignedEthTx, TransactionWrapper, UnSignedEthTxBuilder};
use crate::hd_wallet::{HDCoinWithdrawOps, HDWalletOps, WithdrawFrom, WithdrawSenderAddress};
use crate::rpc_command::init_withdraw::{WithdrawInProgressStatus, WithdrawTaskHandleShared};
Expand All @@ -15,6 +15,7 @@ use crypto::hw_rpc_task::HwRpcTaskAwaitingStatus;
use crypto::trezor::trezor_rpc_task::{TrezorRequestStatuses, TrezorRpcTaskProcessor};
use crypto::{CryptoCtx, HwRpcError};
use ethabi::Token;
use ethcore_transaction::TxType;
use futures::compat::Future01CompatExt;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::map_mm_error::MapMmError;
Expand Down Expand Up @@ -262,7 +263,10 @@ where
.await?
.map_to_mm(WithdrawError::Transport)?;

let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option);
let mut tx_type = TxType::Legacy;
set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option);
drop_mutability!(tx_type);

if !coin.is_tx_type_supported(&tx_type) {
return MmError::err(WithdrawError::TxTypeNotSupported);
}
Expand Down
3 changes: 2 additions & 1 deletion mm2src/coins/eth/for_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ pub(crate) fn eth_coin_from_keypair(
trezor_coin: None,
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
address_nonce_locks: Arc::new(AsyncMutex::new(new_nonce_lock())),
max_eth_tx_type: None,
max_eth_tx_type: Some(2),
use_access_list: false,
erc20_tokens_infos: Default::default(),
nfts_infos: Arc::new(Default::default()),
platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported),
Expand Down
8 changes: 8 additions & 0 deletions mm2src/coins/eth/nft_swap_v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ impl EthCoin {
Action::Call(*args.nft_swap_info.token_address),
data,
U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -165,6 +167,8 @@ impl EthCoin {
Action::Call(*etomic_swap_contract),
data,
U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -205,6 +209,8 @@ impl EthCoin {
Action::Call(*etomic_swap_contract),
data,
U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value
None,
true,
)
.compat()
.await
Expand Down Expand Up @@ -246,6 +252,8 @@ impl EthCoin {
Action::Call(*etomic_swap_contract),
data,
U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value
None,
true,
)
.compat()
.await
Expand Down
34 changes: 30 additions & 4 deletions mm2src/coins/eth/v2_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,19 @@ 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 mut platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?;
if matches!(
platform_fee_estimator_state.as_ref(),
FeeEstimatorState::PlatformCoinRequired
) {
platform_fee_estimator_state = self.platform_fee_estimator_state.clone();
// try to use the state from the platform coin
}
drop_mutability!(platform_fee_estimator_state);
let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type)
.await?
.or(self.max_eth_tx_type); // use the platform coin setting if token setting is none
let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false);
let gas_limit = extract_gas_limit_from_conf(&conf)
.map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?;

Expand All @@ -438,6 +449,7 @@ impl EthCoin {
history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()),
swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal),
max_eth_tx_type,
use_access_list,
ctx: self.ctx.clone(),
required_confirmations,
chain_id: self.chain_id,
Expand Down Expand Up @@ -504,8 +516,19 @@ impl EthCoin {
let coin_type = EthCoinType::Nft {
platform: self.ticker.clone(),
};
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 mut platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?;
if matches!(
platform_fee_estimator_state.as_ref(),
FeeEstimatorState::PlatformCoinRequired
) {
platform_fee_estimator_state = self.platform_fee_estimator_state.clone();
// try to use the state from the platform coin
}
drop_mutability!(platform_fee_estimator_state);
let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type)
.await?
.or(self.max_eth_tx_type); // use the platform coin setting if token setting is none
let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false);
let gas_limit = extract_gas_limit_from_conf(&conf)
.map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?;

Expand All @@ -524,6 +547,7 @@ impl EthCoin {
history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()),
swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal),
max_eth_tx_type,
use_access_list,
required_confirmations,
ctx: self.ctx.clone(),
chain_id: self.chain_id,
Expand Down Expand Up @@ -639,6 +663,7 @@ pub async fn eth_coin_from_conf_and_request_v2(
let coin_type = EthCoinType::Eth;
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 use_access_list = conf["use_access_list"].as_bool().unwrap_or(false);
let gas_limit = extract_gas_limit_from_conf(conf)
.map_to_mm(|e| EthActivationV2Error::InternalError(format!("invalid gas_limit config {}", e)))?;

Expand All @@ -657,6 +682,7 @@ pub async fn eth_coin_from_conf_and_request_v2(
history_sync_state: Mutex::new(HistorySyncState::NotEnabled),
swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal),
max_eth_tx_type,
use_access_list,
ctx: ctx.weak(),
required_confirmations,
chain_id,
Expand Down
25 changes: 25 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,19 @@ pub mod z_coin;
use crate::coin_balance::{BalanceObjectOps, HDWalletBalanceObject};
use z_coin::{ZCoin, ZcoinProtocolInfo};

mod swap_features;

/// Default swap protocol version before version field added to NegotiationDataMsg
pub const LEGACY_PROTOCOL_VERSION: u16 = 0;

/// Current swap protocol version
pub const SWAP_PROTOCOL_VERSION: u16 = 1;

/// Minimal supported swap protocol version implemented by remote peer
pub const MIN_SWAP_PROTOCOL_VERSION: u16 = LEGACY_PROTOCOL_VERSION;

// TODO: add version field to the SWAP V2 negotiation protocol

pub type TransactionFut = Box<dyn Future<Item = TransactionEnum, Error = TransactionErr> + Send>;
pub type TransactionResult = Result<TransactionEnum, TransactionErr>;
pub type BalanceResult<T> = Result<T, MmError<BalanceError>>;
Expand Down Expand Up @@ -528,6 +541,8 @@ pub struct SignEthTransactionParams {
gas_limit: U256,
/// Optional gas price or fee per gas params
pay_for_gas: Option<PayForGasParams>,
/// Optional access list
access_list: Option<Vec<EthAccessListItem>>,
}

#[derive(Clone, Debug, Deserialize)]
Expand Down Expand Up @@ -558,6 +573,12 @@ pub struct MyWalletAddress {
wallet_address: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct EthAccessListItem {
pub address: String,
pub storage_keys: Vec<String>,
}

pub type SignatureResult<T> = Result<T, MmError<SignatureError>>;
pub type VerificationResult<T> = Result<T, MmError<VerificationError>>;

Expand Down Expand Up @@ -956,6 +977,8 @@ pub struct SendPaymentArgs<'a> {
pub watcher_reward: Option<WatcherReward>,
/// As of now, this field is specifically used to wait for confirmations of ERC20 approval transaction.
pub wait_for_confirmation_until: u64,
/// other party version
pub other_version: u16,
}

#[derive(Clone, Debug)]
Expand All @@ -974,6 +997,8 @@ pub struct SpendPaymentArgs<'a> {
pub swap_contract_address: &'a Option<BytesJson>,
pub swap_unique_data: &'a [u8],
pub watcher_reward: bool,
/// other party version
pub other_version: u16,
}

#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/rpc_command/get_estimated_fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl FeeEstimatorState {
Ok(Arc::new(fee_estimator_state))
},
(_, EthCoinType::Erc20 { platform, .. }) | (_, EthCoinType::Nft { platform, .. }) => {
let platform_coin = lp_coinfind_or_err(ctx, platform).await;
let platform_coin = lp_coinfind_or_err(ctx, platform).await; // NOTE: this won't find the platform coin when "enable_eth_with_tokens" rpc is used. So we need to get platform coin config from 'self', see initialize_erc20_token fn
match platform_coin {
Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.platform_fee_estimator_state.clone()),
_ => Ok(Arc::new(FeeEstimatorState::PlatformCoinRequired)),
Expand Down
5 changes: 4 additions & 1 deletion mm2src/coins/solana/solana_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::solana_common_tests::{generate_key_pair_from_iguana_seed, generate_ke
solana_coin_for_test, SolanaNet};
use super::solana_decode_tx_helpers::SolanaConfirmedTransaction;
use super::*;
use crate::{MarketCoinOps, SwapTxTypeWithSecretHash};
use crate::{MarketCoinOps, SwapTxTypeWithSecretHash, SWAP_PROTOCOL_VERSION};

#[test]
#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -356,6 +356,7 @@ fn solana_coin_send_and_refund_maker_payment() {
let secret_hash = sha256(&secret);

let args = SendPaymentArgs {
other_version: SWAP_PROTOCOL_VERSION,
time_lock_duration: 0,
time_lock,
other_pubkey: taker_pub.as_ref(),
Expand Down Expand Up @@ -403,6 +404,7 @@ fn solana_coin_send_and_spend_maker_payment() {
let secret_hash = sha256(&secret);

let maker_payment_args = SendPaymentArgs {
other_version: SWAP_PROTOCOL_VERSION,
time_lock_duration: 0,
time_lock: lock_time,
other_pubkey: taker_pub.as_ref(),
Expand All @@ -421,6 +423,7 @@ fn solana_coin_send_and_spend_maker_payment() {
let maker_pub = taker_pub;

let spends_payment_args = SpendPaymentArgs {
other_version: SWAP_PROTOCOL_VERSION,
other_payment_tx: &tx.tx_hex(),
time_lock: lock_time,
other_pubkey: maker_pub.as_ref(),
Expand Down
18 changes: 18 additions & 0 deletions mm2src/coins/swap_features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// Framework to activate new swap protocol features at certain protocol version

#[derive(PartialEq)]
pub(crate) enum SwapFeature {
EthTypeTx, // eth type transaction EIP-2718 supported
}

impl SwapFeature {
// add new features to activate
const SWAP_FEATURE_ACTIVATION: &[(u16, SwapFeature)] = &[(1, SwapFeature::EthTypeTx)];

pub(crate) fn is_active(feature: SwapFeature, version: u16) -> bool {
if let Some(found) = Self::SWAP_FEATURE_ACTIVATION.iter().find(|fv| fv.1 == feature) {
return version >= found.0;
}
false
}
}
3 changes: 2 additions & 1 deletion mm2src/coins/utxo/utxo_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardC
use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps};
use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, DexFee, IguanaPrivKey, PrivKeyBuildPolicy,
SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue,
TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG};
TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG, SWAP_PROTOCOL_VERSION};
#[cfg(not(target_arch = "wasm32"))]
use crate::{WaitForHTLCTxSpendArgs, WithdrawFee};
use chain::{BlockHeader, BlockHeaderBits, OutPoint};
Expand Down Expand Up @@ -165,6 +165,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() {
let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap();
let secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap();
let maker_spends_payment_args = SpendPaymentArgs {
other_version: SWAP_PROTOCOL_VERSION,
other_payment_tx: &tx_hex,
time_lock: 777,
other_pubkey: coin.my_public_key().unwrap(),
Expand Down
Loading
Loading