From f278de4dd80d526e0439b2401bdf828b5821586b Mon Sep 17 00:00:00 2001 From: Jouzo <15011228+Jouzo@users.noreply.github.com> Date: Fri, 18 Aug 2023 01:25:47 +0200 Subject: [PATCH] EVM: DST20 migration TX (#2327) * Add DST20 migration TX and receipt * Temporary disable of ValidateCoinbaseXVMOutput * Temporary disable of rollback related test failure * Extract contract (tx, receipt) creation * Fix test balance issue * Revert "Temporary disable of ValidateCoinbaseXVMOutput" This reverts commit 91fa9aefb79a54cc6c7cd2a2815922a7cbb37012. * Fix test * Refactor to add migration txs in construct_block when genesis EVM block * Get loan tokens from cache * Restore failing test * Remove block cloning * Fix hash used in recover sender address * Use same sender address for receipt and SignedTx * Index bytecode on migration * Restore failing test * Use token_id as migratory TX nonce --- lib/ain-contracts/src/lib.rs | 4 +- lib/ain-cpp-imports/src/bridge.rs | 9 ++ lib/ain-cpp-imports/src/lib.rs | 14 ++ lib/ain-evm/src/evm.rs | 108 ++++++++++++--- lib/ain-evm/src/receipt.rs | 20 +-- lib/ain-evm/src/transaction/mod.rs | 49 +------ lib/ain-evm/src/transaction/system.rs | 1 + lib/ain-rs-exports/src/evm.rs | 9 +- lib/ain-rs-exports/src/lib.rs | 7 +- src/ffi/ffiexports.cpp | 15 +++ src/ffi/ffiexports.h | 7 + src/masternodes/errors.h | 4 - src/masternodes/govvariables/attributes.cpp | 4 +- src/masternodes/mn_checks.cpp | 10 +- src/masternodes/validation.cpp | 50 +------ src/masternodes/validation.h | 2 - src/miner.cpp | 9 +- test/functional/feature_dst20.py | 141 +++++++++++++++++++- 18 files changed, 317 insertions(+), 146 deletions(-) diff --git a/lib/ain-contracts/src/lib.rs b/lib/ain-contracts/src/lib.rs index 2f48aa88a3..29a8543578 100644 --- a/lib/ain-contracts/src/lib.rs +++ b/lib/ain-contracts/src/lib.rs @@ -76,8 +76,8 @@ pub fn get_dst20_codehash() -> Result { Ok(Blake2Hasher::hash(&bytecode)) } -pub fn dst20_address_from_token_id(token_id: &str) -> Result { - let number_str = format!("{:x}", token_id.parse::()?); +pub fn dst20_address_from_token_id(token_id: u64) -> Result { + let number_str = format!("{:x}", token_id); let padded_number_str = format!("{number_str:0>38}"); let final_str = format!("ff{padded_number_str}"); diff --git a/lib/ain-cpp-imports/src/bridge.rs b/lib/ain-cpp-imports/src/bridge.rs index 32e14feed2..00446242aa 100644 --- a/lib/ain-cpp-imports/src/bridge.rs +++ b/lib/ain-cpp-imports/src/bridge.rs @@ -7,9 +7,17 @@ pub mod ffi { pub finality_count: u64, } + #[derive(Debug, Clone)] + pub struct DST20Token { + pub id: u64, + pub name: String, + pub symbol: String, + } + unsafe extern "C++" { include!("ffi/ffiexports.h"); type Attributes; + type DST20Token; fn getChainId() -> u64; fn isMining() -> bool; @@ -28,5 +36,6 @@ pub mod ffi { fn getCurrentHeight() -> i32; fn getAttributeDefaults() -> Attributes; fn CppLogPrintf(message: String); + fn getDST20Tokens(mnview_ptr: usize) -> Vec; } } diff --git a/lib/ain-cpp-imports/src/lib.rs b/lib/ain-cpp-imports/src/lib.rs index ad6f2c81bd..0c068c2138 100644 --- a/lib/ain-cpp-imports/src/lib.rs +++ b/lib/ain-cpp-imports/src/lib.rs @@ -15,6 +15,12 @@ mod ffi { pub finality_count: u64, } + pub struct DST20Token { + pub id: u64, + pub name: String, + pub symbol: String, + } + const UNIMPL_MSG: &str = "This cannot be used on a test path"; pub fn getChainId() -> u64 { unimplemented!("{}", UNIMPL_MSG) @@ -69,6 +75,10 @@ mod ffi { // Intentionally left empty, so it can be used from everywhere. // Just the logs are skipped. } + + pub fn getDST20Tokens(_mnview_ptr: usize) -> Vec { + unimplemented!("{}", UNIMPL_MSG) + } } pub use ffi::Attributes; @@ -155,5 +165,9 @@ pub fn log_print(message: &str) { ffi::CppLogPrintf(message.to_owned()); } +pub fn get_dst20_tokens(mnview_ptr: usize) -> Vec { + ffi::getDST20Tokens(mnview_ptr) +} + #[cfg(test)] mod tests {} diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index ed7f5541d7..4b6adb074f 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -3,7 +3,10 @@ use std::sync::Arc; use ain_contracts::{Contracts, CONTRACT_ADDRESSES}; use anyhow::format_err; -use ethereum::{Block, PartialHeader, ReceiptV3}; +use ethereum::{ + Block, EIP1559ReceiptData, LegacyTransaction, PartialHeader, ReceiptV3, TransactionAction, + TransactionSignature, TransactionV2, +}; use ethereum_types::{Bloom, H160, H64, U256}; use log::debug; use primitive_types::H256; @@ -22,9 +25,9 @@ use crate::storage::traits::BlockStorage; use crate::storage::Storage; use crate::traits::Executor; use crate::transaction::system::{BalanceUpdate, DST20Data, DeployContractData, SystemTx}; -use crate::transaction::SignedTx; +use crate::transaction::{SignedTx, LOWER_H256}; use crate::trie::GENESIS_STATE_ROOT; -use crate::txqueue::{BlockData, QueueTx}; +use crate::txqueue::{BlockData, QueueTx, QueueTxItem}; use crate::Result; pub struct EVMServices { @@ -55,6 +58,8 @@ pub struct DST20BridgeInfo { pub storage: Vec<(H256, H256)>, } +pub type ReceiptAndOptionalContractAddress = (ReceiptV3, Option); + impl EVMServices { /// Constructs a new Handlers instance. Depending on whether the defid -ethstartstate flag is set, /// it either revives the storage from a previously saved state or initializes new storage using input from a JSON file. @@ -114,13 +119,22 @@ impl EVMServices { beneficiary: H160, timestamp: u64, dvm_block_number: u64, + mnview_ptr: usize, ) -> Result { let tx_queue = self.core.tx_queues.get(queue_id)?; let mut queue = tx_queue.data.lock().unwrap(); + + let is_evm_genesis_block = queue.target_block == U256::zero(); + if is_evm_genesis_block { + let migration_txs = get_dst20_migration_txs(mnview_ptr)?; + queue.transactions.extend(migration_txs.into_iter()) + } + let queue_txs_len = queue.transactions.len(); let mut all_transactions = Vec::with_capacity(queue_txs_len); let mut failed_transactions = Vec::with_capacity(queue_txs_len); - let mut receipts_v3: Vec = Vec::with_capacity(queue_txs_len); + let mut receipts_v3: Vec = + Vec::with_capacity(queue_txs_len); let mut total_gas_used = 0u64; let mut total_gas_fees = U256::zero(); let mut logs_bloom: Bloom = Bloom::default(); @@ -188,8 +202,7 @@ impl EVMServices { } = EVMServices::counter_contract(dvm_block_number, current_block_number)?; executor.update_storage(address, storage)?; } - - for queue_item in queue.transactions.clone() { + for (idx, queue_item) in queue.transactions.clone().into_iter().enumerate() { match queue_item.tx { QueueTx::SignedTx(signed_tx) => { let nonce = executor.get_nonce(&signed_tx.sender); @@ -208,11 +221,11 @@ impl EVMServices { receipt, ) = executor.exec(&signed_tx, prepay_gas); debug!( - "receipt : {:#?} for signed_tx : {:#x}", + "receipt : {:#?}, exit_reason {:#?} for signed_tx : {:#x}", receipt, + exit_reason, signed_tx.transaction.hash() ); - if !exit_reason.is_succeed() { failed_transactions.push(hex::encode(queue_item.tx_hash)); } @@ -223,7 +236,7 @@ impl EVMServices { all_transactions.push(signed_tx.clone()); EVMCoreService::logs_bloom(logs, &mut logs_bloom); - receipts_v3.push(receipt); + receipts_v3.push((receipt, None)); } QueueTx::SystemTx(SystemTx::EvmIn(BalanceUpdate { address, amount })) => { debug!( @@ -250,6 +263,7 @@ impl EVMServices { name, symbol, address, + token_id, })) => { debug!( "[construct_block] DeployContract for address {}, name {}, symbol {}", @@ -262,9 +276,18 @@ impl EVMServices { storage, } = EVMServices::dst20_contract(&mut executor, address, name, symbol)?; - if let Err(e) = executor.deploy_contract(address, bytecode, storage) { + if let Err(e) = executor.deploy_contract(address, bytecode.clone(), storage) { debug!("[construct_block] EvmOut failed with {e}"); } + let (tx, receipt) = create_deploy_contract_tx( + token_id, + current_block_number, + &base_fee, + bytecode.into_vec(), + )?; + + all_transactions.push(Box::new(tx)); + receipts_v3.push((receipt, Some(address))); } QueueTx::SystemTx(SystemTx::DST20Bridge(DST20Data { to, @@ -336,19 +359,17 @@ impl EVMServices { Vec::new(), ); + let block_hash = *block.header.hash().as_fixed_bytes(); let receipts = self.receipt.generate_receipts( &all_transactions, receipts_v3, block.header.hash(), block.header.number, ); - queue.block_data = Some(BlockData { - block: block.clone(), - receipts, - }); + queue.block_data = Some(BlockData { block, receipts }); Ok(FinalizedBlockInfo { - block_hash: *block.header.hash().as_fixed_bytes(), + block_hash, failed_transactions, total_burnt_fees, total_priority_fees, @@ -570,7 +591,7 @@ impl EVMServices { queue_id: u64, name: &str, symbol: &str, - token_id: &str, + token_id: u64, ) -> Result { let address = ain_contracts::dst20_address_from_token_id(token_id)?; debug!( @@ -592,6 +613,7 @@ impl EVMServices { let deploy_tx = QueueTx::SystemTx(SystemTx::DeployContract(DeployContractData { name: String::from(name), symbol: String::from(symbol), + token_id, address, })); @@ -603,9 +625,7 @@ impl EVMServices { pub fn reserve_dst20_namespace(&self, executor: &mut AinExecutor) -> Result<()> { let bytecode = ain_contracts::get_system_reserved_bytecode()?; let addresses = (1..=1024) - .map(|token_id| { - ain_contracts::dst20_address_from_token_id(&token_id.to_string()).unwrap() - }) + .map(|token_id| ain_contracts::dst20_address_from_token_id(token_id).unwrap()) .collect::>(); for address in addresses { @@ -616,3 +636,53 @@ impl EVMServices { Ok(()) } } + +fn create_deploy_contract_tx( + token_id: u64, + block_number: U256, + base_fee: &U256, + bytecode: Vec, +) -> Result<(SignedTx, ReceiptV3)> { + let tx = TransactionV2::Legacy(LegacyTransaction { + nonce: U256::from(token_id), + gas_price: base_fee.clone(), + gas_limit: U256::from(u64::MAX), + action: TransactionAction::Create, + value: U256::zero(), + input: bytecode, + signature: TransactionSignature::new(27, LOWER_H256, LOWER_H256) + .ok_or(format_err!("Invalid transaction signature format"))?, + }) + .try_into()?; + + let receipt = ReceiptV3::Legacy(EIP1559ReceiptData { + status_code: 1u8, + used_gas: U256::zero(), + logs_bloom: Bloom::default(), + logs: Vec::new(), + }); + + Ok((tx, receipt)) +} + +fn get_dst20_migration_txs(mnview_ptr: usize) -> Result> { + let mut txs = Vec::new(); + for token in ain_cpp_imports::get_dst20_tokens(mnview_ptr) { + let address = ain_contracts::dst20_address_from_token_id(token.id)?; + debug!("Deploying to address {:#?}", address); + + let tx = QueueTx::SystemTx(SystemTx::DeployContract(DeployContractData { + name: token.name, + symbol: token.symbol, + token_id: token.id, + address, + })); + txs.push(QueueTxItem { + tx, + tx_hash: Default::default(), + tx_fee: U256::zero(), + gas_used: U256::zero(), + }); + } + Ok(txs) +} diff --git a/lib/ain-evm/src/receipt.rs b/lib/ain-evm/src/receipt.rs index b9101bdcb9..3c229fe306 100644 --- a/lib/ain-evm/src/receipt.rs +++ b/lib/ain-evm/src/receipt.rs @@ -7,6 +7,7 @@ use primitive_types::{H160, H256, U256}; use rlp::RlpStream; use serde::{Deserialize, Serialize}; +use crate::evm::ReceiptAndOptionalContractAddress; use crate::storage::{traits::ReceiptStorage, Storage}; use crate::transaction::SignedTx; use crate::Result; @@ -43,18 +44,18 @@ impl ReceiptService { Self { storage } } - pub fn get_receipts_root(receipts: &[ReceiptV3]) -> H256 { + pub fn get_receipts_root(receipts: &[ReceiptAndOptionalContractAddress]) -> H256 { ordered_trie_root( receipts .iter() - .map(|r| EnvelopedEncodable::encode(r).freeze()), + .map(|(r, _)| EnvelopedEncodable::encode(r).freeze()), ) } pub fn generate_receipts( &self, transactions: &[Box], - receipts: Vec, + receipts_and_contract_address: Vec, block_hash: H256, block_number: U256, ) -> Vec { @@ -64,8 +65,8 @@ impl ReceiptService { transactions .iter() .enumerate() - .zip(receipts) - .map(|((index, signed_tx), receipt)| { + .zip(receipts_and_contract_address) + .map(|((index, signed_tx), (receipt, contract_address))| { let receipt_data = match &receipt { ReceiptV3::Legacy(data) | ReceiptV3::EIP2930(data) @@ -84,10 +85,11 @@ impl ReceiptService { to: signed_tx.to(), tx_index: index, tx_type: signed_tx.transaction.type_id().unwrap_or_default(), - contract_address: signed_tx - .to() - .is_none() - .then(|| get_contract_address(&signed_tx.sender, &signed_tx.nonce())), + contract_address: signed_tx.to().is_none().then(|| { + contract_address.unwrap_or_else(|| { + get_contract_address(&signed_tx.sender, &signed_tx.nonce()) + }) + }), logs_index: logs_size - logs_len, cumulative_gas, } diff --git a/lib/ain-evm/src/transaction/mod.rs b/lib/ain-evm/src/transaction/mod.rs index 74b206379b..0b0255b256 100644 --- a/lib/ain-evm/src/transaction/mod.rs +++ b/lib/ain-evm/src/transaction/mod.rs @@ -4,7 +4,6 @@ use ethereum::{ AccessList, EnvelopedDecoderError, LegacyTransaction, TransactionAction, TransactionSignature, TransactionV2, }; -use libsecp256k1::PublicKey; use primitive_types::{H160, H256, U256}; use rlp::RlpStream; use sha3::Digest; @@ -101,26 +100,15 @@ impl From<&LegacyTransaction> for LegacyUnsignedTransaction { pub struct SignedTx { pub transaction: TransactionV2, pub sender: H160, - pub pubkey: PublicKey, } impl TryFrom for SignedTx { type Error = TransactionError; - fn try_from(src: TransactionV2) -> Result { - let pubkey = match &src { + fn try_from(transaction: TransactionV2) -> Result { + let pubkey = match &transaction { TransactionV2::Legacy(tx) => { - let msg = ethereum::LegacyTransactionMessage { - nonce: tx.nonce, - gas_price: tx.gas_price, - gas_limit: tx.gas_limit, - action: tx.action, - value: tx.value, - input: tx.input.clone(), - chain_id: tx.signature.chain_id(), - }; - let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..])?; - let hash = H256::from(signing_message.serialize()); + let hash = tx.hash(); recover_public_key( &hash, tx.signature.r(), @@ -129,41 +117,17 @@ impl TryFrom for SignedTx { ) } TransactionV2::EIP2930(tx) => { - let msg = ethereum::EIP2930TransactionMessage { - chain_id: tx.chain_id, - nonce: tx.nonce, - gas_price: tx.gas_price, - gas_limit: tx.gas_limit, - action: tx.action, - value: tx.value, - input: tx.input.clone(), - access_list: tx.access_list.clone(), - }; - let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..])?; - let hash = H256::from(signing_message.serialize()); + let hash = tx.hash(); recover_public_key(&hash, &tx.r, &tx.s, u8::from(tx.odd_y_parity)) } TransactionV2::EIP1559(tx) => { - let msg = ethereum::EIP1559TransactionMessage { - chain_id: tx.chain_id, - nonce: tx.nonce, - max_priority_fee_per_gas: tx.max_priority_fee_per_gas, - max_fee_per_gas: tx.max_fee_per_gas, - gas_limit: tx.gas_limit, - action: tx.action, - value: tx.value, - input: tx.input.clone(), - access_list: tx.access_list.clone(), - }; - let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..])?; - let hash = H256::from(signing_message.serialize()); + let hash = tx.hash(); recover_public_key(&hash, &tx.r, &tx.s, u8::from(tx.odd_y_parity)) } }?; Ok(SignedTx { - transaction: src, + transaction, sender: public_key_to_address(&pubkey), - pubkey, }) } } @@ -359,7 +323,6 @@ mod tests { // Legacy let signed_tx = crate::transaction::SignedTx::try_from("f86b8085689451eee18252089434c1ca09a2dc717d89baef2f30ff6a6b2975e17e872386f26fc10000802da0ae5c76f8073460cbc7a911d3cc1b367072db64848a9532343559ce6917c51a46a01d2e4928450c59acca3de8340eb15b7446b37936265a51ab35e63f749a048002").unwrap(); - assert_eq!(hex::encode(signed_tx.pubkey.serialize()), "044c6412f7cd3ac0e2538c3c9843d27d1e03b422eaf655c6a699da22b57a89802989318dbaeea62f5fc751fa8cd1404e687d67b8ab8513fe0d37bafbf407aa6cf7"); assert_eq!( hex::encode(signed_tx.sender.as_fixed_bytes()), "f829754bae400b679febefdcfc9944c323e1f94e" diff --git a/lib/ain-evm/src/transaction/system.rs b/lib/ain-evm/src/transaction/system.rs index 2aae18fa27..e4c145615c 100644 --- a/lib/ain-evm/src/transaction/system.rs +++ b/lib/ain-evm/src/transaction/system.rs @@ -5,6 +5,7 @@ pub struct DeployContractData { pub name: String, pub symbol: String, pub address: H160, + pub token_id: u64, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/lib/ain-rs-exports/src/evm.rs b/lib/ain-rs-exports/src/evm.rs index 881876c52c..2656987b9b 100644 --- a/lib/ain-rs-exports/src/evm.rs +++ b/lib/ain-rs-exports/src/evm.rs @@ -437,6 +437,7 @@ pub fn evm_unsafe_try_construct_block_in_q( miner_address: [u8; 20], timestamp: u64, dvm_block_number: u64, + mnview_ptr: usize, ) -> ffi::FinalizeBlockCompletion { let eth_address = H160::from(miner_address); unsafe { @@ -446,6 +447,7 @@ pub fn evm_unsafe_try_construct_block_in_q( eth_address, timestamp, dvm_block_number, + mnview_ptr, ) { Ok(FinalizedBlockInfo { block_hash, @@ -621,7 +623,7 @@ pub fn evm_try_is_dst20_deployed_or_queued( queue_id: u64, name: &str, symbol: &str, - token_id: &str, + token_id: u64, ) -> bool { unsafe { match SERVICES @@ -736,7 +738,7 @@ pub fn evm_try_create_dst20( native_hash: [u8; 32], name: &str, symbol: &str, - token_id: &str, + token_id: u64, ) { let address = match ain_contracts::dst20_address_from_token_id(token_id) { Ok(address) => address, @@ -748,6 +750,7 @@ pub fn evm_try_create_dst20( name: String::from(name), symbol: String::from(symbol), address, + token_id, })); unsafe { @@ -767,7 +770,7 @@ pub fn evm_try_bridge_dst20( address: &str, amount: [u8; 32], native_hash: [u8; 32], - token_id: &str, + token_id: u64, out: bool, ) { let Ok(address) = address.parse() else { diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 17a886092f..53739eb4c0 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -160,6 +160,7 @@ pub mod ffi { miner_address: [u8; 20], timestamp: u64, dvm_block_number: u64, + mnview_ptr: usize, ) -> FinalizeBlockCompletion; fn evm_unsafe_try_commit_queue(result: &mut CrossBoundaryResult, queue_id: u64); fn evm_try_set_attribute( @@ -196,7 +197,7 @@ pub mod ffi { native_hash: [u8; 32], name: &str, symbol: &str, - token_id: &str, + token_id: u64, ); fn evm_try_bridge_dst20( result: &mut CrossBoundaryResult, @@ -204,7 +205,7 @@ pub mod ffi { address: &str, amount: [u8; 32], native_hash: [u8; 32], - token_id: &str, + token_id: u64, out: bool, ); fn evm_try_is_dst20_deployed_or_queued( @@ -212,7 +213,7 @@ pub mod ffi { queue_id: u64, name: &str, symbol: &str, - token_id: &str, + token_id: u64, ) -> bool; fn evm_unsafe_try_get_target_block_in_q( diff --git a/src/ffi/ffiexports.cpp b/src/ffi/ffiexports.cpp index 712a7e295a..d250f02540 100644 --- a/src/ffi/ffiexports.cpp +++ b/src/ffi/ffiexports.cpp @@ -224,3 +224,18 @@ int getCurrentHeight() { Attributes getAttributeDefaults() { return Attributes::Default(); } + +rust::vec getDST20Tokens(std::size_t mnview_ptr) { + LOCK(cs_main); + + rust::vec tokens; + CCustomCSView* cache = reinterpret_cast(static_cast(mnview_ptr)); + cache->ForEachToken([&](DCT_ID const &id, CTokensView::CTokenImpl token) { + if (!token.IsDAT() || token.IsPoolShare()) + return true; + + tokens.push_back({id.v, token.name, token.symbol}); + return true; + }, DCT_ID{1}); // start from non-DFI + return tokens; +} diff --git a/src/ffi/ffiexports.h b/src/ffi/ffiexports.h index cb31ad4f0e..a19cfbf19d 100644 --- a/src/ffi/ffiexports.h +++ b/src/ffi/ffiexports.h @@ -23,6 +23,12 @@ struct Attributes { } }; +struct DST20Token { + uint64_t id; + rust::string name; + rust::string symbol; +}; + uint64_t getChainId(); bool isMining(); rust::string publishEthTransaction(rust::Vec rawTransaction); @@ -40,5 +46,6 @@ int getHighestBlock(); int getCurrentHeight(); Attributes getAttributeDefaults(); void CppLogPrintf(rust::string message); +rust::vec getDST20Tokens(std::size_t mnview_ptr); #endif // DEFI_FFI_FFIEXPORTS_H diff --git a/src/masternodes/errors.h b/src/masternodes/errors.h index 008eac67ec..4f50f30cf9 100644 --- a/src/masternodes/errors.h +++ b/src/masternodes/errors.h @@ -541,10 +541,6 @@ class DeFiErrors { static Res InvalidBlockNumberString(const std::string &number) { return Res::Err("Invalid block number: %s", number); } - - static Res DST20MigrationFailure(const std::string &reason) { - return Res::Err("Error migrating DST20 token: %s", reason); - } }; #endif // DEFI_MASTERNODES_ERRORS_H diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index 8887ee7b96..2dabf6b273 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -1819,11 +1819,11 @@ Res ATTRIBUTES::Validate(const CCustomCSView &view) const { GetValue(enabledKey, false) && evmQueueId && !evm_try_is_dst20_deployed_or_queued(result, evmQueueId, token->name, token->symbol, - tokenID.ToString())) { + tokenID.v)) { evm_try_create_dst20(result, evmQueueId, token->creationTx.GetByteArray(), token->name, token->symbol, - tokenID.ToString()); + tokenID.v); if (!result.ok) { return DeFiErrors::GovVarErrorCreatingDST20(result.reason.c_str()); diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 350e5c52d3..b63d02a88f 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1076,7 +1076,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { evm_try_create_dst20(result, evmQueueId, tx.GetHash().GetByteArray(), rust::string(tokenName.c_str()), rust::string(tokenSymbol.c_str()), - tokenId->ToString()); + tokenId->v); if (!result.ok) { return Res::Err("Error creating DST20 token: %s", result.reason); @@ -3892,7 +3892,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { Res operator()(const CTransferDomainMessage &obj) const { auto res = ValidateTransferDomain(tx, height, coins, mnview, consensus, obj, isEvmEnabledForBlock); if (!res) { return res; } - + auto attributes = mnview.GetAttributes(); auto stats = attributes->GetValue(CTransferDomainStatsLive::Key, CTransferDomainStatsLive{}); @@ -3925,7 +3925,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { else { CrossBoundaryResult result; evm_try_bridge_dst20(result, evmQueueId, HexStr(toAddress.begin(), toAddress.end()), - ArithToUint256(balanceIn).GetByteArray(), tx.GetHash().GetByteArray(), tokenId.ToString(), false); + ArithToUint256(balanceIn).GetByteArray(), tx.GetHash().GetByteArray(), tokenId.v, false); if (!result.ok) { return Res::Err("Error bridging DST20: %s", result.reason); @@ -3955,7 +3955,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { else { CrossBoundaryResult result; evm_try_bridge_dst20(result, evmQueueId, HexStr(fromAddress.begin(), fromAddress.end()), - ArithToUint256(balanceIn).GetByteArray(), tx.GetHash().GetByteArray(), tokenId.ToString(), true); + ArithToUint256(balanceIn).GetByteArray(), tx.GetHash().GetByteArray(), tokenId.v, true); if (!result.ok) { return Res::Err("Error bridging DST20: %s", result.reason); @@ -5445,4 +5445,4 @@ void TransferDomainConfig::SetToAttributesIfNotExists(ATTRIBUTES& attrs) const { if (!attrs.CheckKey(k.evm_to_dvm_auth_formats)) attrs.SetValue(k.evm_to_dvm_auth_formats, evmToDvmAuthFormats); if (!attrs.CheckKey(k.evm_to_dvm_native_enabled)) attrs.SetValue(k.evm_to_dvm_native_enabled, evmToDvmNativeTokenEnabled); if (!attrs.CheckKey(k.evm_to_dvm_dat_enabled)) attrs.SetValue(k.evm_to_dvm_dat_enabled, evmToDvmDatEnabled); -} \ No newline at end of file +} diff --git a/src/masternodes/validation.cpp b/src/masternodes/validation.cpp index 7475cdd426..feb326f340 100644 --- a/src/masternodes/validation.cpp +++ b/src/masternodes/validation.cpp @@ -2383,7 +2383,7 @@ static void RevertFailedTransferDomainTxs(const std::vector &failed static Res ValidateCoinbaseXVMOutput(const XVM &xvm, const FinalizeBlockCompletion &blockResult) { const auto blockResultBlockHash = uint256::FromByteArray(blockResult.block_hash); - + if (xvm.evm.blockHash != blockResultBlockHash) { return Res::Err("Incorrect EVM block hash in coinbase output"); } @@ -2440,7 +2440,7 @@ static Res ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCust if (!xvmRes) return std::move(xvmRes); CrossBoundaryResult result; - const auto blockResult = evm_unsafe_try_construct_block_in_q(result, evmQueueId, block.nBits, xvmRes->evm.beneficiary, block.GetBlockTime(), pindex->nHeight); + const auto blockResult = evm_unsafe_try_construct_block_in_q(result, evmQueueId, block.nBits, xvmRes->evm.beneficiary, block.GetBlockTime(), pindex->nHeight, static_cast(reinterpret_cast(&cache))); if (!result.ok) { return Res::Err(result.reason.c_str()); } @@ -2519,45 +2519,6 @@ static Res ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCust return Res::Ok(); } -Res ProcessDST20Migration(const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams& chainparams, const uint64_t evmQueueId) { - auto res = XResultValue(evm_unsafe_try_get_target_block_in_q(result, evmQueueId)); - if (!res.ok) return DeFiErrors::DST20MigrationFailure(res.msg); - - auto evmTargetBlock = *res; - if (evmTargetBlock > 0) return Res::Ok(); - - auto time = GetTimeMillis(); - LogPrintf("DST20 migration ...\n"); - - std::string errMsg = ""; - cache.ForEachToken([&](DCT_ID const &id, CTokensView::CTokenImpl token) { - if (!token.IsDAT() || token.IsPoolShare()) - return true; - - CrossBoundaryResult result; - auto alreadyExists = evm_try_is_dst20_deployed_or_queued(result, evmQueueId, token.name, token.symbol, id.ToString()); - if (!result.ok) { - errMsg = result.reason.c_str(); - return false; - } - - if (alreadyExists) { - return true; - } - - evm_try_create_dst20(result, evmQueueId, token.creationTx.GetByteArray(), token.name, token.symbol, id.ToString()); - if (!result.ok) { - errMsg = result.reason.c_str(); - return false; - } - - return true; - }, DCT_ID{1}); // start from non-DFI - - LogPrint(BCLog::BENCH, " - DST20 migration took: %dms\n", GetTimeMillis() - time); - return errMsg.empty() ? Res::Ok() : DeFiErrors::DST20MigrationFailure(errMsg); -} - static void FlushCacheCreateUndo(const CBlockIndex *pindex, CCustomCSView &mnview, CCustomCSView &cache, const uint256 hash) { // construct undo auto& flushable = cache.GetStorage(); @@ -2572,13 +2533,10 @@ static void FlushCacheCreateUndo(const CBlockIndex *pindex, CCustomCSView &mnvie Res ProcessDeFiEventFallible(const CBlock &block, const CBlockIndex *pindex, CCustomCSView &mnview, const CChainParams& chainparams, const uint64_t evmQueueId, const bool isEvmEnabledForBlock) { CCustomCSView cache(mnview); - - if (isEvmEnabledForBlock) { - auto res = ProcessDST20Migration(pindex, cache, chainparams, evmQueueId); - if (!res) return res; + if (isEvmEnabledForBlock) { // Process EVM block - res = ProcessEVMQueue(block, pindex, cache, chainparams, evmQueueId); + auto res = ProcessEVMQueue(block, pindex, cache, chainparams, evmQueueId); if (!res) return res; } diff --git a/src/masternodes/validation.h b/src/masternodes/validation.h index 0611453940..beb992b4f9 100644 --- a/src/masternodes/validation.h +++ b/src/masternodes/validation.h @@ -21,8 +21,6 @@ void ProcessDeFiEvent(const CBlock &block, const CBlockIndex* pindex, CCustomCSV Res ProcessDeFiEventFallible(const CBlock &block, const CBlockIndex *pindex, CCustomCSView &mnview, const CChainParams& chainparams, const uint64_t evmQueueId, const bool isEvmEnabledForBlock); -Res ProcessDST20Migration(const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams& chainparams, const uint64_t evmQueueId); - std::vector CollectAuctionBatches(const CVaultAssets& vaultAssets, const TAmounts& collBalances, const TAmounts& loanBalances); diff --git a/src/miner.cpp b/src/miner.cpp index a650744f60..47ce386b9b 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -286,15 +286,10 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc XVM xvm{}; if (isEvmEnabledForBlock) { - if (auto res = ProcessDST20Migration(pindexPrev, mnview, chainparams, evmQueueId); !res) { - LogPrintf("ThreadStaker: Failed to process DST20 migration: %s\n", res.msg); - return nullptr; - } - - auto res = XResultValueLogged(evm_unsafe_try_construct_block_in_q(result, evmQueueId, pos::GetNextWorkRequired(pindexPrev, pblock->nTime, consensus), evmBeneficiary, blockTime, nHeight)); + auto res = XResultValueLogged(evm_unsafe_try_construct_block_in_q(result, evmQueueId, pos::GetNextWorkRequired(pindexPrev, pblock->nTime, consensus), evmBeneficiary, blockTime, nHeight, static_cast(reinterpret_cast(&mnview)))); if (!res) { return nullptr; } auto blockResult = *res; - + auto r = XResultStatusLogged(evm_unsafe_try_remove_queue(result, evmQueueId)); if (!r) { return nullptr; } diff --git a/test/functional/feature_dst20.py b/test/functional/feature_dst20.py index cc7f3804ae..90adf89349 100755 --- a/test/functional/feature_dst20.py +++ b/test/functional/feature_dst20.py @@ -42,6 +42,139 @@ def set_test_params(self): ] ] + def test_dst20_migration_txs(self): + block_height = self.nodes[0].getblockcount() + + self.node.createtoken( + { + "symbol": "USDT", + "name": "USDT token", + "isDAT": True, + "collateralAddress": self.address, + } + ) + self.node.createtoken( + { + "symbol": "BTC", + "name": "BTC token", + "isDAT": True, + "collateralAddress": self.address, + } + ) + self.node.createtoken( + { + "symbol": "ETH", + "name": "ETH token", + "isDAT": True, + "collateralAddress": self.address, + } + ) + self.nodes[0].generate(1) + + # enable EVM, transferdomain, DVM to EVM transfers and EVM to DVM transfers + self.nodes[0].setgov( + { + "ATTRIBUTES": { + "v0/params/feature/evm": "true", + } + } + ) + self.nodes[0].generate(1) + + # Trigger EVM genesis DST20 migration + self.nodes[0].generate(1) + + # should have code on contract address + assert ( + self.nodes[0].w3.to_hex( + self.nodes[0].w3.eth.get_code(self.contract_address_btc) + ) + != "0x" + ) + assert ( + self.nodes[0].w3.to_hex( + self.nodes[0].w3.eth.get_code(self.contract_address_eth) + ) + != "0x" + ) + self.btc = self.nodes[0].w3.eth.contract( + address=self.contract_address_btc, abi=self.abi + ) + assert_equal(self.btc.functions.name().call(), "BTC token") + assert_equal(self.btc.functions.symbol().call(), "BTC") + + self.eth = self.nodes[0].w3.eth.contract( + address=self.contract_address_eth, abi=self.abi + ) + assert_equal(self.eth.functions.name().call(), "ETH token") + assert_equal(self.eth.functions.symbol().call(), "ETH") + + # Check that migration has associated EVM TX and receipt + block = self.nodes[0].eth_getBlockByNumber("latest") + all_tokens = self.nodes[0].listtokens() + # Keep only DAT non-DFI tokens + loanTokens = [token for token in all_tokens.values() if token['isDAT'] == True and token['symbol'] != 'DFI'] + assert_equal(len(block['transactions']), len(loanTokens)) + + # check USDT migration + usdt_tx = block['transactions'][0] + receipt = self.nodes[0].eth_getTransactionReceipt(usdt_tx) + tx1 = self.nodes[0].eth_getTransactionByHash(usdt_tx) + assert_equal(self.w0.to_checksum_address(receipt['contractAddress']), self.contract_address_usdt) + assert_equal(receipt['from'], tx1['from']) + assert_equal(receipt['gasUsed'], '0x0') + assert_equal(receipt['logs'], []) + assert_equal(receipt['status'], '0x1') + assert_equal(receipt['to'], None) + + assert_equal( + self.nodes[0].w3.to_hex( + self.nodes[0].w3.eth.get_code(self.contract_address_usdt) + ) + , tx1['input'] + ) + + # check BTC migration + btc_tx = block['transactions'][1] + receipt = self.nodes[0].eth_getTransactionReceipt(btc_tx) + tx2 = self.nodes[0].eth_getTransactionByHash(btc_tx) + assert_equal(self.w0.to_checksum_address(receipt['contractAddress']), self.contract_address_btc) + assert_equal(receipt['from'], tx2['from']) + assert_equal(receipt['gasUsed'], '0x0') + assert_equal(receipt['logs'], []) + assert_equal(receipt['status'], '0x1') + assert_equal(receipt['to'], None) + + assert_equal( + self.nodes[0].w3.to_hex( + self.nodes[0].w3.eth.get_code(self.contract_address_btc) + ) + , tx2['input'] + ) + + # check ETH migration + eth_tx = block['transactions'][2] + receipt = self.nodes[0].eth_getTransactionReceipt(eth_tx) + tx3 = self.nodes[0].eth_getTransactionByHash(eth_tx) + assert_equal(self.w0.to_checksum_address(receipt['contractAddress']), self.contract_address_eth) + assert_equal(receipt['from'], tx3['from']) + assert_equal(receipt['gasUsed'], '0x0') + assert_equal(receipt['logs'], []) + assert_equal(receipt['status'], '0x1') + assert_equal(receipt['to'], None) + + assert_equal( + self.nodes[0].w3.to_hex( + self.nodes[0].w3.eth.get_code(self.contract_address_eth) + ) + , tx3['input'] + ) + + assert_equal(tx1['input'], tx2['input']) + assert_equal(tx2['input'], tx3['input']) + + self.rollback_to(block_height) + def test_unused_dst20(self): # should have system reserved bytecode assert ( @@ -56,6 +189,7 @@ def test_pre_evm_token(self): assert ( self.nodes[0].w3.to_hex( self.nodes[0].w3.eth.get_code(self.contract_address_usdt) + ) != self.reserved_bytecode ) @@ -658,6 +792,11 @@ def run_test(self): # Generate chain self.node.generate(150) self.nodes[0].utxostoaccount({self.address: "1000@DFI"}) + self.nodes[0].generate(1) + + # Create token and check DST20 migration pre EVM activation + self.test_dst20_migration_txs() + # Create token before EVM self.node.createtoken( { @@ -689,6 +828,7 @@ def run_test(self): self.nodes[0].generate(2) self.test_pre_evm_token() + self.test_deploy_token() self.test_deploy_multiple_tokens() @@ -706,6 +846,5 @@ def run_test(self): self.test_different_tokens() self.test_loan_token() - if __name__ == "__main__": DST20().main()