diff --git a/lib/ain-evm/src/backend.rs b/lib/ain-evm/src/backend.rs index b007754785..0deddbd34d 100644 --- a/lib/ain-evm/src/backend.rs +++ b/lib/ain-evm/src/backend.rs @@ -201,6 +201,12 @@ impl EVMBackend { .unwrap_or_default() } + pub fn get_balance(&self, address: &H160) -> U256 { + self.get_account(address) + .map(|acc| acc.balance) + .unwrap_or_default() + } + pub fn get_contract_storage(&self, contract: H160, storage_index: &[u8]) -> Result { let Some(account) = self.get_account(&contract) else { return Ok(U256::zero()); diff --git a/lib/ain-evm/src/block.rs b/lib/ain-evm/src/block.rs index 0a2e804a8f..112e08013e 100644 --- a/lib/ain-evm/src/block.rs +++ b/lib/ain-evm/src/block.rs @@ -17,7 +17,7 @@ pub struct BlockService { } pub struct FeeHistoryData { - pub oldest_block: H256, + pub oldest_block: U256, pub base_fee_per_gas: Vec, pub gas_used_ratio: Vec, pub reward: Option>>, @@ -163,7 +163,7 @@ impl BlockService { let mut blocks = Vec::with_capacity(block_count); let mut block_number = first_block; - for _ in 0..=block_count { + for _ in 1..=block_count { let block = match self.storage.get_block_by_number(&block_number)? { None => Err(format_err!("Block {} out of range", block_number)), Some(block) => Ok(block), @@ -174,7 +174,7 @@ impl BlockService { block_number -= U256::one(); } - let oldest_block = blocks.last().unwrap().header.hash(); + let oldest_block = blocks.last().unwrap().header.number; let (mut base_fee_per_gas, mut gas_used_ratio): (Vec, Vec) = blocks .iter() @@ -236,7 +236,7 @@ impl BlockService { let mut data = Data::new(priority_fees); for pct in &priority_fee_percentile { - block_rewards.push(U256::from(data.percentile(*pct).ceil() as u64)); + block_rewards.push(U256::from(data.percentile(*pct).floor() as u64)); } reward.push(block_rewards); @@ -246,7 +246,25 @@ impl BlockService { Some(reward) }; + // add another entry for baseFeePerGas + let next_block_base_fee = match self + .storage + .get_block_by_number(&(first_block + U256::one()))? + { + None => { + // get one block earlier (this should exist) + let block = self + .storage + .get_block_by_number(&first_block)? + .ok_or_else(|| format_err!("Block {} out of range", first_block))?; + self.calculate_base_fee(block.header.hash())? + } + Some(block) => self.calculate_base_fee(block.header.hash())?, + }; + base_fee_per_gas.reverse(); + base_fee_per_gas.push(next_block_base_fee); + gas_used_ratio.reverse(); Ok(FeeHistoryData { diff --git a/lib/ain-evm/src/core.rs b/lib/ain-evm/src/core.rs index 962b411e4e..1a21e0c325 100644 --- a/lib/ain-evm/src/core.rs +++ b/lib/ain-evm/src/core.rs @@ -588,6 +588,11 @@ impl EVMCoreService { "[get_latest_block_backend] At block number : {:#x}, state_root : {:#x}", block_number, state_root ); + self.get_backend(state_root) + } + + pub fn get_backend(&self, state_root: H256) -> Result { + debug!("[get_backend] State_root : {:#x}", state_root); EVMBackend::from_root( state_root, Arc::clone(&self.trie_store), @@ -595,4 +600,11 @@ impl EVMCoreService { Vicinity::default(), ) } + + pub fn get_balance_at_state_root(&self, address: H160, state_root: H256) -> Result { + let balance = self.get_backend(state_root)?.get_balance(&address); + + debug!("Account {:x?} balance {:x?}", address, balance); + Ok(balance) + } } diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index e8c6ba9539..29714e7c07 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -51,6 +51,7 @@ pub struct FinalizedBlockInfo { pub total_burnt_fees: U256, pub total_priority_fees: U256, pub block_number: U256, + pub state_root: XHash, } pub type ReceiptAndOptionalContractAddress = (ReceiptV3, Option); @@ -487,6 +488,7 @@ impl EVMServices { block.header.hash(), block.header.number, ); + let new_state_root = format!("{:?}", block.header.state_root); queue.block_data = Some(BlockData { block, receipts }); Ok(FinalizedBlockInfo { @@ -495,6 +497,7 @@ impl EVMServices { total_burnt_fees, total_priority_fees, block_number: current_block_number, + state_root: new_state_root, }) } @@ -620,6 +623,19 @@ impl EVMServices { Ok(nonce) } + pub fn get_dst20_total_supply(&self, token_id: u64, state_root: Option) -> Result { + let address = ain_contracts::dst20_address_from_token_id(token_id)?; + debug!("[get_dst20_total_supply] Fetching address {:#?}", address); + + let backend = match state_root { + Some(state_root) => self.core.get_backend(state_root), + None => self.core.get_latest_block_backend(), + }?; + + let total_supply_index = H256::from_low_u64_be(2); + backend.get_contract_storage(address, total_supply_index.as_bytes()) + } + pub fn reserve_dst20_namespace(&self, executor: &mut AinExecutor) -> Result<()> { let Contract { bytecode, .. } = get_reserved_contract(); let addresses = (1..=1024) diff --git a/lib/ain-grpc/src/block.rs b/lib/ain-grpc/src/block.rs index 73a908354c..5f7fe04d5b 100644 --- a/lib/ain-grpc/src/block.rs +++ b/lib/ain-grpc/src/block.rs @@ -337,7 +337,7 @@ impl Serialize for BlockTransactions { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct RpcFeeHistory { - pub oldest_block: H256, + pub oldest_block: U256, pub base_fee_per_gas: Vec, pub gas_used_ratio: Vec, pub reward: Option>>, diff --git a/lib/ain-rs-exports/src/evm.rs b/lib/ain-rs-exports/src/evm.rs index d676632fc2..54bf9412cf 100644 --- a/lib/ain-rs-exports/src/evm.rs +++ b/lib/ain-rs-exports/src/evm.rs @@ -274,6 +274,46 @@ pub fn evm_try_get_balance(result: &mut ffi::CrossBoundaryResult, address: &str) } } +/// Retrieves the balance of an EVM account at state root. +/// +/// # Arguments +/// +/// * `address` - The EVM address of the account. +/// * `state_root` - The state root from which to restore temporary trie database. +/// +/// # Errors +/// +/// Returns an Error if the address is not a valid EVM address. +/// +/// # Returns +/// +/// Returns the balance of the account as a `u64` on success. +pub fn evm_try_get_balance_at_state_root( + result: &mut ffi::CrossBoundaryResult, + address: &str, + state_root: &str, +) -> u64 { + let Ok(address) = address.parse() else { + return cross_boundary_error_return(result, "Invalid address"); + }; + let Ok(state_root) = state_root.parse() else { + return cross_boundary_error_return(result, "Invalid state root"); + }; + + match SERVICES + .evm + .core + .get_balance_at_state_root(address, state_root) + { + Err(e) => cross_boundary_error_return(result, e.to_string()), + Ok(balance) => { + let amount = WeiAmount(balance).to_satoshi().try_into(); + + try_cross_boundary_return(result, amount) + } + } +} + /// Retrieves the next valid nonce of an EVM account in a specific queue_id /// /// # Arguments @@ -647,6 +687,7 @@ pub fn evm_unsafe_try_construct_block_in_q( total_burnt_fees, total_priority_fees, block_number, + state_root, }) => { let Ok(total_burnt_fees) = u64::try_from(WeiAmount(total_burnt_fees).to_satoshi()) else { @@ -667,6 +708,7 @@ pub fn evm_unsafe_try_construct_block_in_q( total_burnt_fees, total_priority_fees, block_number: block_number.as_u64(), + state_root, } } Err(e) => cross_boundary_error_return(result, e.to_string()), @@ -826,6 +868,30 @@ pub fn evm_try_is_dst20_deployed_or_queued( } } +pub fn evm_try_get_dst20_total_supply( + result: &mut ffi::CrossBoundaryResult, + token_id: u64, + state_root: &str, +) -> u64 { + let state_root = match state_root { + "" => None, + _ => match state_root.parse() { + Ok(state_root) => Some(state_root), + Err(_) => return cross_boundary_error_return(result, "Invalid state root"), + }, + }; + + match SERVICES.evm.get_dst20_total_supply(token_id, state_root) { + Ok(total_supply) => { + let Ok(total_supply) = u64::try_from(WeiAmount(total_supply).to_satoshi()) else { + return cross_boundary_error_return(result, "Total supply value overflow"); + }; + cross_boundary_success_return(result, total_supply) + } + Err(e) => cross_boundary_error_return(result, e.to_string()), + } +} + pub fn evm_try_get_tx_by_hash( result: &mut ffi::CrossBoundaryResult, tx_hash: &str, @@ -1014,7 +1080,7 @@ mod tests { #[test] fn test_hash_type_string() { use primitive_types::H160; - let num = 0b11010111_11010111_11010111_11010111_11010111_11010111_11010111_11010111; + let num = 0b1101_0111_1101_0111_1101_0111_1101_0111_1101_0111_1101_0111_1101_0111_1101_0111; let num_h160 = H160::from_low_u64_be(num); let num_h160_string = format!("{:?}", num_h160); println!("{}", num_h160_string); diff --git a/lib/ain-rs-exports/src/lib.rs b/lib/ain-rs-exports/src/lib.rs index 737b18ea7c..0701339666 100644 --- a/lib/ain-rs-exports/src/lib.rs +++ b/lib/ain-rs-exports/src/lib.rs @@ -93,6 +93,7 @@ pub mod ffi { pub total_burnt_fees: u64, pub total_priority_fees: u64, pub block_number: u64, + pub state_root: String, } #[derive(Default)] @@ -110,6 +111,12 @@ pub mod ffi { // If they are fallible, it's a TODO to changed and move later // so errors are propogated up properly. fn evm_try_get_balance(result: &mut CrossBoundaryResult, address: &str) -> u64; + fn evm_try_get_balance_at_state_root( + result: &mut CrossBoundaryResult, + address: &str, + state_root: &str, + ) -> u64; + fn evm_unsafe_try_create_queue(result: &mut CrossBoundaryResult) -> u64; fn evm_unsafe_try_remove_queue(result: &mut CrossBoundaryResult, queue_id: u64); fn evm_try_disconnect_latest_block(result: &mut CrossBoundaryResult); @@ -222,5 +229,11 @@ pub mod ffi { result: &mut CrossBoundaryResult, queue_id: u64, ) -> u64; + + fn evm_try_get_dst20_total_supply( + result: &mut CrossBoundaryResult, + token_id: u64, + state_root: &str, + ) -> u64; } } diff --git a/src/masternodes/errors.h b/src/masternodes/errors.h index 4f50f30cf9..16a32f00f4 100644 --- a/src/masternodes/errors.h +++ b/src/masternodes/errors.h @@ -518,6 +518,30 @@ class DeFiErrors { return Res::Err("TransferDomain currently only supports a single transfer per transaction"); } + static Res AccountingMoreDVMInThanOut(const std::string &token, const std::string &amount) { + return Res::Err("More token #%s moved in DVM from EVM than out. DVM domain balance (should be negative or zero): %s", token, amount); + } + + static Res AccountingMoreEVMOurThanIn(const std::string &token, const std::string &amount) { + return Res::Err("More token #%s moved in DVM from EVM than out. DVM domain balance (should be negative or zero): %s", token, amount); + } + + static Res AccountingMissmatch(const std::string &direction, const std::string &oldBalance, const std::string &newBalance, const std::string &accounting) { + return Res::Err("Accounting mistmatch %s - Old: %s New: %s Accounting: %s", direction, oldBalance, newBalance, accounting); + } + + static Res AccountingMissmatchEVM(const std::string &address, const CAmount oldBalance, const CAmount newBalance, const CAmount currentBalance) { + return Res::Err("Accounting mistmatch on EVM side for DFI token on address %s: Old: %lld New: %lld Current: %lld", address, oldBalance, newBalance, currentBalance); + } + + static Res AccountingMissmatchEVMDST20(const std::string &token, const CAmount oldBalance, const CAmount newBalance, const CAmount currentBalance) { + return Res::Err("Accounting mistmatch on EVM side for DST20 total supply on token #%s: Old: %lld New: %lld Current: %lld", token, oldBalance, newBalance, currentBalance); + } + + static Res InvalidEVMAddressBalance() { + return Res::Err("Unable to read EVM address balance"); + } + static Res SettingEVMAttributeFailure() { return Res::Err("Failed to set EVM attribute"); } diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index a0387ab3aa..97c60bb583 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -25,6 +25,19 @@ enum class EVMAttributesTypes : uint32_t { extern UniValue AmountsToJSON(const TAmounts &diffs, AmountFormat format = AmountFormat::Symbol); +UniValue CTransferDomainStatsLive::ToUniValue() const { + UniValue obj(UniValue::VOBJ); + obj.pushKV("dvmIn", AmountsToJSON(dvmIn.balances)); + obj.pushKV("dvmOut", AmountsToJSON(dvmOut.balances)); + obj.pushKV("dvmCurrent", AmountsToJSON(dvmCurrent.balances)); + obj.pushKV("evmIn", AmountsToJSON(evmIn.balances)); + obj.pushKV("evmOut", AmountsToJSON(evmOut.balances)); + obj.pushKV("evmOut", AmountsToJSON(evmCurrent.balances)); + obj.pushKV("dvmEvm", AmountsToJSON(dvmEvmTotal.balances)); + obj.pushKV("evmDvm", AmountsToJSON(evmDvmTotal.balances)); + return obj; +} + static inline std::string trim_all_ws(std::string s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 1538af1b77..75c1c5d432 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -306,9 +306,27 @@ struct CTransferDomainStatsLive { READWRITE(evmCurrent); } + UniValue ToUniValue() const; + static constexpr CDataStructureV0 Key = {AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::TransferDomainStatsLive}; }; +struct CEVMInitialState +{ + CTransferDomainStatsLive transferDomainState; + std::map evmBalances; + CStatsTokenBalances dst20EvmTotalSupply; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream &s, Operation ser_action) { + READWRITE(transferDomainState); + READWRITE(evmBalances); + READWRITE(dst20EvmTotalSupply); + } +}; + struct CConsortiumMember { static const uint16_t MAX_CONSORTIUM_MEMBERS_STRING_LENGTH = 512; static const uint16_t MIN_CONSORTIUM_MEMBERS_STRING_LENGTH = 3; diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index b230aad03a..6d55277406 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3909,7 +3909,6 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { } } else { - CrossBoundaryResult result; evm_try_bridge_dst20(result, evmQueueId, HexStr(dst.data), tx.GetHash().GetHex(), tokenId.v, true); if (!result.ok) { return Res::Err("Error bridging DST20: %s", result.reason); @@ -3924,8 +3923,8 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { ExtractDestination(src.address, dest); auto tokenId = dst.amount.nTokenId; + CrossBoundaryResult result; if (tokenId == DCT_ID{0}) { - CrossBoundaryResult result; if (!evm_unsafe_try_sub_balance_in_q(result, evmQueueId, HexStr(dst.data), tx.GetHash().GetHex())) { return DeFiErrors::TransferDomainNotEnoughBalance(EncodeDestination(dest)); } @@ -3934,7 +3933,6 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { } } else { - CrossBoundaryResult result; evm_try_bridge_dst20(result, evmQueueId, HexStr(dst.data), tx.GetHash().GetHex(), tokenId.v, false); if (!result.ok) { return Res::Err("Error bridging DST20: %s", result.reason); diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index bc1d361ba5..0815c81493 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -430,7 +430,7 @@ UniValue getaccount(const JSONRPCRequest& request) { if (auto res = GetRPCResultCache().TryGet(request)) return *res; // decode owner - const auto reqOwner = DecodeScript(request.params[0].get_str()); + const auto reqOwner = GetScriptForDestination(DecodeDestination(request.params[0].get_str())); // parse pagination size_t limit = 100; @@ -2057,8 +2057,12 @@ UniValue transferdomain(const JSONRPCRequest& request) { CTransferDomainItem src, dst; - if (!srcObj["address"].isNull()) - src.address = DecodeScript(srcObj["address"].getValStr()); + if (!srcObj["address"].isNull()) { + const auto dest = DecodeDestination(srcObj["address"].getValStr()); + if (!IsValidDestination(dest)) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid src address provided"); + src.address = GetScriptForDestination(dest); + } else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, src argument \"address\" must not be null"); @@ -2089,8 +2093,12 @@ UniValue transferdomain(const JSONRPCRequest& request) { // if (!srcObj["data"].isNull()) // src.data.assign(srcObj["data"].getValStr().begin(), srcObj["data"].getValStr().end()); - if (!dstObj["address"].isNull()) - dst.address = DecodeScript(dstObj["address"].getValStr()); + if (!dstObj["address"].isNull()) { + const auto dest = DecodeDestination(dstObj["address"].getValStr()); + if (!IsValidDestination(dest)) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid dst address provided"); + dst.address = GetScriptForDestination(dest); + } else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, dst argument \"address\" must not be null"); diff --git a/src/masternodes/validation.cpp b/src/masternodes/validation.cpp index 4a50c92ba4..172f29caff 100644 --- a/src/masternodes/validation.cpp +++ b/src/masternodes/validation.cpp @@ -25,6 +25,8 @@ #define MILLI 0.001 +extern UniValue AmountsToJSON(const TAmounts &diffs, AmountFormat format = AmountFormat::Symbol); + template static void UpdateDailyGovVariables(const std::map::const_iterator& incentivePair, CCustomCSView& cache, int nHeight) { if (incentivePair != Params().GetConsensus().blockTokenRewards.end()) @@ -2384,7 +2386,7 @@ static void RevertFailedTransferDomainTxs(const std::vector &failed static Res ValidateCoinbaseXVMOutput(const XVM &xvm, const FinalizeBlockCompletion &blockResult) { const auto blockResultBlockHash = std::string(blockResult.block_hash.data(), blockResult.block_hash.length()).substr(2); - + if (xvm.evm.blockHash != blockResultBlockHash) { return Res::Err("Incorrect EVM block hash in coinbase output"); } @@ -2400,7 +2402,216 @@ static Res ValidateCoinbaseXVMOutput(const XVM &xvm, const FinalizeBlockCompleti return Res::Ok(); } -static Res ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams& chainparams, const uint64_t evmQueueId) { +CTransferDomainMessage DecodeTransferDomainMessage(const CTransactionRef& tx, const CBlockIndex* pindex, const CChainParams& chainparams) { + std::vector metadata; + CustomTxType txType = GuessCustomTxType(*tx, metadata); + if (txType == CustomTxType::TransferDomain) { + auto txMessage = customTypeToMessage(txType); + if (CustomMetadataParse(pindex->nHeight, chainparams.GetConsensus(), metadata, txMessage)) + return *(std::get_if(&txMessage)); + } + return CTransferDomainMessage{}; +} + +ResVal GetEVMBlockCount() { + auto result = XResultValue(evm_try_get_block_count(result)); + if (!result) { + return result; + } + + return {static_cast(*result), Res::Ok()}; +} + +ResVal GetEvmDST20TotalSupply(const DCT_ID& id, const std::string &stateRoot = std::string()) { + auto result = XResultValue(evm_try_get_dst20_total_supply(result, id.v, stateRoot)); + if (!result) { + return result; + } + + return {static_cast(*result), Res::Ok()}; +} + +ResVal GetEvmDFIBalance(const CScript& address, const std::string &stateRoot = std::string()) { + CTxDestination dest; + if (ExtractDestination(address, dest) && dest.index() == WitV16KeyEthHashType) { + const auto keyID = std::get(dest); + auto result = XResultValue(stateRoot.empty() ? evm_try_get_balance(result, keyID.ToHexString()) : evm_try_get_balance_at_state_root(result, keyID.ToHexString(), stateRoot)); + if (!result) { + return result; + } + + return {static_cast(*result), Res::Ok()}; + } + + return DeFiErrors::InvalidEVMAddressBalance(); +} + +Res ProcessAccountingStateBeforeBlock(const CBlock &block, const CBlockIndex* pindex, CCustomCSView &mnview, const CChainParams& chainparams, CEVMInitialState& evmInitialState) { + auto evmBlockCount = GetEVMBlockCount(); + if (!evmBlockCount || !*evmBlockCount) + return Res::Ok(); + + std::map &evmBalances = evmInitialState.evmBalances; + CStatsTokenBalances& dst20EvmTotalSupply = evmInitialState.dst20EvmTotalSupply; + + // Storing inital EVM address balances + for (const auto &tx : block.vtx) { + auto txMessage = DecodeTransferDomainMessage(tx, pindex, chainparams); + for (auto const &[src, dst]: txMessage.transfers){ + if (src.amount.nTokenId == DCT_ID{0}) { + std::vector> balanceList{ + { src.domain, VMDomain::EVM, src.address, src.amount, evmBalances.find(src.address) == evmBalances.end() }, + { dst.domain, VMDomain::EVM, dst.address, src.amount, evmBalances.find(dst.address) == evmBalances.end() }, + }; + for (auto [domain, domainTarget, address, amount, condition]: balanceList) { + if (domain == static_cast(domainTarget) && condition) { + auto balance = GetEvmDFIBalance(address); + if (!balance) + return balance; + + evmBalances[address].Add({amount.nTokenId, *balance}); + } + } + } + } + } + + auto res = Res::Ok(); + // Storing inital EVM DST20 total supply + mnview.ForEachToken([&](DCT_ID const& id, CTokenImplementation token) { + auto supply = GetEvmDST20TotalSupply(id); + if (!supply) { + res = supply; + return false; + } + dst20EvmTotalSupply.Add({id, *supply}); + + return true; + }); + + return res; +} + +static Res ProcessAccountingConsensusChecks(const CBlock &block, const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams, CEVMInitialState& evmInitialState, const CEvmBlockStatsLive& evmStats, const std::string &stateRoot) { + auto evmBlockCount = GetEVMBlockCount(); + if (!evmBlockCount || !*evmBlockCount) + return Res::Ok(); + + auto attributes = cache.GetAttributes(); + assert(attributes); + + CStatsTokenBalances totalBlockDVMOut, totalBlockEVMOut, totalBlockDVMIn, totalBlockEVMIn; + std::map deltaEvmBalances; + CStatsTokenBalances deltaDST20EvmTotalSupply; + + // Calculating delta balances and supply + for (const auto &tx : block.vtx) { + auto txMessage = DecodeTransferDomainMessage(tx, pindex, chainparams); + for (auto const &[src, dst]: txMessage.transfers){ + std::vector> statsList{ + { src.domain, src.amount, VMDomain::DVM, totalBlockDVMOut }, + { dst.domain, dst.amount, VMDomain::DVM, totalBlockDVMIn }, + { src.domain, src.amount, VMDomain::EVM, totalBlockEVMOut }, + { dst.domain, dst.amount, VMDomain::EVM, totalBlockEVMIn }, + }; + for (auto [domain, amount, domainTarget, targetStat]: statsList) { + if (domain == static_cast(domainTarget)) { + targetStat.Add(amount); + } + } + + if (src.amount.nTokenId == DCT_ID{0}) { + if (src.domain == static_cast(VMDomain::EVM)) { + deltaEvmBalances[src.address].Sub(src.amount); + } else if (dst.domain == static_cast(VMDomain::EVM)) + deltaEvmBalances[dst.address].Add(dst.amount); + } else { + if (src.domain == static_cast(VMDomain::EVM)) { + deltaDST20EvmTotalSupply.Sub({src.amount.nTokenId, src.amount.nValue}); + } else if (dst.domain == static_cast(VMDomain::EVM)) { + deltaDST20EvmTotalSupply.Add({dst.amount.nTokenId, dst.amount.nValue}); + } + } + } + } + + auto transferDomainStats = attributes->GetValue(CTransferDomainStatsLive::Key, CTransferDomainStatsLive{}); + + // Consensus check of sum balance in/out for both domains + // DVM + for (const auto &[id, current] : transferDomainStats.dvmCurrent.balances) { + // Must be less or equal zero + // Only for DFI token - dvmOut must be greater or equal to dvmIn + fees_burnt + fees_priority, dvmCurrent = dvmIn - dvmOut + if ((id.v == 0 && current + evmStats.feeBurnt + evmStats.feePriority > 0) || (id.v !=0 && current > 0)) + return DeFiErrors::AccountingMoreDVMInThanOut(id.ToString(), GetDecimalString(current)); + } + // EVM + for (const auto &[id, current] : transferDomainStats.evmCurrent.balances) { + // Must be greater or equal to zero - evmOut must be less then evmIn, evmCurrent = evmIn - evmOut + if (current < 0) + return DeFiErrors::AccountingMoreEVMOurThanIn(id.ToString(), GetDecimalString(current)); + } + + auto totalBlockDVMCurrent = totalBlockDVMIn; + totalBlockDVMCurrent.SubBalances(totalBlockDVMOut.balances); + auto totalBlockEVMCurrent = totalBlockEVMIn; + totalBlockEVMCurrent.SubBalances(totalBlockEVMOut.balances); + std::vector> statsList{ + { evmInitialState.transferDomainState.dvmOut, totalBlockDVMOut, transferDomainStats.dvmOut, "dvmOut"}, + { evmInitialState.transferDomainState.dvmIn, totalBlockDVMIn, transferDomainStats.dvmIn, "dvmIn" }, + { evmInitialState.transferDomainState.dvmCurrent, totalBlockDVMCurrent, transferDomainStats.dvmCurrent, "dvmCurrent" }, + { evmInitialState.transferDomainState.evmOut, totalBlockEVMOut, transferDomainStats.evmOut, "evmOut" }, + { evmInitialState.transferDomainState.evmIn, totalBlockEVMIn, transferDomainStats.evmIn, "evmIn" }, + { evmInitialState.transferDomainState.evmCurrent, totalBlockEVMCurrent, transferDomainStats.evmCurrent, "evmCurrent" }, + }; + + // Transfer accounting tally + for (auto [oldState, totalBlock, stats, direction]: statsList) { + auto newState = oldState; + newState.AddBalances(totalBlock.balances); + if (newState != stats) + return DeFiErrors::AccountingMissmatch(direction, evmInitialState.transferDomainState.ToUniValue().write(), newState.ToString(), transferDomainStats.ToUniValue().write()); + } + + // DFI token EVM address balance tally + /* TODO skipping for now as this fails when having an EVM and transfer domain to the same address in the same block. + for (auto &[address, delta]: deltaEvmBalances) + { + auto DFIToken = DCT_ID{0}; + auto oldBalance = (evmInitialState.evmBalances.find(address) != evmInitialState.evmBalances.end()) ? evmInitialState.evmBalances[address] : CStatsTokenBalances{}; + auto newBalance = oldBalance; + auto balance = GetEvmDFIBalance(address,stateRoot); + if (!balance) + return balance; + + newBalance.AddBalances(delta.balances); + if (newBalance.balances[DFIToken] != *balance) + return DeFiErrors::AccountingMissmatchEVM(ScriptToString(address), oldBalance.balances[DFIToken], newBalance.balances[DFIToken], *balance); + } + */ + + // DST20 token EVM totaly supply tally + deltaDST20EvmTotalSupply.AddBalances(evmInitialState.dst20EvmTotalSupply.balances); + auto res = Res::Ok(); + cache.ForEachToken([&](DCT_ID const& id, CTokenImplementation token) { + auto supply = GetEvmDST20TotalSupply(id, stateRoot); + if (!supply) { + res = supply; + return false; + } + + if (deltaDST20EvmTotalSupply.balances[id] != *supply || deltaDST20EvmTotalSupply.balances[id] < 0) { + res = DeFiErrors::AccountingMissmatchEVMDST20(id.ToString(), evmInitialState.dst20EvmTotalSupply.balances[id], deltaDST20EvmTotalSupply.balances[id], *supply); + return false; + } + + return true; + }); + + return res; +} + +static Res ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams& chainparams, const uint64_t evmQueueId, CEVMInitialState& evmInitialState) { CKeyID minter; assert(block.ExtractMinterKey(minter)); CScript minerAddress; @@ -2499,18 +2710,10 @@ static Res ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCust stats.feePriorityMaxHash = block.GetHash(); } - auto transferDomainStats = attributes->GetValue(CTransferDomainStatsLive::Key, CTransferDomainStatsLive{}); - - for (const auto &[id, amount] : transferDomainStats.dvmCurrent.balances) { - if (id.v == 0) { - if (amount + stats.feeBurnt + stats.feePriority > 0) { - return Res::Err("More DFI moved from DVM to EVM than in. DVM Out: %s Fees: %s Total: %s\n", GetDecimalString(amount), - GetDecimalString(stats.feeBurnt + stats.feePriority), GetDecimalString(amount + stats.feeBurnt + stats.feePriority)); - } - } else if (amount > 0) { - return Res::Err("More %s moved from DVM to EVM than in. DVM Out: %s\n", id.ToString(), GetDecimalString(amount)); - } - } + // Process transferdomain events + auto evmStateRoot = std::string(blockResult.state_root.data(), blockResult.state_root.length()); + res = ProcessAccountingConsensusChecks(block, pindex, cache, chainparams, evmInitialState, stats, evmStateRoot); + if (!res) return res; attributes->SetValue(CEvmBlockStatsLive::Key, stats); cache.SetVariable(*attributes); @@ -2530,12 +2733,12 @@ 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) { +Res ProcessDeFiEventFallible(const CBlock &block, const CBlockIndex *pindex, CCustomCSView &mnview, const CChainParams& chainparams, const uint64_t evmQueueId, const bool isEvmEnabledForBlock, CEVMInitialState& evmInitialState) { CCustomCSView cache(mnview); if (isEvmEnabledForBlock) { // Process EVM block - auto res = ProcessEVMQueue(block, pindex, cache, chainparams, evmQueueId); + auto res = ProcessEVMQueue(block, pindex, cache, chainparams, evmQueueId, evmInitialState); if (!res) return res; } diff --git a/src/masternodes/validation.h b/src/masternodes/validation.h index beb992b4f9..775f63be11 100644 --- a/src/masternodes/validation.h +++ b/src/masternodes/validation.h @@ -19,7 +19,9 @@ using CreationTxs = std::map CollectAuctionBatches(const CVaultAssets& vaultAssets, const TAmounts& collBalances, const TAmounts& loanBalances); diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 0d03b0ccbd..505c32544b 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -67,7 +67,9 @@ ResVal GuessTokenAmount(interfaces::Chain const & chain, std::stri } -// decodes either base58/bech32 address, or a hex format +// Decodes either base58/bech32 address, or a hex format. Legacy function, should +// use DecodeScript(DecodeDestination(str)) to ensure proper decoding of address +// to script to include erc55 address support. CScript DecodeScript(std::string const& str) { if (IsHex(str)) { diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index bbe15e0712..742c994981 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -19,17 +20,31 @@ static const std::string strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHf static const std::string strSecret2 = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"; static const std::string strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; static const std::string strSecret2C = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"; -static const std::string addr1 = "8eLhZJqPrKuFmBonk7tK3Tma6oyRvJM4Tz"; -static const std::string addr2 = "8VApoBSS8rRKiRpSchh5JjYDNLrvyXEYgJ"; -static const std::string addr1C = "8ctAamF4jdX6NzoTk5So1qXUBc4CxovyK9"; -static const std::string addr2C = "8SWakFLXnSsHi5g5mxtSzbsr1T68JmXMdR"; + +// public key hash addresses +static const std::string pkh_addr1 = "8eLhZJqPrKuFmBonk7tK3Tma6oyRvJM4Tz"; +static const std::string pkh_addr2 = "8VApoBSS8rRKiRpSchh5JjYDNLrvyXEYgJ"; +static const std::string pkh_addr1C = "8ctAamF4jdX6NzoTk5So1qXUBc4CxovyK9"; +static const std::string pkh_addr2C = "8SWakFLXnSsHi5g5mxtSzbsr1T68JmXMdR"; + +// witness public key hash addresses +static const std::string wpkh_addr1 = "df1qluvhk989q245ruau3n95339t4j02kddu2vqwve"; +static const std::string wpkh_addr2 = "df1qn2prk6v0w78vay9sjnwr7y4gra0rcv69f5qxqz"; +static const std::string wpkh_addr1C = "df1qauw2aajwu832l7rhkl5wjufacfdj9z0jquwv3z"; +static const std::string wpkh_addr2C = "df1q04t8rax7tc7s2jzeuphjpyvuc0vgygsz3drcsg"; + +// erc55 addresses +static const std::string erc55_addr1 = "0x482e975Ee029d6d268CC1dCce529748a06A46AAc"; +static const std::string erc55_addr2 = "0x43162a466BD5891dfBf7c438b0c35F0144690D26"; +static const std::string erc55_addr1C = "0x2D586e4Dec0798F728b626a4f134a3728772a8E5"; +static const std::string erc55_addr2C = "0x83bB997178Cd7F6876620096EFADB18a712eDdca"; static const std::string strAddressBad = "1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"; BOOST_FIXTURE_TEST_SUITE(key_tests, BasicTestingSetup) -BOOST_AUTO_TEST_CASE(key_test1) +BOOST_AUTO_TEST_CASE(key_test_1) { CKey key1 = DecodeSecret(strSecret1); BOOST_CHECK(key1.IsValid() && !key1.IsCompressed()); @@ -67,10 +82,10 @@ BOOST_AUTO_TEST_CASE(key_test1) BOOST_CHECK(!key2C.VerifyPubKey(pubkey2)); BOOST_CHECK(key2C.VerifyPubKey(pubkey2C)); - BOOST_CHECK(DecodeDestination(addr1) == CTxDestination(PKHash(pubkey1))); - BOOST_CHECK(DecodeDestination(addr2) == CTxDestination(PKHash(pubkey2))); - BOOST_CHECK(DecodeDestination(addr1C) == CTxDestination(PKHash(pubkey1C))); - BOOST_CHECK(DecodeDestination(addr2C) == CTxDestination(PKHash(pubkey2C))); + BOOST_CHECK(DecodeDestination(pkh_addr1) == CTxDestination(PKHash(pubkey1))); + BOOST_CHECK(DecodeDestination(pkh_addr2) == CTxDestination(PKHash(pubkey2))); + BOOST_CHECK(DecodeDestination(pkh_addr1C) == CTxDestination(PKHash(pubkey1C))); + BOOST_CHECK(DecodeDestination(pkh_addr2C) == CTxDestination(PKHash(pubkey2C))); for (int n=0; n<16; n++) { @@ -219,4 +234,248 @@ BOOST_AUTO_TEST_CASE(key_key_negation) BOOST_CHECK(key.GetPubKey().data()[0] == 0x03); } +BOOST_AUTO_TEST_CASE(pkh_key_test) +{ + CKey key1 = DecodeSecret(strSecret1); + BOOST_CHECK(key1.IsValid() && !key1.IsCompressed()); + CKey key2 = DecodeSecret(strSecret2); + BOOST_CHECK(key2.IsValid() && !key2.IsCompressed()); + CKey key1C = DecodeSecret(strSecret1C); + BOOST_CHECK(key1C.IsValid() && key1C.IsCompressed()); + CKey key2C = DecodeSecret(strSecret2C); + BOOST_CHECK(key2C.IsValid() && key2C.IsCompressed()); + CKey bad_key = DecodeSecret(strAddressBad); + BOOST_CHECK(!bad_key.IsValid()); + + CPubKey pubkey1 = key1. GetPubKey(); + CPubKey pubkey2 = key2. GetPubKey(); + CPubKey pubkey1C = key1C.GetPubKey(); + CPubKey pubkey2C = key2C.GetPubKey(); + + BOOST_CHECK(key1.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key1C.VerifyPubKey(pubkey1)); + BOOST_CHECK(key1C.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key1C.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key1C.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key2.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key2.VerifyPubKey(pubkey1C)); + BOOST_CHECK(key2.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key2.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key2C.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key2C.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key2C.VerifyPubKey(pubkey2)); + BOOST_CHECK(key2C.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(DecodeDestination(pkh_addr1) == CTxDestination(PKHash(pubkey1))); + BOOST_CHECK(DecodeDestination(pkh_addr2) == CTxDestination(PKHash(pubkey2))); + BOOST_CHECK(DecodeDestination(pkh_addr1C) == CTxDestination(PKHash(pubkey1C))); + BOOST_CHECK(DecodeDestination(pkh_addr2C) == CTxDestination(PKHash(pubkey2C))); + + BOOST_CHECK(pkh_addr1 == EncodeDestination(CTxDestination(PKHash(pubkey1)))); + BOOST_CHECK(pkh_addr2 == EncodeDestination(CTxDestination(PKHash(pubkey2)))); + BOOST_CHECK(pkh_addr1C == EncodeDestination(CTxDestination(PKHash(pubkey1C)))); + BOOST_CHECK(pkh_addr2C == EncodeDestination(CTxDestination(PKHash(pubkey2C)))); + + // Test script to destination conversions + CScript pkh_addr1_script = GetScriptForDestination(DecodeDestination(pkh_addr1)); + CScript pkh_addr2_script = GetScriptForDestination(DecodeDestination(pkh_addr2)); + CScript pkh_addr1C_script = GetScriptForDestination(DecodeDestination(pkh_addr1C)); + CScript pkh_addr2C_script = GetScriptForDestination(DecodeDestination(pkh_addr2C)); + + CTxDestination pkh_addr1_script_dest; + CTxDestination pkh_addr2_script_dest; + CTxDestination pkh_addr1C_script_dest; + CTxDestination pkh_addr2C_script_dest; + ExtractDestination(pkh_addr1_script, pkh_addr1_script_dest); + ExtractDestination(pkh_addr2_script, pkh_addr2_script_dest); + ExtractDestination(pkh_addr1C_script, pkh_addr1C_script_dest); + ExtractDestination(pkh_addr2C_script, pkh_addr2C_script_dest); + + BOOST_CHECK(pkh_addr1 == EncodeDestination(pkh_addr1_script_dest)); + BOOST_CHECK(pkh_addr2 == EncodeDestination(pkh_addr2_script_dest)); + BOOST_CHECK(pkh_addr1C == EncodeDestination(pkh_addr1C_script_dest)); + BOOST_CHECK(pkh_addr2C == EncodeDestination(pkh_addr2C_script_dest)); +} + +BOOST_AUTO_TEST_CASE(serialised_address_from_block_test) +{ + // Addresses + auto bech32 = "bcrt1qta8meuczw0mhqupzjl5wplz47xajz0dn0wxxr8"; + auto eth = "0x9b8a4af42140d8a4c153a822f02571a1dd037e89"; + + // CKeyIDs taken from serialised block + auto bech32Hex = "5f4fbcf30273f770702297e8e0fc55f1bb213db3"; + auto ethHex = "897e03dda17125f022a853c1a4d84021f44a8a9b"; + + // Encode Bech32 + auto bech32Vec = ParseHex(bech32Hex); + std::vector data = {0}; + data.reserve(33); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bech32Vec.begin(), bech32Vec.end()); + auto bech32Encoded = bech32::Encode("bcrt", data); + + // Encode Eth + auto ethVec = ParseHex(ethHex); + auto ethID = HexStr(ethVec.rbegin(), ethVec.rend()); + std::vector ethOutput, ethInput(ethID.begin(), ethID.end()); + sha3_256_safe(ethInput, ethOutput); + auto hashedAddress = HexStr(ethOutput); + std::string ethEncoded = "0x"; + for (size_t i{}; i < ethID.size(); ++i) { + if (std::isdigit(ethID[i]) || hashedAddress[i] < '8') { + ethEncoded += ethID[i]; + } else { + ethEncoded += std::toupper(ethID[i]); + } + } + + // Check results match + BOOST_CHECK_EQUAL(bech32, bech32Encoded); + BOOST_CHECK_EQUAL(eth, ethEncoded); +} + +BOOST_AUTO_TEST_CASE(wpkh_key_test) +{ + CKey key1 = DecodeSecret(strSecret1); + BOOST_CHECK(key1.IsValid() && !key1.IsCompressed()); + CKey key2 = DecodeSecret(strSecret2); + BOOST_CHECK(key2.IsValid() && !key2.IsCompressed()); + CKey key1C = DecodeSecret(strSecret1C); + BOOST_CHECK(key1C.IsValid() && key1C.IsCompressed()); + CKey key2C = DecodeSecret(strSecret2C); + BOOST_CHECK(key2C.IsValid() && key2C.IsCompressed()); + CKey bad_key = DecodeSecret(strAddressBad); + BOOST_CHECK(!bad_key.IsValid()); + + CPubKey pubkey1 = key1. GetPubKey(); + CPubKey pubkey2 = key2. GetPubKey(); + CPubKey pubkey1C = key1C.GetPubKey(); + CPubKey pubkey2C = key2C.GetPubKey(); + + BOOST_CHECK(key1.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key1C.VerifyPubKey(pubkey1)); + BOOST_CHECK(key1C.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key1C.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key1C.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key2.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key2.VerifyPubKey(pubkey1C)); + BOOST_CHECK(key2.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key2.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key2C.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key2C.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key2C.VerifyPubKey(pubkey2)); + BOOST_CHECK(key2C.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(DecodeDestination(wpkh_addr1) == CTxDestination(WitnessV0KeyHash(pubkey1))); + BOOST_CHECK(DecodeDestination(wpkh_addr2) == CTxDestination(WitnessV0KeyHash(pubkey2))); + BOOST_CHECK(DecodeDestination(wpkh_addr1C) == CTxDestination(WitnessV0KeyHash(pubkey1C))); + BOOST_CHECK(DecodeDestination(wpkh_addr2C) == CTxDestination(WitnessV0KeyHash(pubkey2C))); + + BOOST_CHECK(wpkh_addr1 == EncodeDestination(CTxDestination(WitnessV0KeyHash(pubkey1)))); + BOOST_CHECK(wpkh_addr2 == EncodeDestination(CTxDestination(WitnessV0KeyHash(pubkey2)))); + BOOST_CHECK(wpkh_addr1C == EncodeDestination(CTxDestination(WitnessV0KeyHash(pubkey1C)))); + BOOST_CHECK(wpkh_addr2C == EncodeDestination(CTxDestination(WitnessV0KeyHash(pubkey2C)))); + + // Test script to destination conversions + CScript wpkh_addr1_script = GetScriptForDestination(DecodeDestination(wpkh_addr1)); + CScript wpkh_addr2_script = GetScriptForDestination(DecodeDestination(wpkh_addr2)); + CScript wpkh_addr1C_script = GetScriptForDestination(DecodeDestination(wpkh_addr1C)); + CScript wpkh_addr2C_script = GetScriptForDestination(DecodeDestination(wpkh_addr2C)); + + CTxDestination wpkh_addr1_script_dest; + CTxDestination wpkh_addr2_script_dest; + CTxDestination wpkh_addr1C_script_dest; + CTxDestination wpkh_addr2C_script_dest; + ExtractDestination(wpkh_addr1_script, wpkh_addr1_script_dest); + ExtractDestination(wpkh_addr2_script, wpkh_addr2_script_dest); + ExtractDestination(wpkh_addr1C_script, wpkh_addr1C_script_dest); + ExtractDestination(wpkh_addr2C_script, wpkh_addr2C_script_dest); + + BOOST_CHECK(wpkh_addr1 == EncodeDestination(wpkh_addr1_script_dest)); + BOOST_CHECK(wpkh_addr2 == EncodeDestination(wpkh_addr2_script_dest)); + BOOST_CHECK(wpkh_addr1C == EncodeDestination(wpkh_addr1C_script_dest)); + BOOST_CHECK(wpkh_addr2C == EncodeDestination(wpkh_addr2C_script_dest)); +} + +BOOST_AUTO_TEST_CASE(erc55_key_test) +{ + CKey key1 = DecodeSecret(strSecret1); + BOOST_CHECK(key1.IsValid() && !key1.IsCompressed()); + CKey key2 = DecodeSecret(strSecret2); + BOOST_CHECK(key2.IsValid() && !key2.IsCompressed()); + CKey key1C = DecodeSecret(strSecret1C); + BOOST_CHECK(key1C.IsValid() && key1C.IsCompressed()); + CKey key2C = DecodeSecret(strSecret2C); + BOOST_CHECK(key2C.IsValid() && key2C.IsCompressed()); + CKey bad_key = DecodeSecret(strAddressBad); + BOOST_CHECK(!bad_key.IsValid()); + + CPubKey pubkey1 = key1. GetPubKey(); + CPubKey pubkey2 = key2. GetPubKey(); + CPubKey pubkey1C = key1C.GetPubKey(); + CPubKey pubkey2C = key2C.GetPubKey(); + + BOOST_CHECK(key1.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key1.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key1C.VerifyPubKey(pubkey1)); + BOOST_CHECK(key1C.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key1C.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key1C.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key2.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key2.VerifyPubKey(pubkey1C)); + BOOST_CHECK(key2.VerifyPubKey(pubkey2)); + BOOST_CHECK(!key2.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(!key2C.VerifyPubKey(pubkey1)); + BOOST_CHECK(!key2C.VerifyPubKey(pubkey1C)); + BOOST_CHECK(!key2C.VerifyPubKey(pubkey2)); + BOOST_CHECK(key2C.VerifyPubKey(pubkey2C)); + + BOOST_CHECK(DecodeDestination(erc55_addr1) == CTxDestination(WitnessV16EthHash(pubkey1))); + BOOST_CHECK(DecodeDestination(erc55_addr2) == CTxDestination(WitnessV16EthHash(pubkey2))); + BOOST_CHECK(DecodeDestination(erc55_addr1C) == CTxDestination(WitnessV16EthHash(pubkey1C))); + BOOST_CHECK(DecodeDestination(erc55_addr2C) == CTxDestination(WitnessV16EthHash(pubkey2C))); + + BOOST_CHECK(erc55_addr1 == EncodeDestination(CTxDestination(WitnessV16EthHash(pubkey1)))); + BOOST_CHECK(erc55_addr2 == EncodeDestination(CTxDestination(WitnessV16EthHash(pubkey2)))); + BOOST_CHECK(erc55_addr1C == EncodeDestination(CTxDestination(WitnessV16EthHash(pubkey1C)))); + BOOST_CHECK(erc55_addr2C == EncodeDestination(CTxDestination(WitnessV16EthHash(pubkey2C)))); + + // Test script to destination conversions + CScript erc55_addr1_script = GetScriptForDestination(DecodeDestination(erc55_addr1)); + CScript erc55_addr2_script = GetScriptForDestination(DecodeDestination(erc55_addr2)); + CScript erc55_addr1C_script = GetScriptForDestination(DecodeDestination(erc55_addr1C)); + CScript erc55_addr2C_script = GetScriptForDestination(DecodeDestination(erc55_addr2C)); + + CTxDestination erc55_addr1_script_dest; + CTxDestination erc55_addr2_script_dest; + CTxDestination erc55_addr1C_script_dest; + CTxDestination erc55_addr2C_script_dest; + ExtractDestination(erc55_addr1_script, erc55_addr1_script_dest); + ExtractDestination(erc55_addr2_script, erc55_addr2_script_dest); + ExtractDestination(erc55_addr1C_script, erc55_addr1C_script_dest); + ExtractDestination(erc55_addr2C_script, erc55_addr2C_script_dest); + + BOOST_CHECK(erc55_addr1 == EncodeDestination(erc55_addr1_script_dest)); + BOOST_CHECK(erc55_addr2 == EncodeDestination(erc55_addr2_script_dest)); + BOOST_CHECK(erc55_addr1C == EncodeDestination(erc55_addr1C_script_dest)); + BOOST_CHECK(erc55_addr2C == EncodeDestination(erc55_addr2C_script_dest)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index a43eb7373e..1be4cd7301 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2617,6 +2617,15 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl const auto consensus = chainparams.GetConsensus(); auto isEvmEnabledForBlock = IsEVMEnabled(pindex->nHeight, mnview, consensus); + CEVMInitialState evmInitialState; + + if (isEvmEnabledForBlock) { + evmInitialState.transferDomainState = attributes->GetValue(CTransferDomainStatsLive::Key, CTransferDomainStatsLive{}); + auto res = ProcessAccountingStateBeforeBlock(block, pindex, mnview, chainparams, evmInitialState); + if (!res.ok) + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: %s", __func__, res.msg), REJECT_INVALID, res.dbgMsg); + } + // Variable to tally total gas used in the block uint64_t totalGas{}; @@ -2866,7 +2875,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl accountsView.Flush(); // Execute EVM Queue - res = ProcessDeFiEventFallible(block, pindex, mnview, chainparams, evmQueueId, isEvmEnabledForBlock); + res = ProcessDeFiEventFallible(block, pindex, mnview, chainparams, evmQueueId, isEvmEnabledForBlock, evmInitialState); if (!res.ok) { return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: %s", __func__, res.msg), REJECT_INVALID, res.dbgMsg); } @@ -3364,7 +3373,7 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp auto r = XResultValue(evm_unsafe_try_create_queue(result)); if (!r) { return invalidStateReturn(state, pindexNew, mnview, 0); } uint64_t evmQueueId = *r; - + bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, mnview, chainparams, rewardedAnchors, false, evmQueueId); GetMainSignals().BlockChecked(blockConnecting, state); if (!rv) { return invalidStateReturn(state, pindexNew, mnview, evmQueueId); } diff --git a/test/functional/contracts/StateChange.sol b/test/functional/contracts/StateChange.sol new file mode 100644 index 0000000000..d903a4badb --- /dev/null +++ b/test/functional/contracts/StateChange.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract StateChange { + mapping(address => bool) public state; + function changeState(bool a) public { + state[msg.sender] = a; + } + + function loop(uint256 num) public { + uint number = 0; + require(state[msg.sender]); + while (number < num) { + number++; + } + } +} \ No newline at end of file diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index 92c67031c4..563f42e893 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -93,6 +93,9 @@ def run_test(self): # Toggle EVM self.toggle_evm_enablement() + # Check multiple transfer domain TXs in the same block + self.multiple_transferdomain() + def test_tx_without_chainid(self): node = self.nodes[0] @@ -980,14 +983,15 @@ def validate_xvm_coinbase(self): assert_equal(opreturn_priority_fee_amount, self.miner_fee) # Check EVM beneficiary address - opreturn_miner_keyid = coinbase_xvm["msg"]["evm"]["beneficiary"][2:] - miner_eth_address = self.nodes[0].addressmap( - self.nodes[0].get_genesis_keys().operatorAuthAddress, 1 + opreturn_miner_address = coinbase_xvm["msg"]["evm"]["beneficiary"][2:] + miner_eth_address = ( + self.nodes[0] + .addressmap(self.nodes[0].get_genesis_keys().operatorAuthAddress, 1)[ + "format" + ]["erc55"][2:] + .lower() ) - miner_eth_keyid = self.nodes[0].getaddressinfo( - miner_eth_address["format"]["erc55"] - )["witness_program"] - assert_equal(opreturn_miner_keyid, miner_eth_keyid) + assert_equal(opreturn_miner_address, miner_eth_address) def evm_rollback(self): # Test rollback of EVM TX @@ -1008,7 +1012,7 @@ def evm_rollback(self): def mempool_tx_limit(self): # Test max limit of TX from a specific sender - for i in range(63): + for i in range(64): self.nodes[0].evmtx(self.eth_address, i, 21, 21001, self.to_address, 1) # Test error at the 64th EVM TX @@ -1017,7 +1021,7 @@ def mempool_tx_limit(self): "too-many-eth-txs-by-sender", self.nodes[0].evmtx, self.eth_address, - 63, + 64, 21, 21001, self.to_address, @@ -1030,31 +1034,31 @@ def mempool_tx_limit(self): block_txs = self.nodes[0].getblock( self.nodes[0].getblockhash(self.nodes[0].getblockcount()) )["tx"] - assert_equal(len(block_txs), 64) + assert_equal(len(block_txs), 65) # Check accounting of EVM fees attributes = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] assert_equal( - attributes["v0/live/economy/evm/block/fee_burnt"], self.burnt_fee * 63 + attributes["v0/live/economy/evm/block/fee_burnt"], self.burnt_fee * 64 ) assert_equal( - attributes["v0/live/economy/evm/block/fee_burnt_min"], self.burnt_fee * 63 + attributes["v0/live/economy/evm/block/fee_burnt_min"], self.burnt_fee * 64 ) assert_equal( attributes["v0/live/economy/evm/block/fee_burnt_min_hash"], self.blockHash ) assert_equal( - attributes["v0/live/economy/evm/block/fee_burnt_max"], self.burnt_fee * 63 + attributes["v0/live/economy/evm/block/fee_burnt_max"], self.burnt_fee * 64 ) assert_equal( attributes["v0/live/economy/evm/block/fee_burnt_max_hash"], self.blockHash ) assert_equal( - attributes["v0/live/economy/evm/block/fee_priority"], self.priority_fee * 63 + attributes["v0/live/economy/evm/block/fee_priority"], self.priority_fee * 64 ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_min"], - self.priority_fee * 63, + self.priority_fee * 64, ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_min_hash"], @@ -1062,7 +1066,7 @@ def mempool_tx_limit(self): ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_max"], - self.priority_fee * 63, + self.priority_fee * 64, ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_max_hash"], @@ -1072,22 +1076,22 @@ def mempool_tx_limit(self): # Check Eth balances after transfer assert_equal( int(self.nodes[0].eth_getBalance(self.eth_address)[2:], 16), - 136972217000000000000, + 135971776000000000000, ) assert_equal( int(self.nodes[0].eth_getBalance(self.to_address)[2:], 16), - 63000000000000000000, + 64000000000000000000, ) - # Try and send another TX to make sure mempool has removed entires - tx = self.nodes[0].evmtx(self.eth_address, 63, 21, 21001, self.to_address, 1) + # Try and send another TX to make sure mempool has removed entries + tx = self.nodes[0].evmtx(self.eth_address, 64, 21, 21001, self.to_address, 1) self.nodes[0].generate(1) self.blockHash1 = self.nodes[0].getblockhash(self.nodes[0].getblockcount()) # Check accounting of EVM fees attributes = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] assert_equal( - attributes["v0/live/economy/evm/block/fee_burnt"], self.burnt_fee * 64 + attributes["v0/live/economy/evm/block/fee_burnt"], self.burnt_fee * 65 ) assert_equal( attributes["v0/live/economy/evm/block/fee_burnt_min"], self.burnt_fee @@ -1096,13 +1100,13 @@ def mempool_tx_limit(self): attributes["v0/live/economy/evm/block/fee_burnt_min_hash"], self.blockHash1 ) assert_equal( - attributes["v0/live/economy/evm/block/fee_burnt_max"], self.burnt_fee * 63 + attributes["v0/live/economy/evm/block/fee_burnt_max"], self.burnt_fee * 64 ) assert_equal( attributes["v0/live/economy/evm/block/fee_burnt_max_hash"], self.blockHash ) assert_equal( - attributes["v0/live/economy/evm/block/fee_priority"], self.priority_fee * 64 + attributes["v0/live/economy/evm/block/fee_priority"], self.priority_fee * 65 ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_min"], self.priority_fee @@ -1113,7 +1117,7 @@ def mempool_tx_limit(self): ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_max"], - self.priority_fee * 63, + self.priority_fee * 64, ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_max_hash"], @@ -1128,11 +1132,11 @@ def mempool_tx_limit(self): def multiple_eth_rbf(self): # Test multiple replacement TXs with differing fees - self.nodes[0].evmtx(self.eth_address, 64, 22, 21001, self.to_address, 1) - self.nodes[0].evmtx(self.eth_address, 64, 23, 21001, self.to_address, 1) - tx0 = self.nodes[0].evmtx(self.eth_address, 64, 25, 21001, self.to_address, 1) - self.nodes[0].evmtx(self.eth_address, 64, 21, 21001, self.to_address, 1) - self.nodes[0].evmtx(self.eth_address, 64, 24, 21001, self.to_address, 1) + self.nodes[0].evmtx(self.eth_address, 65, 22, 21001, self.to_address, 1) + self.nodes[0].evmtx(self.eth_address, 65, 23, 21001, self.to_address, 1) + tx0 = self.nodes[0].evmtx(self.eth_address, 65, 25, 21001, self.to_address, 1) + self.nodes[0].evmtx(self.eth_address, 65, 21, 21001, self.to_address, 1) + self.nodes[0].evmtx(self.eth_address, 65, 24, 21001, self.to_address, 1) self.nodes[0].evmtx(self.to_address, 0, 22, 21001, self.eth_address, 1) self.nodes[0].evmtx(self.to_address, 0, 23, 21001, self.eth_address, 1) tx1 = self.nodes[0].evmtx(self.to_address, 0, 25, 21001, self.eth_address, 1) @@ -1141,29 +1145,29 @@ def multiple_eth_rbf(self): self.nodes[0].generate(1) # Check accounting of EVM fees - txLegacy64 = { + txLegacy65 = { "nonce": "0x1", "from": self.eth_address, "value": "0x1", "gas": "0x5208", # 21000 "gasPrice": "0x5D21DBA00", # 25_000_000_000, } - fees64 = self.nodes[0].debug_feeEstimate(txLegacy64) - self.burnt_fee64 = hex_to_decimal(fees64["burnt_fee"]) - self.priority_fee64 = hex_to_decimal(fees64["priority_fee"]) + fees65 = self.nodes[0].debug_feeEstimate(txLegacy65) + self.burnt_fee65 = hex_to_decimal(fees65["burnt_fee"]) + self.priority_fee65 = hex_to_decimal(fees65["priority_fee"]) attributes = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] assert_equal( attributes["v0/live/economy/evm/block/fee_burnt"], - self.burnt_fee * 64 + 2 * self.burnt_fee64, + self.burnt_fee * 65 + 2 * self.burnt_fee65, ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority"], - self.priority_fee * 64 + 2 * self.priority_fee64, + self.priority_fee * 65 + 2 * self.priority_fee65, ) attributes = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] assert_equal( attributes["v0/live/economy/evm/block/fee_burnt"], - self.burnt_fee * 64 + 2 * self.burnt_fee64, + self.burnt_fee * 65 + 2 * self.burnt_fee65, ) assert_equal( attributes["v0/live/economy/evm/block/fee_burnt_min"], self.burnt_fee @@ -1172,14 +1176,14 @@ def multiple_eth_rbf(self): attributes["v0/live/economy/evm/block/fee_burnt_min_hash"], self.blockHash1 ) assert_equal( - attributes["v0/live/economy/evm/block/fee_burnt_max"], self.burnt_fee * 63 + attributes["v0/live/economy/evm/block/fee_burnt_max"], self.burnt_fee * 64 ) assert_equal( attributes["v0/live/economy/evm/block/fee_burnt_max_hash"], self.blockHash ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority"], - self.priority_fee * 64 + 2 * self.priority_fee64, + self.priority_fee * 65 + 2 * self.priority_fee65, ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_min"], self.priority_fee @@ -1190,7 +1194,7 @@ def multiple_eth_rbf(self): ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_max"], - self.priority_fee * 63, + self.priority_fee * 64, ) assert_equal( attributes["v0/live/economy/evm/block/fee_priority_max_hash"], @@ -1228,6 +1232,40 @@ def toggle_evm_enablement(self): int(evm_enabling_block["number"], base=16) + 1, ) + def multiple_transferdomain(self): + # Get block count + block_count = self.nodes[0].getblockcount() + + # Create multiple transfer domain TXs in one block + self.nodes[0].transferdomain( + [ + { + "src": {"address": self.address, "amount": "1@DFI", "domain": 2}, + "dst": { + "address": self.eth_address, + "amount": "1@DFI", + "domain": 3, + }, + } + ] + ) + self.nodes[0].transferdomain( + [ + { + "src": {"address": self.address, "amount": "1@DFI", "domain": 2}, + "dst": { + "address": self.eth_address, + "amount": "1@DFI", + "domain": 3, + }, + } + ] + ) + self.nodes[0].generate(1) + + # Make sure that block count has incremented + assert_equal(self.nodes[0].getblockcount(), block_count + 1) + if __name__ == "__main__": EVMTest().main() diff --git a/test/functional/feature_evm_miner.py b/test/functional/feature_evm_miner.py index aa20505d8f..1c215e3a9c 100644 --- a/test/functional/feature_evm_miner.py +++ b/test/functional/feature_evm_miner.py @@ -99,8 +99,14 @@ def setup(self): ] ) self.nodes[0].generate(1) + self.start_height = self.nodes[0].getblockcount() + + def rollback_and_clear_mempool(self): + self.rollback_to(self.start_height) + self.nodes[0].clearmempool() def mempool_block_limit(self): + self.rollback_and_clear_mempool() abi, bytecode = EVMContract.from_file("Loop.sol", "Loop").compile() compiled = self.nodes[0].w3.eth.contract(abi=abi, bytecode=bytecode) tx = compiled.constructor().build_transaction( @@ -230,6 +236,7 @@ def mempool_block_limit(self): assert_equal(tx_infos[idx]["vm"]["msg"]["to"], self.toAddress) def invalid_evm_tx_in_block_creation(self): + self.rollback_and_clear_mempool() before_balance = Decimal( self.nodes[0].getaccount(self.ethAddress)[0].split("@")[0] ) @@ -267,6 +274,206 @@ def invalid_evm_tx_in_block_creation(self): block_info = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 4) assert_equal(len(block_info["tx"]) - 1, 20) + def test_for_fee_mismatch_between_block_and_queue(self): + self.rollback_and_clear_mempool() + before_balance = Decimal( + self.nodes[0].getaccount(self.ethAddress)[0].split("@")[0] + ) + assert_equal(before_balance, Decimal("100")) + + abi, bytecode = EVMContract.from_file( + "StateChange.sol", "StateChange" + ).compile() + compiled = self.nodes[0].w3.eth.contract(abi=abi, bytecode=bytecode) + tx = compiled.constructor().build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.ethAddress), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 1_500_000_000, + "gas": 1_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + contract_address = self.nodes[0].w3.eth.wait_for_transaction_receipt(hash)[ + "contractAddress" + ] + contract = self.nodes[0].w3.eth.contract(address=contract_address, abi=abi) + + # gas used values + gas_used_when_true = Decimal("1589866") + gas_used_when_false = Decimal("23830") + gas_used_when_change_state = Decimal("21952") + + # Set state to true + tx = contract.functions.changeState(True).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.ethAddress), + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + + tx = contract.functions.loop(9_000).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.ethAddress), + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + gas_used = Decimal( + self.nodes[0].w3.eth.wait_for_transaction_receipt(hash)["gasUsed"] + ) + assert_equal(gas_used, gas_used_when_true) + + # Set state to false + tx = contract.functions.changeState(False).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.ethAddress), + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + gas_used = Decimal( + self.nodes[0].w3.eth.wait_for_transaction_receipt(hash)["gasUsed"] + ) + assert_equal(gas_used, gas_used_when_change_state) + + tx = contract.functions.loop(9_000).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.ethAddress), + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + gas_used = Decimal( + self.nodes[0].w3.eth.wait_for_transaction_receipt(hash)["gasUsed"] + ) + assert_equal(gas_used, gas_used_when_false) + + # Set state back to true + tx = contract.functions.changeState(True).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.ethAddress), + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + + # Only the first 10 txs should have gas used = gas_used_when_true + hashes = [] + count = self.nodes[0].w3.eth.get_transaction_count(self.ethAddress) + for idx in range(10): + tx = contract.functions.loop(9_000).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": count + idx, + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + hashes.append((signed.hash.hex())) + + # Send change of state + tx = contract.functions.changeState(False).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": count + 10, + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + hashes.append(signed.hash.hex()) + + # Only the 8 of the 15 txs should be able to be minted before reaching block limit + # calculated in txqueue. + # All of these txs should have gas used = gas_used_when_false + for idx in range(15): + tx = contract.functions.loop(9_000).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": count + 11 + idx, + "gasPrice": 25_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.ethPrivKey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + hashes.append(signed.hash.hex()) + + self.nodes[0].generate(1) + + # Check that only 19 txs are minted + block = self.nodes[0].eth_getBlockByNumber("latest", False) + assert_equal(len(block["transactions"]), 19) + + # Check first 10 txs should have gas used when true + for idx in range(10): + gas_used = Decimal( + self.nodes[0].w3.eth.wait_for_transaction_receipt(hashes[idx])[ + "gasUsed" + ] + ) + assert_equal(block["transactions"][idx], hashes[idx]) + assert_equal(gas_used, gas_used_when_true) + + gas_used = Decimal( + self.nodes[0].w3.eth.wait_for_transaction_receipt(hashes[10])["gasUsed"] + ) + assert_equal(gas_used, gas_used_when_change_state) + + # Check last 5 txs should have gas used when false + for idx in range(8): + gas_used = Decimal( + self.nodes[0].w3.eth.wait_for_transaction_receipt(hashes[11 + idx])[ + "gasUsed" + ] + ) + assert_equal(block["transactions"][11 + idx], hashes[11 + idx]) + assert_equal(gas_used, gas_used_when_false) + + # TODO: Thereotical block size calculated in txqueue would be: + # gas_used_when_true * 18 + gas_used_when_change_state = 28639540 + # But the minted block is only of size 16111252. + correct_gas_used = ( + gas_used_when_true * 10 + + gas_used_when_false * 8 + + gas_used_when_change_state + ) + block_info = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 4) + assert_equal( + block_info["tx"][0]["vm"]["xvmHeader"]["gasUsed"], correct_gas_used + ) + + # Check that the remaining 7 evm txs are still in mempool + assert_equal(Decimal(self.nodes[0].getmempoolinfo()["size"]), Decimal("7")) + def run_test(self): self.setup() @@ -276,6 +483,9 @@ def run_test(self): # Test invalid tx in block creation self.invalid_evm_tx_in_block_creation() + # Test for block size overflow from fee mismatch between tx queue and block + self.test_for_fee_mismatch_between_block_and_queue() + if __name__ == "__main__": EVMTest().main() diff --git a/test/functional/feature_evm_rpc_fee_history.py b/test/functional/feature_evm_rpc_fee_history.py new file mode 100755 index 0000000000..2273600b31 --- /dev/null +++ b/test/functional/feature_evm_rpc_fee_history.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test EVM behaviour""" + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import ( + assert_equal, + int_to_eth_u256, +) + +# pragma solidity ^0.8.2; +# contract Multiply { +# function multiply(uint a, uint b) public pure returns (uint) { +# return a * b; +# } +# } +CONTRACT_BYTECODE = "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b603c6038366004605f565b604e565b60405190815260200160405180910390f35b600060588284607f565b9392505050565b600080604083850312156070578182fd5b50508035926020909101359150565b600081600019048311821515161560a457634e487b7160e01b81526011600452602481fd5b50029056fea2646970667358221220223df7833fd08eb1cd3ce363a9c4cb4619c1068a5f5517ea8bb862ed45d994f764736f6c63430008020033" + + +class EVMTest(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [ + [ + "-dummypos=0", + "-txnotokens=0", + "-amkheight=50", + "-bayfrontheight=51", + "-eunosheight=80", + "-fortcanningheight=82", + "-fortcanninghillheight=84", + "-fortcanningroadheight=86", + "-fortcanningcrunchheight=88", + "-fortcanningspringheight=90", + "-fortcanninggreatworldheight=94", + "-fortcanningepilogueheight=96", + "-grandcentralheight=101", + "-nextnetworkupgradeheight=105", + "-subsidytest=1", + "-txindex=1", + ], + ] + + def setup(self): + self.address = self.nodes[0].get_genesis_keys().ownerAuthAddress + self.ethAddress = "0x9b8a4af42140d8a4c153a822f02571a1dd037e89" + self.toAddress = "0x6c34cbb9219d8caa428835d2073e8ec88ba0a110" + self.nodes[0].importprivkey( + "af990cc3ba17e776f7f57fcc59942a82846d75833fa17d2ba59ce6858d886e23" + ) # ethAddress + self.nodes[0].importprivkey( + "17b8cb134958b3d8422b6c43b0732fcdb8c713b524df2d45de12f0c7e214ba35" + ) # toAddress + + # Generate chain + self.nodes[0].generate(105) + + self.nodes[0].getbalance() + self.nodes[0].utxostoaccount({self.address: "201@DFI"}) + self.nodes[0].setgov( + { + "ATTRIBUTES": { + "v0/params/feature/evm": "true", + "v0/params/feature/transferdomain": "true", + "v0/transferdomain/dvm-evm/enabled": "true", + "v0/transferdomain/dvm-evm/src-formats": ["p2pkh", "bech32"], + "v0/transferdomain/dvm-evm/dest-formats": ["erc55"], + "v0/transferdomain/evm-dvm/src-formats": ["erc55"], + "v0/transferdomain/evm-dvm/auth-formats": ["bech32-erc55"], + "v0/transferdomain/evm-dvm/dest-formats": ["p2pkh", "bech32"], + } + } + ) + self.nodes[0].generate(1) + + self.nodes[0].transferdomain( + [ + { + "src": {"address": self.address, "amount": "50@DFI", "domain": 2}, + "dst": { + "address": self.ethAddress, + "amount": "50@DFI", + "domain": 3, + }, + } + ] + ) + self.nodes[0].generate(1) + + balance = self.nodes[0].eth_getBalance(self.ethAddress, "latest") + assert_equal(balance, int_to_eth_u256(50)) + + def create_block(self, count, priority_fees): + node = self.nodes[0] + nonce = 0 + for x in range(count): + for y in priority_fees: + tx = { + "from": self.ethAddress, + "value": "0x0", + "data": CONTRACT_BYTECODE, + "gas": "0x18e70", # 102_000 + "maxPriorityFeePerGas": hex(y), + "maxFeePerGas": "0x22ecb25c00", # 150_000_000_000 + "type": "0x2", + "nonce": hex(nonce), + } + nonce += 1 + node.eth_sendTransaction(tx) + node.generate(1) + + def test_fee_history(self): + node = self.nodes[0] + + self.create_block(2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + + count = 2 + reward_percentiles = [20, 30, 50, 70, 85, 100] + history = node.eth_feeHistory(hex(count), "latest", reward_percentiles) + + current = node.eth_blockNumber() + assert_equal(history["oldestBlock"], hex(int(current, 16) - count + 1)) + assert_equal(len(history["baseFeePerGas"]), count + 1) + for x in history["reward"]: + assert_equal(len(x), len(reward_percentiles)) + assert_equal(x, ["0x2", "0x3", "0x5", "0x7", "0x9", "0xa"]) + + def run_test(self): + self.setup() + + self.test_fee_history() + + +if __name__ == "__main__": + EVMTest().main() diff --git a/test/functional/feature_evm_transferdomain.py b/test/functional/feature_evm_transferdomain.py index f1a5852fa4..eb3cb7cdca 100755 --- a/test/functional/feature_evm_transferdomain.py +++ b/test/functional/feature_evm_transferdomain.py @@ -365,8 +365,8 @@ def invalid_parameters(self): ], ) assert_raises_rpc_error( - -5, - "recipient (blablabla) does not refer to any valid address", + -8, + "Invalid src address provided", self.nodes[0].transferdomain, [ { @@ -380,8 +380,8 @@ def invalid_parameters(self): ], ) assert_raises_rpc_error( - -5, - "recipient (blablabla) does not refer to any valid address", + -8, + "Invalid dst address provided", self.nodes[0].transferdomain, [ { @@ -482,12 +482,18 @@ def valid_transfer_dvm_evm(self): attributes["v0/live/economy/transferdomain/dvm-evm/0/total"], Decimal("100.00000000"), ) - # assert_equal(attributes['v0/live/economy/transferdomain/dvm/0/current'], Decimal('-100.00000000')) + assert_equal( + attributes["v0/live/economy/transferdomain/dvm/0/current"], + Decimal("-100.00000000"), + ) assert_equal( attributes["v0/live/economy/transferdomain/dvm/0/out"], Decimal("100.00000000"), ) - # assert_equal(attributes['v0/live/economy/transferdomain/evm/0/current'], Decimal('100.00000000')) + assert_equal( + attributes["v0/live/economy/transferdomain/evm/0/current"], + Decimal("100.00000000"), + ) assert_equal( attributes["v0/live/economy/transferdomain/evm/0/in"], Decimal("100.00000000"), @@ -901,6 +907,79 @@ def valid_transfer_to_evm_then_move_then_back_to_dvm(self): Decimal("100.00000000"), ) + def valid_transfer_dvm_evm_dvm_with_evm_tx_in_same_block(self): + self.rollback_to(self.start_height) + + # Transfer 100 DFI from DVM to EVM + self.valid_transfer_dvm_evm() + + # Transfer 100 DFI from EVM to DVM + tx = transfer_domain( + self.nodes[0], self.eth_address, self.address, "90@DFI", 3, 2 + ) + + self.nodes[0].evmtx(self.eth_address, 0, 21, 21000, self.eth_address1, 5) + + self.nodes[0].generate(1) + + # Check tx fields + result = self.nodes[0].getcustomtx(tx)["results"]["transfers"][0] + assert_equal(result["src"]["address"], self.eth_address) + assert_equal(result["src"]["amount"], "90.00000000@0") + assert_equal(result["src"]["domain"], "EVM") + assert_equal(result["dst"]["address"], self.address) + assert_equal(result["dst"]["amount"], "90.00000000@0") + assert_equal(result["dst"]["domain"], "DVM") + + # Check new balances + new_dfi_balance = self.nodes[0].getaccount(self.address, {}, True)["0"] + new_eth_balance = self.nodes[0].eth_getBalance(self.eth_address) + assert_equal(new_dfi_balance, self.dfi_balance - Decimal("10")) + assert_equal(new_eth_balance, int_to_eth_u256(10)) + assert_equal(len(self.nodes[0].getaccount(self.eth_address, {}, True)), 0) + + # Check accounting of DVM->EVM transfer + attributes = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] + assert_equal( + attributes["v0/live/economy/transferdomain/dvm-evm/0/total"], + Decimal("100.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/dvm/0/current"], + Decimal("-10.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/dvm/0/out"], + Decimal("100.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/dvm/0/in"], + Decimal("90.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/evm-dvm/0/total"], + Decimal("90.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/evm/0/current"], + Decimal("10.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/evm/0/in"], + Decimal("100.00000000"), + ) + assert_equal( + attributes["v0/live/economy/transferdomain/evm/0/out"], + Decimal("90.00000000"), + ) + + # Check accounting of EVM fees + attributes = self.nodes[0].getgov("ATTRIBUTES")["ATTRIBUTES"] + assert_equal(attributes["v0/live/economy/evm/block/fee_burnt"], Decimal("0E-8")) + assert_equal( + attributes["v0/live/economy/evm/block/fee_priority"], Decimal("0E-8") + ) + def run_test(self): self.setup() self.invalid_before_fork_and_disabled() @@ -920,6 +999,8 @@ def run_test(self): self.valid_transfer_to_evm_then_move_then_back_to_dvm() + # self.valid_transfer_dvm_evm_dvm_with_evm_tx_in_same_block() + if __name__ == "__main__": EVMTest().main() diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index b1b70b3a5c..4d00193262 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -16,6 +16,8 @@ KNOWN_VIOLATIONS=( "src/rest.cpp:.*strtol" "src/rpc/rawtransaction_util.cpp:.*stoul" "src/test/dbwrapper_tests.cpp:.*snprintf" + "src/test/key_tests.cpp:.*isdigit" + "src/test/key_tests.cpp:.*toupper" "src/test/liquidity_tests.cpp:.*printf" "src/torcontrol.cpp:.*atoi" "src/torcontrol.cpp:.*strtol"