From dd1987dfbc7fd6155bab8afbb444e227b8b4bc6b Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Sat, 29 Jul 2023 21:22:30 +1200 Subject: [PATCH 01/17] Add value param to 'XvmCall'. --- chain-extensions/types/xvm/src/lib.rs | 2 ++ chain-extensions/xvm/src/lib.rs | 10 +++++-- pallets/xvm/Cargo.toml | 2 +- pallets/xvm/src/benchmarking.rs | 14 +++++++-- pallets/xvm/src/lib.rs | 42 ++++++++++++++++++++------- pallets/xvm/src/mock.rs | 1 - precompiles/xvm/src/lib.rs | 8 +++-- primitives/src/xvm.rs | 3 ++ 8 files changed, 60 insertions(+), 22 deletions(-) diff --git a/chain-extensions/types/xvm/src/lib.rs b/chain-extensions/types/xvm/src/lib.rs index d23ccd9102..014c1bcbb1 100644 --- a/chain-extensions/types/xvm/src/lib.rs +++ b/chain-extensions/types/xvm/src/lib.rs @@ -65,4 +65,6 @@ pub struct XvmCallArgs { pub to: Vec, /// Encoded call params pub input: Vec, + /// Value to transfer + pub value: u128, } diff --git a/chain-extensions/xvm/src/lib.rs b/chain-extensions/xvm/src/lib.rs index 9696fc6d7a..4e3d749b8f 100644 --- a/chain-extensions/xvm/src/lib.rs +++ b/chain-extensions/xvm/src/lib.rs @@ -30,7 +30,6 @@ use xvm_chain_extension_types::{XvmCallArgs, XvmExecutionResult}; enum XvmFuncId { Call, - // TODO: expand with other calls too } impl TryFrom for XvmFuncId { @@ -89,7 +88,12 @@ where } }; - let XvmCallArgs { vm_id, to, input } = env.read_as_unbounded(env.in_len())?; + let XvmCallArgs { + vm_id, + to, + input, + value, + } = env.read_as_unbounded(env.in_len())?; let _origin_address = env.ext().address().clone(); let _value = env.ext().value_transferred(); @@ -108,7 +112,7 @@ where } } }; - let call_result = XC::call(xvm_context, vm_id, caller, to, input); + let call_result = XC::call(xvm_context, vm_id, caller, to, input, value); let actual_weight = match call_result { Ok(ref info) => info.used_weight, diff --git a/pallets/xvm/Cargo.toml b/pallets/xvm/Cargo.toml index 6f1022bf24..e0650d3b82 100644 --- a/pallets/xvm/Cargo.toml +++ b/pallets/xvm/Cargo.toml @@ -39,7 +39,7 @@ pallet-timestamp = { workspace = true, features = ["std"] } sp-io = { workspace = true } [features] -default = ["std"] +default = ["std", "runtime-benchmarks"] std = [ "log/std", "parity-scale-codec/std", diff --git a/pallets/xvm/src/benchmarking.rs b/pallets/xvm/src/benchmarking.rs index 97edd7d0d5..38c41a1d76 100644 --- a/pallets/xvm/src/benchmarking.rs +++ b/pallets/xvm/src/benchmarking.rs @@ -24,7 +24,11 @@ use parity_scale_codec::Encode; use sp_core::H160; use sp_runtime::MultiAddress; -#[benchmarks] +use astar_primitives::Balance; + +#[benchmarks( + where ::Currency: Currency, +)] mod benchmarks { use super::*; @@ -38,10 +42,12 @@ mod benchmarks { let source = whitelisted_caller(); let target = H160::repeat_byte(1).encode(); let input = vec![1, 2, 3]; + let value = 1_000_000u128; #[block] { - Pallet::::call_without_execution(context, vm_id, source, target, input).unwrap(); + Pallet::::call_without_execution(context, vm_id, source, target, input, value) + .unwrap(); } } @@ -55,10 +61,12 @@ mod benchmarks { let source = whitelisted_caller(); let target = MultiAddress::::Id(whitelisted_caller()).encode(); let input = vec![1, 2, 3]; + let value = 1_000_000u128; #[block] { - Pallet::::call_without_execution(context, vm_id, source, target, input).unwrap(); + Pallet::::call_without_execution(context, vm_id, source, target, input, value) + .unwrap(); } } diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index 5da16856fc..f0be4bf96e 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -37,7 +37,11 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ensure, traits::ConstU32, BoundedVec}; +use frame_support::{ + ensure, + traits::{ConstU32, Currency}, + BoundedVec, +}; use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_evm::GasWeightMapping; use parity_scale_codec::Decode; @@ -50,6 +54,7 @@ use astar_primitives::{ AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, MAX_ETHEREUM_TX_INPUT_SIZE, }, xvm::{CallError, CallErrorWithWeight, CallInfo, CallResult, Context, VmId, XvmCall}, + Balance, }; #[cfg(feature = "runtime-benchmarks")] @@ -58,7 +63,6 @@ mod benchmarking; pub mod weights; pub use weights::WeightInfo; -#[cfg(test)] mod mock; pub use pallet::*; @@ -88,25 +92,35 @@ pub mod pallet { } } -impl XvmCall for Pallet { +impl XvmCall for Pallet +where + T: Config, + T::Currency: Currency, +{ fn call( context: Context, vm_id: VmId, source: T::AccountId, target: Vec, input: Vec, + value: Balance, ) -> CallResult { - Pallet::::do_call(context, vm_id, source, target, input, false) + Pallet::::do_call(context, vm_id, source, target, input, value, false) } } -impl Pallet { +impl Pallet +where + T: Config, + T::Currency: Currency, +{ fn do_call( context: Context, vm_id: VmId, source: T::AccountId, target: Vec, input: Vec, + value: Balance, skip_execution: bool, ) -> CallResult { ensure!( @@ -121,8 +135,12 @@ impl Pallet { ); match vm_id { - VmId::Evm => Pallet::::evm_call(context, source, target, input, skip_execution), - VmId::Wasm => Pallet::::wasm_call(context, source, target, input, skip_execution), + VmId::Evm => { + Pallet::::evm_call(context, source, target, input, value, skip_execution) + } + VmId::Wasm => { + Pallet::::wasm_call(context, source, target, input, value, skip_execution) + } } } @@ -131,6 +149,7 @@ impl Pallet { source: T::AccountId, target: Vec, input: Vec, + value: Balance, skip_execution: bool, ) -> CallResult { log::trace!( @@ -150,7 +169,7 @@ impl Pallet { used_weight: WeightInfoOf::::evm_call_overheads(), })?; - let value = U256::zero(); + let value_u256 = U256::from(value); // With overheads, less weight is available. let weight_limit = context .weight_limit @@ -161,7 +180,7 @@ impl Pallet { let tx = CheckedEthereumTx { gas_limit, target: target_decoded, - value, + value: value_u256, input: bounded_input, maybe_access_list: None, }; @@ -210,6 +229,7 @@ impl Pallet { source: T::AccountId, target: Vec, input: Vec, + value: Balance, skip_execution: bool, ) -> CallResult { log::trace!( @@ -231,7 +251,6 @@ impl Pallet { let weight_limit = context .weight_limit .saturating_sub(WeightInfoOf::::wasm_call_overheads()); - let value = Default::default(); // Note the skip execution check should be exactly before `pallet_contracts::bare_call` // to benchmark the correct overheads. @@ -277,7 +296,8 @@ impl Pallet { source: T::AccountId, target: Vec, input: Vec, + value: Balance, ) -> CallResult { - Self::do_call(context, vm_id, source, target, input, true) + Self::do_call(context, vm_id, source, target, input, value, true) } } diff --git a/pallets/xvm/src/mock.rs b/pallets/xvm/src/mock.rs index a02472c3b7..eab38ffed3 100644 --- a/pallets/xvm/src/mock.rs +++ b/pallets/xvm/src/mock.rs @@ -175,7 +175,6 @@ impl pallet_xvm::Config for TestRuntime { pub(crate) type AccountId = AccountId32; pub(crate) type BlockNumber = u64; -pub(crate) type Balance = u128; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; diff --git a/precompiles/xvm/src/lib.rs b/precompiles/xvm/src/lib.rs index 78772c5244..62a7a898b6 100644 --- a/precompiles/xvm/src/lib.rs +++ b/precompiles/xvm/src/lib.rs @@ -38,7 +38,7 @@ mod tests; #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] pub enum Action { - XvmCall = "xvm_call(uint8,bytes,bytes)", + XvmCall = "xvm_call(uint8,bytes,bytes,uint256)", } /// A precompile that expose XVM related functions. @@ -74,7 +74,7 @@ where { fn xvm_call(handle: &mut impl PrecompileHandle) -> EvmResult { let mut input = handle.read_input()?; - input.expect_arguments(3)?; + input.expect_arguments(4)?; let vm_id = { let id = input.read::()?; @@ -90,9 +90,11 @@ where let call_to = input.read::()?.0; let call_input = input.read::()?.0; + //TODO: type + let value = input.read::()?; let from = R::AddressMapping::into_account_id(handle.context().caller); - let call_result = XC::call(xvm_context, vm_id, from, call_to, call_input); + let call_result = XC::call(xvm_context, vm_id, from, call_to, call_input, value); let used_weight = match &call_result { Ok(s) => s.used_weight, diff --git a/primitives/src/xvm.rs b/primitives/src/xvm.rs index e3ae06c526..5acf81766a 100644 --- a/primitives/src/xvm.rs +++ b/primitives/src/xvm.rs @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . +use crate::Balance; + use frame_support::weights::Weight; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -108,5 +110,6 @@ pub trait XvmCall { source: AccountId, target: Vec, input: Vec, + value: Balance, ) -> CallResult; } From e74b81a7432cd4cec836db95f595a7c83d2da5e4 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Sat, 29 Jul 2023 21:38:10 +1200 Subject: [PATCH 02/17] Fix precompile tests. --- pallets/xvm/Cargo.toml | 2 +- precompiles/xvm/src/lib.rs | 8 +++----- precompiles/xvm/src/mock.rs | 1 + precompiles/xvm/src/tests.rs | 3 +++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pallets/xvm/Cargo.toml b/pallets/xvm/Cargo.toml index e0650d3b82..6f1022bf24 100644 --- a/pallets/xvm/Cargo.toml +++ b/pallets/xvm/Cargo.toml @@ -39,7 +39,7 @@ pallet-timestamp = { workspace = true, features = ["std"] } sp-io = { workspace = true } [features] -default = ["std", "runtime-benchmarks"] +default = ["std"] std = [ "log/std", "parity-scale-codec/std", diff --git a/precompiles/xvm/src/lib.rs b/precompiles/xvm/src/lib.rs index 62a7a898b6..32476440aa 100644 --- a/precompiles/xvm/src/lib.rs +++ b/precompiles/xvm/src/lib.rs @@ -18,13 +18,12 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xvm::{Context, VmId, XvmCall}; +use astar_primitives::{xvm::{Context, VmId, XvmCall}, Balance}; use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::dispatch::Dispatchable; use pallet_evm::{AddressMapping, GasWeightMapping, Precompile}; use sp_runtime::codec::Encode; -use sp_std::marker::PhantomData; -use sp_std::prelude::*; +use sp_std::{marker::PhantomData, prelude::*}; use precompile_utils::{ revert, succeed, Bytes, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, @@ -90,8 +89,7 @@ where let call_to = input.read::()?.0; let call_input = input.read::()?.0; - //TODO: type - let value = input.read::()?; + let value = input.read::()?; let from = R::AddressMapping::into_account_id(handle.context().caller); let call_result = XC::call(xvm_context, vm_id, from, call_to, call_input, value); diff --git a/precompiles/xvm/src/mock.rs b/precompiles/xvm/src/mock.rs index bf209b7480..7115a43467 100644 --- a/precompiles/xvm/src/mock.rs +++ b/precompiles/xvm/src/mock.rs @@ -244,6 +244,7 @@ impl XvmCall for MockXvmWithArgsCheck { _source: AccountId, target: Vec, input: Vec, + _value: Balance, ) -> CallResult { ensure!( vm_id != VmId::Evm, diff --git a/precompiles/xvm/src/tests.rs b/precompiles/xvm/src/tests.rs index 844267fc40..dc09f55f99 100644 --- a/precompiles/xvm/src/tests.rs +++ b/precompiles/xvm/src/tests.rs @@ -23,6 +23,7 @@ use astar_primitives::xvm::CallError; use parity_scale_codec::Encode; use precompile_utils::testing::*; use precompile_utils::EvmDataWriter; +use sp_core::U256; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -50,6 +51,7 @@ fn wrong_argument_reverts() { .write(0u8) .write(Bytes(b"".to_vec())) .write(Bytes(b"".to_vec())) + .write(U256::one()) .build(), ) .expect_no_logs() @@ -68,6 +70,7 @@ fn correct_arguments_works() { .write(0x1Fu8) .write(Bytes(b"".to_vec())) .write(Bytes(b"".to_vec())) + .write(U256::one()) .build(), ) .expect_no_logs() From 02bfcb9bf27a650a3ae488df8986d5b8da2e8ca5 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Sat, 29 Jul 2023 21:43:13 +1200 Subject: [PATCH 03/17] Update types in CE & precompiles. --- chain-extensions/types/xvm/src/lib.rs | 4 ++-- precompiles/xvm/evm_sdk/XVM.sol | 8 +++++--- precompiles/xvm/evm_sdk/flipper.sol | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/chain-extensions/types/xvm/src/lib.rs b/chain-extensions/types/xvm/src/lib.rs index 014c1bcbb1..a003b74726 100644 --- a/chain-extensions/types/xvm/src/lib.rs +++ b/chain-extensions/types/xvm/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xvm::CallError; +use astar_primitives::{xvm::CallError, Balance}; use parity_scale_codec::{Decode, Encode}; use sp_std::vec::Vec; @@ -66,5 +66,5 @@ pub struct XvmCallArgs { /// Encoded call params pub input: Vec, /// Value to transfer - pub value: u128, + pub value: Balance, } diff --git a/precompiles/xvm/evm_sdk/XVM.sol b/precompiles/xvm/evm_sdk/XVM.sol index 652582ff08..6757a92ddf 100644 --- a/precompiles/xvm/evm_sdk/XVM.sol +++ b/precompiles/xvm/evm_sdk/XVM.sol @@ -6,15 +6,17 @@ pragma solidity ^0.8.0; interface XVM { /** * @dev Execute external VM call - * @param context - execution context - * @param to - call recepient + * @param vm_id - target VM id + * @param to - call recipient * @param input - SCALE-encoded call arguments + * @param value - value to transfer * @return success - operation outcome * @return data - output data if successful, error data on error */ function xvm_call( - bytes calldata context, + uint8 calldata vm_id, bytes calldata to, bytes calldata input + uint256 value ) external returns (bool success, bytes memory data); } diff --git a/precompiles/xvm/evm_sdk/flipper.sol b/precompiles/xvm/evm_sdk/flipper.sol index fcbfdd3d40..724fc419df 100644 --- a/precompiles/xvm/evm_sdk/flipper.sol +++ b/precompiles/xvm/evm_sdk/flipper.sol @@ -5,6 +5,7 @@ interface XVM { uint8 calldata vm_id, bytes calldata to, bytes calldata input, + uint256 calldata value, ) external; } @@ -13,6 +14,6 @@ library Flipper { function flip(bytes to) { bytes input = "0xcafecafe"; - XVM_PRECOMPILE.xvm_call(0x1F, to, input); + XVM_PRECOMPILE.xvm_call(0x1F, to, input, 1000000); } } From 1317da5248d6d4d91994ef6e9d59e03218048562 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Sat, 29 Jul 2023 22:52:55 +1200 Subject: [PATCH 04/17] Add EVM call tests for XVM. --- Cargo.lock | 3 + pallets/xvm/Cargo.toml | 1 + pallets/xvm/src/lib.rs | 10 +- pallets/xvm/src/mock.rs | 21 ++++- pallets/xvm/src/tests.rs | 160 ++++++++++++++++++++++++++++++++ precompiles/xvm/evm_sdk/XVM.sol | 2 +- precompiles/xvm/src/lib.rs | 5 +- primitives/src/xvm.rs | 1 + tests/integration/Cargo.toml | 5 +- tests/integration/src/lib.rs | 3 + tests/integration/src/xvm.rs | 29 ++++++ 11 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 pallets/xvm/src/tests.rs create mode 100644 tests/integration/src/xvm.rs diff --git a/Cargo.lock b/Cargo.lock index 004fee0ca2..20fb554e53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4456,6 +4456,7 @@ dependencies = [ name = "integration-tests" version = "0.1.0" dependencies = [ + "astar-primitives", "astar-runtime", "frame-support", "frame-system", @@ -4463,6 +4464,7 @@ dependencies = [ "pallet-dapps-staking", "pallet-proxy", "pallet-utility", + "parity-scale-codec", "shibuya-runtime", "shiden-runtime", "sp-core", @@ -8238,6 +8240,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "hex", "log", "pallet-balances", "pallet-contracts", diff --git a/pallets/xvm/Cargo.toml b/pallets/xvm/Cargo.toml index 6f1022bf24..6b83517d55 100644 --- a/pallets/xvm/Cargo.toml +++ b/pallets/xvm/Cargo.toml @@ -33,6 +33,7 @@ astar-primitives = { workspace = true } [dev-dependencies] fp-evm = { workspace = true } +hex = { workspace = true } pallet-balances = { workspace = true, features = ["std"] } pallet-insecure-randomness-collective-flip = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true, features = ["std"] } diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index f0be4bf96e..825ca7f189 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -45,7 +45,7 @@ use frame_support::{ use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_evm::GasWeightMapping; use parity_scale_codec::Decode; -use sp_core::U256; +use sp_core::{H160, U256}; use sp_runtime::traits::StaticLookup; use sp_std::{marker::PhantomData, prelude::*}; @@ -64,6 +64,7 @@ pub mod weights; pub use weights::WeightInfo; mod mock; +mod tests; pub use pallet::*; @@ -158,6 +159,13 @@ where context, source, target, input, ); + ensure!( + target.len() == H160::len_bytes(), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight: WeightInfoOf::::evm_call_overheads(), + } + ); let target_decoded = Decode::decode(&mut target.as_ref()).map_err(|_| CallErrorWithWeight { error: CallError::InvalidTarget, diff --git a/pallets/xvm/src/mock.rs b/pallets/xvm/src/mock.rs index eab38ffed3..9783b71297 100644 --- a/pallets/xvm/src/mock.rs +++ b/pallets/xvm/src/mock.rs @@ -36,6 +36,7 @@ use sp_runtime::{ traits::{AccountIdLookup, BlakeTwo256}, AccountId32, }; +use sp_std::cell::RefCell; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = @@ -131,12 +132,22 @@ impl astar_primitives::ethereum_checked::AccountMapping for HashedAcc } } +thread_local! { + static TRANSACTED: RefCell> = RefCell::new(None); +} + pub struct MockEthereumTransact; +impl MockEthereumTransact { + pub(crate) fn assert_transacted(source: H160, checked_tx: CheckedEthereumTx) { + assert!(TRANSACTED.with(|v| v.eq(&RefCell::new(Some((source, checked_tx)))))); + } +} impl CheckedEthereumTransact for MockEthereumTransact { fn xvm_transact( - _source: H160, - _checked_tx: CheckedEthereumTx, + source: H160, + checked_tx: CheckedEthereumTx, ) -> Result<(PostDispatchInfo, EvmCallInfo), DispatchErrorWithPostInfo> { + TRANSACTED.with(|v| *v.borrow_mut() = Some((source, checked_tx))); Ok(( PostDispatchInfo { actual_weight: Default::default(), @@ -170,7 +181,7 @@ impl pallet_xvm::Config for TestRuntime { type GasWeightMapping = MockGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = MockEthereumTransact; - type WeightInfo = (); + type WeightInfo = weights::SubstrateWeight; } pub(crate) type AccountId = AccountId32; @@ -195,12 +206,16 @@ construct_runtime!( } ); +pub(crate) const ALICE: AccountId = AccountId32::new([0u8; 32]); + #[derive(Default)] pub struct ExtBuilder; impl ExtBuilder { #[allow(dead_code)] pub fn build(self) -> TestExternalities { + TRANSACTED.with(|v| *v.borrow_mut() = None); + let t = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs new file mode 100644 index 0000000000..27ee473c04 --- /dev/null +++ b/pallets/xvm/src/tests.rs @@ -0,0 +1,160 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +#![cfg(test)] + +use super::*; +use mock::*; + +use frame_support::{assert_noop, assert_ok, weights::Weight}; +use parity_scale_codec::Encode; +use sp_core::H160; +use sp_runtime::MultiAddress; + +#[test] +fn calling_into_same_vm_is_not_allowed() { + ExtBuilder::default().build().execute_with(|| { + // Calling EVM from EVM + let evm_context = Context { + source_vm_id: VmId::Evm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let evm_vm_id = VmId::Evm; + let evm_target = H160::repeat_byte(1).encode(); + let input = vec![1, 2, 3]; + let value = 1_000_000u128; + let evm_used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); + assert_noop!( + Xvm::call( + evm_context, + evm_vm_id, + ALICE, + evm_target, + input.clone(), + value + ), + CallErrorWithWeight { + error: CallError::SameVmCallNotAllowed, + used_weight: evm_used_weight + }, + ); + + // Calling WASM from WASM + let wasm_context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let wasm_vm_id = VmId::Wasm; + let wasm_target = MultiAddress::::Id(ALICE).encode(); + let wasm_used_weight: Weight = + weights::SubstrateWeight::::wasm_call_overheads(); + assert_noop!( + Xvm::call(wasm_context, wasm_vm_id, ALICE, wasm_target, input, value), + CallErrorWithWeight { + error: CallError::SameVmCallNotAllowed, + used_weight: wasm_used_weight + }, + ); + }); +} + +#[test] +fn evm_call_works() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Evm; + let target = H160::repeat_byte(0xFF); + let input = vec![1; 65_536]; + let value = 1_000_000u128; + let used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); + + // Invalid target + assert_noop!( + Xvm::call( + context.clone(), + vm_id, + ALICE, + ALICE.encode(), + input.clone(), + value + ), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight + }, + ); + assert_noop!( + Xvm::call( + context.clone(), + vm_id, + ALICE, + vec![1, 2, 3], + input.clone(), + value + ), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight + }, + ); + // Input too large + assert_noop!( + Xvm::call( + context.clone(), + vm_id, + ALICE, + target.encode(), + vec![1; 65_537], + value + ), + CallErrorWithWeight { + error: CallError::InputTooLarge, + used_weight + }, + ); + + assert_ok!(Xvm::call( + context, + vm_id, + ALICE, + target.encode(), + input.clone(), + value + )); + let source = Decode::decode( + &mut hex::decode("f0bd9ffde7f9f4394d8cc1d86bf24d87e5d5a9a9") + .unwrap() + .as_ref(), + ) + .unwrap(); + MockEthereumTransact::assert_transacted( + source, + CheckedEthereumTx { + gas_limit: U256::from(182000), + target: H160::repeat_byte(0xFF), + value: U256::from(value), + input: BoundedVec::>::try_from(input) + .unwrap(), + maybe_access_list: None, + }, + ); + }); +} diff --git a/precompiles/xvm/evm_sdk/XVM.sol b/precompiles/xvm/evm_sdk/XVM.sol index 6757a92ddf..050c076281 100644 --- a/precompiles/xvm/evm_sdk/XVM.sol +++ b/precompiles/xvm/evm_sdk/XVM.sol @@ -17,6 +17,6 @@ interface XVM { uint8 calldata vm_id, bytes calldata to, bytes calldata input - uint256 value + uint256 calldata value ) external returns (bool success, bytes memory data); } diff --git a/precompiles/xvm/src/lib.rs b/precompiles/xvm/src/lib.rs index 32476440aa..5ccdfcb342 100644 --- a/precompiles/xvm/src/lib.rs +++ b/precompiles/xvm/src/lib.rs @@ -18,7 +18,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::{xvm::{Context, VmId, XvmCall}, Balance}; +use astar_primitives::{ + xvm::{Context, VmId, XvmCall}, + Balance, +}; use fp_evm::{PrecompileHandle, PrecompileOutput}; use frame_support::dispatch::Dispatchable; use pallet_evm::{AddressMapping, GasWeightMapping, Precompile}; diff --git a/primitives/src/xvm.rs b/primitives/src/xvm.rs index 5acf81766a..bb6a64f49d 100644 --- a/primitives/src/xvm.rs +++ b/primitives/src/xvm.rs @@ -104,6 +104,7 @@ pub trait XvmCall { /// - `source`: Caller Id. /// - `target`: Target contract address. /// - `input`: call input data. + /// - `value`: value to transfer. fn call( context: Context, vm_id: VmId, diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 9e291b6295..05ac94d1ec 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -8,6 +8,8 @@ homepage.workspace = true repository.workspace = true [dependencies] +parity-scale-codec = { workspace = true } + # frame dependencies frame-support = { workspace = true } frame-system = { workspace = true } @@ -19,7 +21,8 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -# runtime +# astar dependencies +astar-primitives = { workspace = true } astar-runtime = { workspace = true, features = ["std"], optional = true } shibuya-runtime = { workspace = true, features = ["std"], optional = true } shiden-runtime = { workspace = true, features = ["std"], optional = true } diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 8757043330..6b01e7cf77 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -25,3 +25,6 @@ mod setup; #[cfg(any(feature = "shibuya", feature = "shiden", feature = "astar"))] mod proxy; + +#[cfg(feature = "shibuya")] +mod xvm; diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs new file mode 100644 index 0000000000..21ded51e7e --- /dev/null +++ b/tests/integration/src/xvm.rs @@ -0,0 +1,29 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::setup::*; + +// use astar_primitives::xvm::{Context, VmId, XvmCall}; +// use frame_support::weights::Weight; +// use parity_scale_codec::Encode; +// use sp_core::H160; + +#[test] +fn cross_vm_payable_call() { + new_test_ext().execute_with(|| {}); +} From 7ab559eba04045efa5ed276b18b020eb6c81aae2 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Sat, 29 Jul 2023 23:03:40 +1200 Subject: [PATCH 05/17] New type idiom for EthereumTxInput. --- pallets/ethereum-checked/src/benchmarking.rs | 4 ++-- pallets/ethereum-checked/src/tests.rs | 9 ++++----- pallets/xvm/src/lib.rs | 17 ++++++----------- pallets/xvm/src/tests.rs | 3 +-- primitives/src/ethereum_checked.rs | 4 +++- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/pallets/ethereum-checked/src/benchmarking.rs b/pallets/ethereum-checked/src/benchmarking.rs index b45458afe6..6abf9f8075 100644 --- a/pallets/ethereum-checked/src/benchmarking.rs +++ b/pallets/ethereum-checked/src/benchmarking.rs @@ -18,7 +18,7 @@ use super::*; -use astar_primitives::ethereum_checked::MAX_ETHEREUM_TX_INPUT_SIZE; +use astar_primitives::ethereum_checked::EthereumTxInput; use frame_benchmarking::v2::*; #[benchmarks] @@ -31,7 +31,7 @@ mod benchmarks { let target = H160::from_slice(&hex::decode("dfb975d018f03994a3b943808e3aa0964bd78463").unwrap()); // Calling `store(3)` - let input = BoundedVec::>::try_from( + let input = EthereumTxInput::try_from( hex::decode("6057361d0000000000000000000000000000000000000000000000000000000000000003") .unwrap(), ) diff --git a/pallets/ethereum-checked/src/tests.rs b/pallets/ethereum-checked/src/tests.rs index 7d77601fd9..fd227a581e 100644 --- a/pallets/ethereum-checked/src/tests.rs +++ b/pallets/ethereum-checked/src/tests.rs @@ -21,14 +21,13 @@ use super::*; use mock::*; -use astar_primitives::ethereum_checked::MAX_ETHEREUM_TX_INPUT_SIZE; +use astar_primitives::ethereum_checked::EthereumTxInput; use ethereum::{ReceiptV3, TransactionV2 as Transaction}; -use frame_support::{assert_noop, assert_ok, traits::ConstU32}; +use frame_support::{assert_noop, assert_ok}; use sp_runtime::DispatchError; -fn bounded_input(data: &'static str) -> BoundedVec> { - BoundedVec::>::try_from(hex::decode(data).unwrap()) - .unwrap() +fn bounded_input(data: &'static str) -> EthereumTxInput { + EthereumTxInput::try_from(hex::decode(data).unwrap()).unwrap() } #[test] diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index 825ca7f189..659f21148a 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -37,11 +37,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - ensure, - traits::{ConstU32, Currency}, - BoundedVec, -}; +use frame_support::{ensure, traits::Currency}; use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_evm::GasWeightMapping; use parity_scale_codec::Decode; @@ -51,7 +47,7 @@ use sp_std::{marker::PhantomData, prelude::*}; use astar_primitives::{ ethereum_checked::{ - AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, MAX_ETHEREUM_TX_INPUT_SIZE, + AccountMapping, CheckedEthereumTransact, CheckedEthereumTx, EthereumTxInput, }, xvm::{CallError, CallErrorWithWeight, CallInfo, CallResult, Context, VmId, XvmCall}, Balance, @@ -171,11 +167,10 @@ where error: CallError::InvalidTarget, used_weight: WeightInfoOf::::evm_call_overheads(), })?; - let bounded_input = BoundedVec::>::try_from(input) - .map_err(|_| CallErrorWithWeight { - error: CallError::InputTooLarge, - used_weight: WeightInfoOf::::evm_call_overheads(), - })?; + let bounded_input = EthereumTxInput::try_from(input).map_err(|_| CallErrorWithWeight { + error: CallError::InputTooLarge, + used_weight: WeightInfoOf::::evm_call_overheads(), + })?; let value_u256 = U256::from(value); // With overheads, less weight is available. diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs index 27ee473c04..0c948ac23b 100644 --- a/pallets/xvm/src/tests.rs +++ b/pallets/xvm/src/tests.rs @@ -151,8 +151,7 @@ fn evm_call_works() { gas_limit: U256::from(182000), target: H160::repeat_byte(0xFF), value: U256::from(value), - input: BoundedVec::>::try_from(input) - .unwrap(), + input: EthereumTxInput::try_from(input).unwrap(), maybe_access_list: None, }, ); diff --git a/primitives/src/ethereum_checked.rs b/primitives/src/ethereum_checked.rs index e50edc318c..29af13c229 100644 --- a/primitives/src/ethereum_checked.rs +++ b/primitives/src/ethereum_checked.rs @@ -35,6 +35,8 @@ use sp_std::{prelude::*, result::Result}; /// Max Ethereum tx input size: 65_536 bytes pub const MAX_ETHEREUM_TX_INPUT_SIZE: u32 = 2u32.pow(16); +pub type EthereumTxInput = BoundedVec>; + /// The checked Ethereum transaction. Only contracts `call` is support(no `create`). #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct CheckedEthereumTx { @@ -45,7 +47,7 @@ pub struct CheckedEthereumTx { /// Amount to transfer. pub value: U256, /// Input of a contract call. - pub input: BoundedVec>, + pub input: EthereumTxInput, /// Optional access list, specified in EIP-2930. pub maybe_access_list: Option)>>, } From 8f785c223d7c2ff23b1c19686d83ab5a573c4c8b Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Mon, 31 Jul 2023 23:42:49 +1200 Subject: [PATCH 06/17] Add XVM integration tests. --- Cargo.lock | 5 + pallets/xvm/src/lib.rs | 15 +- pallets/xvm/src/mock.rs | 1 + precompiles/xvm/evm_sdk/XVM.sol | 8 +- precompiles/xvm/evm_sdk/flipper.sol | 6 +- runtime/local/src/lib.rs | 1 + runtime/shibuya/src/lib.rs | 1 + tests/integration/Cargo.toml | 8 + tests/integration/resource/payable.json | 287 ++++++++++++++++++++++++ tests/integration/resource/payable.wasm | Bin 0 -> 11235 bytes tests/integration/src/setup.rs | 24 ++ tests/integration/src/xvm.rs | 252 ++++++++++++++++++++- 12 files changed, 593 insertions(+), 15 deletions(-) create mode 100644 tests/integration/resource/payable.json create mode 100644 tests/integration/resource/payable.wasm diff --git a/Cargo.lock b/Cargo.lock index 20fb554e53..7aeab46cd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4460,8 +4460,13 @@ dependencies = [ "astar-runtime", "frame-support", "frame-system", + "hex", "pallet-balances", + "pallet-contracts", + "pallet-contracts-primitives", "pallet-dapps-staking", + "pallet-ethereum-checked", + "pallet-evm", "pallet-proxy", "pallet-utility", "parity-scale-codec", diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index 659f21148a..9e3a7f6b20 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -37,7 +37,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ensure, traits::Currency}; +use frame_support::{ + ensure, + traits::{Currency, Get}, +}; use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_evm::GasWeightMapping; use parity_scale_codec::Decode; @@ -84,6 +87,9 @@ pub mod pallet { /// `CheckedEthereumTransact` implementation. type EthereumTransact: CheckedEthereumTransact; + /// Existential deposit of currency for payable calls. + type ExistentialDeposit: Get; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -250,6 +256,11 @@ where T::Lookup::lookup(decoded).map_err(|_| error) }?; + // TODO: maybe max(source_balance - existential_deposit, value)? might be overkill + // Adjust value to account for existential deposit, as `pallet-contracts` + // respects it on transfer. + let adjusted_value = value.saturating_sub(T::ExistentialDeposit::get()); + // With overheads, less weight is available. let weight_limit = context .weight_limit @@ -267,7 +278,7 @@ where let call_result = pallet_contracts::Pallet::::bare_call( source, dest, - value, + adjusted_value, weight_limit, None, input, diff --git a/pallets/xvm/src/mock.rs b/pallets/xvm/src/mock.rs index 9783b71297..bde6a54aa2 100644 --- a/pallets/xvm/src/mock.rs +++ b/pallets/xvm/src/mock.rs @@ -181,6 +181,7 @@ impl pallet_xvm::Config for TestRuntime { type GasWeightMapping = MockGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = MockEthereumTransact; + type ExistentialDeposit = ConstU128<2>; type WeightInfo = weights::SubstrateWeight; } diff --git a/precompiles/xvm/evm_sdk/XVM.sol b/precompiles/xvm/evm_sdk/XVM.sol index 050c076281..5008896afe 100644 --- a/precompiles/xvm/evm_sdk/XVM.sol +++ b/precompiles/xvm/evm_sdk/XVM.sol @@ -14,9 +14,9 @@ interface XVM { * @return data - output data if successful, error data on error */ function xvm_call( - uint8 calldata vm_id, + uint8 vm_id, bytes calldata to, - bytes calldata input - uint256 calldata value - ) external returns (bool success, bytes memory data); + bytes calldata input, + uint256 value + ) external payable returns (bool success, bytes memory data); } diff --git a/precompiles/xvm/evm_sdk/flipper.sol b/precompiles/xvm/evm_sdk/flipper.sol index 724fc419df..058f649baa 100644 --- a/precompiles/xvm/evm_sdk/flipper.sol +++ b/precompiles/xvm/evm_sdk/flipper.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.0; interface XVM { function xvm_call( - uint8 calldata vm_id, + uint8 vm_id, bytes calldata to, bytes calldata input, - uint256 calldata value, - ) external; + uint256 value + ) external payable returns (bool success, bytes memory data); } library Flipper { diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 765dc47b84..564907d2bb 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -475,6 +475,7 @@ impl pallet_xvm::Config for Runtime { type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = EthereumChecked; + type ExistentialDeposit = ExistentialDeposit; type WeightInfo = pallet_xvm::weights::SubstrateWeight; } diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index bc94244c62..d33627a336 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -790,6 +790,7 @@ impl pallet_xvm::Config for Runtime { type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = EthereumChecked; + type ExistentialDeposit = ExistentialDeposit; type WeightInfo = pallet_xvm::weights::SubstrateWeight; } diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 05ac94d1ec..26ca026e01 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -8,12 +8,18 @@ homepage.workspace = true repository.workspace = true [dependencies] +hex = { workspace = true } parity-scale-codec = { workspace = true } +# frontier +pallet-evm = { workspace = true } + # frame dependencies frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } +pallet-contracts = { workspace = true } +pallet-contracts-primitives = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-proxy = { workspace = true } pallet-utility = { workspace = true } @@ -22,6 +28,8 @@ sp-io = { workspace = true } sp-runtime = { workspace = true } # astar dependencies +pallet-ethereum-checked = { workspace = true } + astar-primitives = { workspace = true } astar-runtime = { workspace = true, features = ["std"], optional = true } shibuya-runtime = { workspace = true, features = ["std"], optional = true } diff --git a/tests/integration/resource/payable.json b/tests/integration/resource/payable.json new file mode 100644 index 0000000000..5be916d5c7 --- /dev/null +++ b/tests/integration/resource/payable.json @@ -0,0 +1,287 @@ +{ + "source": { + "hash": "0xcfbb81744c83a08b460d939a434f4c9876a6901f6a4a48e1516004632c5d02f0", + "language": "ink! 4.2.1", + "compiler": "rustc 1.68.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "3.0.1", + "rust_toolchain": "nightly-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "payable", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 0 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 3 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 6 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 9 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 10 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 7 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 8 + } + }, + "events": [], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 2 + }, + "messages": [ + { + "args": [], + "default": false, + "docs": [], + "label": "deposit", + "mutates": false, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 0 + }, + "selector": "0x0000002a" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [], + "name": "Payable" + } + }, + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 4, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 4, + "type": { + "def": { + "array": { + "len": 32, + "type": 5 + } + } + } + }, + { + "id": 5, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 6, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 7, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 4, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 10, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": "4" +} \ No newline at end of file diff --git a/tests/integration/resource/payable.wasm b/tests/integration/resource/payable.wasm new file mode 100644 index 0000000000000000000000000000000000000000..34587fc018b655f28846a57d186d2de6b91b7c59 GIT binary patch literal 11235 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 H160 { + h160_from(ALICE) +} + +/// `AccountId32` mapped from `alith()`. +pub fn alicia() -> AccountId32 { + account_id_from(alith()) +} + +/// Convert `H160` to `AccountId32`. +pub fn account_id_from(address: H160) -> AccountId32 { + ::AddressMapping::into_account_id(address) +} + +/// Convert `AccountId32` to `H160`. +pub fn h160_from(account_id: AccountId32) -> H160 { + ::AccountMapping::into_h160(account_id) +} + pub const INITIAL_AMOUNT: u128 = 100_000 * UNIT; pub type SystemError = frame_system::Error; @@ -107,6 +130,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (ALICE, INITIAL_AMOUNT), (BOB, INITIAL_AMOUNT), (CAT, INITIAL_AMOUNT), + (alicia(), INITIAL_AMOUNT), ]) .build() } diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index 21ded51e7e..74afb1d44c 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -18,12 +18,252 @@ use crate::setup::*; -// use astar_primitives::xvm::{Context, VmId, XvmCall}; -// use frame_support::weights::Weight; -// use parity_scale_codec::Encode; -// use sp_core::H160; +use astar_primitives::xvm::{Context, VmId, XvmCall}; +use frame_support::weights::Weight; +use pallet_contracts::{CollectEvents, DebugInfo}; +use pallet_contracts_primitives::Code; +use parity_scale_codec::Encode; +use sp_core::{H160, H256, U256}; +use sp_runtime::MultiAddress; + +/* + +pragma solidity >=0.8.2 <0.9.0; + +contract Payable { + address payable public owner; + + constructor() payable { + owner = payable(msg.sender); + } + + // 0xd0e30db0 + function deposit() public payable {} + + // 0x3ccfd60b + function withdraw() public { + uint amount = address(this).balance; + (bool success, ) = owner.call{value: amount}(""); + require(success, "Failed to withdraw Ether"); + } +} + + */ +const EVM_PAYABLE: &str = "6080604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102d6806100536000396000f3fe6080604052600436106100345760003560e01c80633ccfd60b146100395780638da5cb5b14610050578063d0e30db01461007b575b600080fd5b34801561004557600080fd5b5061004e610085565b005b34801561005c57600080fd5b5061006561015b565b60405161007291906101c2565b60405180910390f35b61008361017f565b005b600047905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16826040516100d19061020e565b60006040518083038185875af1925050503d806000811461010e576040519150601f19603f3d011682016040523d82523d6000602084013e610113565b606091505b5050905080610157576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161014e90610280565b60405180910390fd5b5050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101ac82610181565b9050919050565b6101bc816101a1565b82525050565b60006020820190506101d760008301846101b3565b92915050565b600081905092915050565b50565b60006101f86000836101dd565b9150610203826101e8565b600082019050919050565b6000610219826101eb565b9150819050919050565b600082825260208201905092915050565b7f4661696c656420746f2077697468647261772045746865720000000000000000600082015250565b600061026a601883610223565b915061027582610234565b602082019050919050565b600060208201905081810360008301526102998161025d565b905091905056fea2646970667358221220bd8883b6a524d12ac9c29f105fdd1a0221a0436a79002f2a04e69d252596a62a64736f6c63430008120033"; +const EVM_PAYABLE_ADDR: &str = "4a15f25194b60fd07860b3a20a060fd003cae5a6"; + +fn evm_payable_addr() -> Vec { + hex::decode(EVM_PAYABLE_ADDR).unwrap() +} +fn evm_payable_addr_h160() -> H160 { + H160::from_slice(evm_payable_addr().as_slice()) +} + +/* + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod payable { + #[ink(storage)] + pub struct Payable {} + + impl Payable { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + #[ink(message, payable, selector = 42)] + pub fn deposit(&self) {} + } +} + +*/ +fn wasm_payable() -> std::io::Result> { + let path = "resource/payable.wasm"; + std::fs::read(path) +} + +/* + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.8.2 <0.9.0; + +interface XVM { + function xvm_call( + uint8 vm_id, + bytes calldata to, + bytes calldata input, + uint256 value + ) external payable returns (bool success, bytes memory data); +} + +contract CallXVMPayble { + function call_xvm_payable(bytes calldata to, bytes calldata input, uint256 value) external payable returns (bool success, bytes memory data) { + return XVM(0x0000000000000000000000000000000000005005).xvm_call(0x1F, to, input, value); + } +} + + */ +const CALL_WASM_PAYBLE: &str = "608060405234801561001057600080fd5b506105e6806100206000396000f3fe60806040526004361061001e5760003560e01c80634012b91414610023575b600080fd5b61003d600480360381019061003891906101a3565b610054565b60405161004b9291906102e3565b60405180910390f35b6000606061500573ffffffffffffffffffffffffffffffffffffffff1663e5d9bac0601f89898989896040518763ffffffff1660e01b815260040161009e969594939291906103b0565b6000604051808303816000875af11580156100bd573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906100e69190610554565b915091509550959350505050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f84011261012d5761012c610108565b5b8235905067ffffffffffffffff81111561014a5761014961010d565b5b60208301915083600182028301111561016657610165610112565b5b9250929050565b6000819050919050565b6101808161016d565b811461018b57600080fd5b50565b60008135905061019d81610177565b92915050565b6000806000806000606086880312156101bf576101be6100fe565b5b600086013567ffffffffffffffff8111156101dd576101dc610103565b5b6101e988828901610117565b9550955050602086013567ffffffffffffffff81111561020c5761020b610103565b5b61021888828901610117565b9350935050604061022b8882890161018e565b9150509295509295909350565b60008115159050919050565b61024d81610238565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561028d578082015181840152602081019050610272565b60008484015250505050565b6000601f19601f8301169050919050565b60006102b582610253565b6102bf818561025e565b93506102cf81856020860161026f565b6102d881610299565b840191505092915050565b60006040820190506102f86000830185610244565b818103602083015261030a81846102aa565b90509392505050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b600061034f61034a61034584610313565b61032a565b61031d565b9050919050565b61035f81610334565b82525050565b82818337600083830152505050565b6000610380838561025e565b935061038d838584610365565b61039683610299565b840190509392505050565b6103aa8161016d565b82525050565b60006080820190506103c56000830189610356565b81810360208301526103d8818789610374565b905081810360408301526103ed818587610374565b90506103fc60608301846103a1565b979650505050505050565b61041081610238565b811461041b57600080fd5b50565b60008151905061042d81610407565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61047082610299565b810181811067ffffffffffffffff8211171561048f5761048e610438565b5b80604052505050565b60006104a26100f4565b90506104ae8282610467565b919050565b600067ffffffffffffffff8211156104ce576104cd610438565b5b6104d782610299565b9050602081019050919050565b60006104f76104f2846104b3565b610498565b90508281526020810184848401111561051357610512610433565b5b61051e84828561026f565b509392505050565b600082601f83011261053b5761053a610108565b5b815161054b8482602086016104e4565b91505092915050565b6000806040838503121561056b5761056a6100fe565b5b60006105798582860161041e565b925050602083015167ffffffffffffffff81111561059a57610599610103565b5b6105a685828601610526565b915050925092905056fea264697066735822122047908cecfa9ace275a4ba96e787bb0d1541ec599c370983693c6cd9c1f5b7dbe64736f6c63430008120033"; +const CALL_WASM_PAYBLE_ADDR: &str = "79b3ca9f410de3d98c1dafac0e291871f752bc7a"; + +fn call_wasm_payable_addr() -> Vec { + hex::decode(CALL_WASM_PAYBLE_ADDR).unwrap() +} +fn call_wasm_payable_addr_h160() -> H160 { + H160::from_slice(call_wasm_payable_addr().as_slice()) +} + +fn deploy_evm_payable() { + assert_ok!(EVM::create2( + RuntimeOrigin::root(), + alith(), + hex::decode(EVM_PAYABLE).unwrap(), + H256::zero(), + U256::zero(), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(pallet_evm::Event::Created { + address: evm_payable_addr_h160(), + })); +} + +fn deploy_wasm_payable() -> AccountId32 { + let wasm_payable = wasm_payable().unwrap(); + let instantiate_result = Contracts::bare_instantiate( + ALICE, + 0, + Weight::from_parts(10_000_000_000, 1024 * 1024), + None, + Code::Upload(wasm_payable), + // `new` constructor + hex::decode("9bae9d5e").unwrap(), + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ); + let wasm_payable_addr = instantiate_result.result.unwrap().account_id; + // On instantiation, the contract got exising deposit. + assert_eq!( + Balances::free_balance(&wasm_payable_addr), + ExistentialDeposit::get(), + ); + wasm_payable_addr +} #[test] -fn cross_vm_payable_call() { - new_test_ext().execute_with(|| {}); +fn evm_payable_call_via_xvm_works() { + new_test_ext().execute_with(|| { + deploy_evm_payable(); + + let value = 1_000_000_000; + assert_ok!(Xvm::call( + Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000_000, 1024 * 1024), + }, + VmId::Evm, + ALICE, + evm_payable_addr(), + // Calling `deposit` + hex::decode("d0e30db0").unwrap(), + value, + )); + assert_eq!( + Balances::free_balance(account_id_from(evm_payable_addr_h160())), + value + ); + + assert_ok!(Xvm::call( + Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(10_000_000_000, 1024 * 1024), + }, + VmId::Evm, + ALICE, + evm_payable_addr(), + // `Calling withdraw` + hex::decode("3ccfd60b").unwrap(), + 0, + )); + assert_eq!( + Balances::free_balance(account_id_from(evm_payable_addr_h160())), + ExistentialDeposit::get(), + ); + }); +} + +#[test] +fn wasm_payable_call_via_xvm_works() { + new_test_ext().execute_with(|| { + let contract_addr = deploy_wasm_payable(); + + let value = 1_000_000_000; + assert_ok!(Xvm::call( + Context { + source_vm_id: VmId::Evm, + weight_limit: Weight::from_parts(10_000_000_000, 1024 * 1024), + }, + VmId::Wasm, + ALICE, + MultiAddress::::Id(contract_addr.clone()).encode(), + // Calling `deposit` + hex::decode("0000002a").unwrap(), + value + )); + assert_eq!(Balances::free_balance(contract_addr.clone()), value,); + }); +} + +#[test] +fn calling_wasm_payable_from_evm_works() { + new_test_ext().execute_with(|| { + let wasm_payable_addr = deploy_wasm_payable(); + + assert_ok!(EVM::create2( + RuntimeOrigin::root(), + alith(), + hex::decode(CALL_WASM_PAYBLE).unwrap(), + H256::zero(), + U256::zero(), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(pallet_evm::Event::Created { + address: call_wasm_payable_addr_h160(), + })); + + let value = 1_000_000_000; + assert_ok!(EVM::call( + RuntimeOrigin::root(), + alith(), + call_wasm_payable_addr_h160(), + // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c + // input: 0x0000002a (deposit) + // value: 1000000000 + hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").unwrap(), + U256::from(value), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + // `pallet-contracts` respects the existential deposit of caller. The actual amount + // it got is `value - ExistentialDeposit`. Adding to its existing balance and we got `value`. + assert_eq!( + Balances::free_balance(&wasm_payable_addr), + value, + ); + }); } From 682883ffee9a87a542352b1f6dc725019f796b4a Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Wed, 2 Aug 2023 21:49:49 +1200 Subject: [PATCH 07/17] More XVM integration tests. --- chain-extensions/types/xvm/src/lib.rs | 3 +- chain-extensions/xvm/src/lib.rs | 30 +- pallets/xvm/src/lib.rs | 8 +- primitives/src/xvm.rs | 2 - .../resource/call_xvm_payable.json | 438 ++++++++++++++++++ .../resource/call_xvm_payable.wasm | Bin 0 -> 13469 bytes tests/integration/src/setup.rs | 3 + tests/integration/src/xvm.rs | 164 +++++-- 8 files changed, 579 insertions(+), 69 deletions(-) create mode 100644 tests/integration/resource/call_xvm_payable.json create mode 100644 tests/integration/resource/call_xvm_payable.wasm diff --git a/chain-extensions/types/xvm/src/lib.rs b/chain-extensions/types/xvm/src/lib.rs index a003b74726..c026fc52dc 100644 --- a/chain-extensions/types/xvm/src/lib.rs +++ b/chain-extensions/types/xvm/src/lib.rs @@ -41,8 +41,7 @@ impl From for XvmExecutionResult { SameVmCallNotAllowed => 2, InvalidTarget => 3, InputTooLarge => 4, - BadOrigin => 5, - ExecutionFailed(_) => 6, + ExecutionFailed(_) => 5, }; Self::Err(error_code) } diff --git a/chain-extensions/xvm/src/lib.rs b/chain-extensions/xvm/src/lib.rs index 4e3d749b8f..6b3d4c58ca 100644 --- a/chain-extensions/xvm/src/lib.rs +++ b/chain-extensions/xvm/src/lib.rs @@ -18,12 +18,9 @@ #![cfg_attr(not(feature = "std"), no_std)] -use astar_primitives::xvm::{CallError, Context, VmId, XvmCall}; +use astar_primitives::xvm::{Context, VmId, XvmCall}; use frame_support::dispatch::Encode; -use pallet_contracts::{ - chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal}, - Origin, -}; +use pallet_contracts::chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal}; use sp_runtime::DispatchError; use sp_std::marker::PhantomData; use xvm_chain_extension_types::{XvmCallArgs, XvmExecutionResult}; @@ -75,19 +72,6 @@ where // So we will charge a 32KB dummy value as a temporary replacement. let charged_weight = env.charge_weight(weight_limit.set_proof_size(32 * 1024))?; - let caller = match env.ext().caller().clone() { - Origin::Signed(address) => address, - Origin::Root => { - log::trace!( - target: "xvm-extension::xvm_call", - "root origin not supported" - ); - return Ok(RetVal::Converging( - XvmExecutionResult::from(CallError::BadOrigin).into(), - )); - } - }; - let XvmCallArgs { vm_id, to, @@ -95,13 +79,15 @@ where value, } = env.read_as_unbounded(env.in_len())?; - let _origin_address = env.ext().address().clone(); - let _value = env.ext().value_transferred(); + // Similar to EVM behavior, the `source` should be (limited to) the + // contract address. Otherwise contracts would be able to do arbitrary + // things on be half of the caller via XVM. + let source = env.ext().address(); + let xvm_context = Context { source_vm_id: VmId::Wasm, weight_limit, }; - let vm_id = { match TryInto::::try_into(vm_id) { Ok(id) => id, @@ -112,7 +98,7 @@ where } } }; - let call_result = XC::call(xvm_context, vm_id, caller, to, input, value); + let call_result = XC::call(xvm_context, vm_id, source.clone(), to, input, value); let actual_weight = match call_result { Ok(ref info) => info.used_weight, diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index 9e3a7f6b20..cd42cd2eba 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -157,8 +157,8 @@ where ) -> CallResult { log::trace!( target: "xvm::evm_call", - "Calling EVM: {:?} {:?}, {:?}, {:?}", - context, source, target, input, + "Calling EVM: {:?} {:?}, {:?}, {:?}, {:?}", + context, source, target, input, value, ); ensure!( @@ -243,8 +243,8 @@ where ) -> CallResult { log::trace!( target: "xvm::wasm_call", - "Calling WASM: {:?} {:?}, {:?}, {:?}", - context, source, target, input, + "Calling WASM: {:?} {:?}, {:?}, {:?}, {:?}", + context, source, target, input, value, ); let dest = { diff --git a/primitives/src/xvm.rs b/primitives/src/xvm.rs index bb6a64f49d..38de257e27 100644 --- a/primitives/src/xvm.rs +++ b/primitives/src/xvm.rs @@ -66,8 +66,6 @@ pub enum CallError { InvalidTarget, /// Input is too large. InputTooLarge, - /// Bad origin. - BadOrigin, /// The call failed on EVM or WASM execution. ExecutionFailed(Vec), } diff --git a/tests/integration/resource/call_xvm_payable.json b/tests/integration/resource/call_xvm_payable.json new file mode 100644 index 0000000000..774d549f04 --- /dev/null +++ b/tests/integration/resource/call_xvm_payable.json @@ -0,0 +1,438 @@ +{ + "source": { + "hash": "0x2f161d6093ea4272bde71b8e3c8e81efbd7b15bc3b482ebd8bac2f3f43950af9", + "language": "ink! 4.2.1", + "compiler": "rustc 1.68.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "3.0.1", + "rust_toolchain": "nightly-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "call_xvm_payable", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 0 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 8 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 10 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 13 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 14 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 11 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 12 + } + }, + "events": [], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 2 + }, + "messages": [ + { + "args": [ + { + "label": "target", + "type": { + "displayName": [ + "Vec" + ], + "type": 3 + } + }, + { + "label": "input", + "type": { + "displayName": [ + "Vec" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "call_xvm_payable", + "mutates": false, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 5 + }, + "selector": "0x0000002a" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [], + "name": "CallXvmPayable" + } + }, + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "sequence": { + "type": 4 + } + } + } + }, + { + "id": 4, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 5, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 6 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 6 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 7 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 7 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 7, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "InvalidVmId" + }, + { + "index": 1, + "name": "SameVmCallNotAllowed" + }, + { + "index": 2, + "name": "InvalidTarget" + }, + { + "index": 3, + "name": "InputTooLarge" + }, + { + "index": 4, + "name": "BadOrigin" + }, + { + "index": 5, + "name": "ExecutionFailed" + } + ] + } + }, + "path": [ + "call_xvm_payable", + "XvmCallError" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 9, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 9, + "type": { + "def": { + "array": { + "len": 32, + "type": 4 + } + } + } + }, + { + "id": 10, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 11, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 9, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 12, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 13, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 14, + "type": { + "def": { + "variant": {} + }, + "path": [ + "call_xvm_payable", + "XvmCall" + ] + } + } + ], + "version": "4" +} \ No newline at end of file diff --git a/tests/integration/resource/call_xvm_payable.wasm b/tests/integration/resource/call_xvm_payable.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9d2b538e7c952068ed03675791f2463e3644b71d GIT binary patch literal 13469 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 L{4Jj+BJY0zJH-Da literal 0 HcmV?d00001 diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index 559603ec80..8b897be677 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -63,6 +63,9 @@ pub const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); pub const BOB: AccountId32 = AccountId32::new([2_u8; 32]); pub const CAT: AccountId32 = AccountId32::new([3_u8; 32]); +// TODO: once Account Unification is finished, remove `alith` and `alicia`, +// which are mocks of two way account/address mapping. + /// H160 address mapped from `ALICE`. pub fn alith() -> H160 { h160_from(ALICE) diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index 74afb1d44c..e30687d0ec 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -19,7 +19,7 @@ use crate::setup::*; use astar_primitives::xvm::{Context, VmId, XvmCall}; -use frame_support::weights::Weight; +use frame_support::{traits::Currency, weights::Weight}; use pallet_contracts::{CollectEvents, DebugInfo}; use pallet_contracts_primitives::Code; use parity_scale_codec::Encode; @@ -59,7 +59,7 @@ fn evm_payable_addr_h160() -> H160 { H160::from_slice(evm_payable_addr().as_slice()) } -/* +/* WASM payable: #![cfg_attr(not(feature = "std"), no_std, no_main)] @@ -80,12 +80,8 @@ mod payable { } */ -fn wasm_payable() -> std::io::Result> { - let path = "resource/payable.wasm"; - std::fs::read(path) -} -/* +/* Call WASM payable: // SPDX-License-Identifier: GPL-3.0 @@ -117,11 +113,69 @@ fn call_wasm_payable_addr_h160() -> H160 { H160::from_slice(call_wasm_payable_addr().as_slice()) } -fn deploy_evm_payable() { +/* Call EVM Payable: + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::env::{DefaultEnvironment, Environment}; +use ink::prelude::vec::Vec; + +#[ink::contract(env = CustomEnvironment)] +mod call_xvm_payable { + use super::*; + + #[ink(storage)] + pub struct CallXvmPayable {} + + impl CallXvmPayable { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + #[ink(message, payable, selector = 42)] + pub fn call_xvm_payable( + &self, + target: Vec, + input: Vec, + ) -> CallResult { + let value = Self::env().transferred_value(); + // Calling EVM + Self::env().extension().call(0x0F, target, input, value) + } + } +} + +pub type CallResult = u32; + +#[ink::chain_extension] +pub trait XvmCall { + type ErrorCode = u32; + + #[ink(extension = 0x00010001, handle_status = false)] + fn call(vm_id: u8, target: Vec, input: Vec, value: u128) -> CallResult; +} + +pub enum CustomEnvironment {} +impl Environment for CustomEnvironment { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = XvmCall; +} + + */ + +fn deploy_evm_contract(code: &str, assert_addr: H160) { assert_ok!(EVM::create2( RuntimeOrigin::root(), alith(), - hex::decode(EVM_PAYABLE).unwrap(), + hex::decode(code).unwrap(), H256::zero(), U256::zero(), 1_000_000, @@ -131,37 +185,36 @@ fn deploy_evm_payable() { vec![], )); System::assert_last_event(RuntimeEvent::EVM(pallet_evm::Event::Created { - address: evm_payable_addr_h160(), + address: assert_addr, })); } -fn deploy_wasm_payable() -> AccountId32 { - let wasm_payable = wasm_payable().unwrap(); +fn deploy_wasm_contract(name: &str) -> AccountId32 { + let path = format!("resource/{}.wasm", name); + let code = std::fs::read(path).unwrap(); let instantiate_result = Contracts::bare_instantiate( ALICE, 0, Weight::from_parts(10_000_000_000, 1024 * 1024), None, - Code::Upload(wasm_payable), + Code::Upload(code), // `new` constructor hex::decode("9bae9d5e").unwrap(), vec![], DebugInfo::Skip, CollectEvents::Skip, ); - let wasm_payable_addr = instantiate_result.result.unwrap().account_id; - // On instantiation, the contract got exising deposit. - assert_eq!( - Balances::free_balance(&wasm_payable_addr), - ExistentialDeposit::get(), - ); - wasm_payable_addr + + let address = instantiate_result.result.unwrap().account_id; + // On instantiation, the contract got existential deposit. + assert_eq!(Balances::free_balance(&address), ExistentialDeposit::get(),); + address } #[test] fn evm_payable_call_via_xvm_works() { new_test_ext().execute_with(|| { - deploy_evm_payable(); + deploy_evm_contract(EVM_PAYABLE, evm_payable_addr_h160()); let value = 1_000_000_000; assert_ok!(Xvm::call( @@ -203,7 +256,7 @@ fn evm_payable_call_via_xvm_works() { #[test] fn wasm_payable_call_via_xvm_works() { new_test_ext().execute_with(|| { - let contract_addr = deploy_wasm_payable(); + let contract_addr = deploy_wasm_contract("payable"); let value = 1_000_000_000; assert_ok!(Xvm::call( @@ -225,23 +278,8 @@ fn wasm_payable_call_via_xvm_works() { #[test] fn calling_wasm_payable_from_evm_works() { new_test_ext().execute_with(|| { - let wasm_payable_addr = deploy_wasm_payable(); - - assert_ok!(EVM::create2( - RuntimeOrigin::root(), - alith(), - hex::decode(CALL_WASM_PAYBLE).unwrap(), - H256::zero(), - U256::zero(), - 1_000_000, - U256::from(DefaultBaseFeePerGas::get()), - None, - None, - vec![], - )); - System::assert_last_event(RuntimeEvent::EVM(pallet_evm::Event::Created { - address: call_wasm_payable_addr_h160(), - })); + let wasm_payable_addr = deploy_wasm_contract("payable"); + deploy_evm_contract(CALL_WASM_PAYBLE, call_wasm_payable_addr_h160()); let value = 1_000_000_000; assert_ok!(EVM::call( @@ -265,5 +303,53 @@ fn calling_wasm_payable_from_evm_works() { Balances::free_balance(&wasm_payable_addr), value, ); + assert_eq!( + Balances::free_balance(&account_id_from(call_wasm_payable_addr_h160())), + ExistentialDeposit::get(), + ); + }); +} + +#[test] +fn calling_evm_payable_from_wasm_works() { + new_test_ext().execute_with(|| { + deploy_evm_contract(EVM_PAYABLE, evm_payable_addr_h160()); + let wasm_address = deploy_wasm_contract("call_xvm_payable"); + + let value = UNIT; + + // TODO: after Account Unification finished, remove this mock account. + // It is needed now because currently the `AccountMapping` and `AddressMapping` are + // both one way mapping. + let mock_unified_wasm_account = account_id_from(h160_from(wasm_address.clone())); + let _ = Balances::deposit_creating(&mock_unified_wasm_account, value); + + let evm_payable = evm_payable_addr_h160().as_ref().to_vec(); + let deposit_func = hex::decode("d0e30db0").unwrap(); + let input = hex::decode("0000002a") + .unwrap() + .iter() + .chain(evm_payable.encode().iter()) + .chain(deposit_func.encode().iter()) + .cloned() + .collect::>(); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + MultiAddress::Id(wasm_address.clone()), + value, + Weight::from_parts(10_000_000_000, 10 * 1024 * 1024), + None, + input, + )); + + assert_eq!( + Balances::free_balance(account_id_from(evm_payable_addr_h160())), + value + ); + + // TODO: after Account Unification finished, enable the wasm address balance check + // and remove the mock account balance check. + // assert_eq!(Balances::free_balance(&wasm_address), ExistentialDeposit::get()); + assert_eq!(Balances::free_balance(&mock_unified_wasm_account), 0); }); } From b76f6ca0574b99901a603c5ebf7dfa26280b1e55 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Wed, 2 Aug 2023 21:56:29 +1200 Subject: [PATCH 08/17] Fix runtime tests. --- tests/integration/src/setup.rs | 48 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index 8b897be677..25d1307495 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -33,10 +33,34 @@ pub use astar_primitives::ethereum_checked::AccountMapping; pub use shibuya::*; #[cfg(feature = "shibuya")] mod shibuya { + use super::*; pub use shibuya_runtime::*; /// 1 SBY. pub const UNIT: Balance = SBY; + + // TODO: once Account Unification is finished, remove `alith` and `alicia`, + // which are mocks of two way account/address mapping. + + /// H160 address mapped from `ALICE`. + pub fn alith() -> H160 { + h160_from(ALICE) + } + + /// `AccountId32` mapped from `alith()`. + pub fn alicia() -> AccountId32 { + account_id_from(alith()) + } + + /// Convert `H160` to `AccountId32`. + pub fn account_id_from(address: H160) -> AccountId32 { + ::AddressMapping::into_account_id(address) + } + + /// Convert `AccountId32` to `H160`. + pub fn h160_from(account_id: AccountId32) -> H160 { + ::AccountMapping::into_h160(account_id) + } } #[cfg(feature = "shiden")] @@ -63,29 +87,6 @@ pub const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); pub const BOB: AccountId32 = AccountId32::new([2_u8; 32]); pub const CAT: AccountId32 = AccountId32::new([3_u8; 32]); -// TODO: once Account Unification is finished, remove `alith` and `alicia`, -// which are mocks of two way account/address mapping. - -/// H160 address mapped from `ALICE`. -pub fn alith() -> H160 { - h160_from(ALICE) -} - -/// `AccountId32` mapped from `alith()`. -pub fn alicia() -> AccountId32 { - account_id_from(alith()) -} - -/// Convert `H160` to `AccountId32`. -pub fn account_id_from(address: H160) -> AccountId32 { - ::AddressMapping::into_account_id(address) -} - -/// Convert `AccountId32` to `H160`. -pub fn h160_from(account_id: AccountId32) -> H160 { - ::AccountMapping::into_h160(account_id) -} - pub const INITIAL_AMOUNT: u128 = 100_000 * UNIT; pub type SystemError = frame_system::Error; @@ -133,6 +134,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (ALICE, INITIAL_AMOUNT), (BOB, INITIAL_AMOUNT), (CAT, INITIAL_AMOUNT), + #[cfg(feature = "shibuya")] (alicia(), INITIAL_AMOUNT), ]) .build() From 06bb378aca85aca9fb7ca549eeeff468ece47ab5 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Wed, 2 Aug 2023 22:21:24 +1200 Subject: [PATCH 09/17] Move contract deploy helpers to setup.rs. --- tests/integration/src/setup.rs | 45 ++++++++++++++++++- tests/integration/src/xvm.rs | 81 +++++----------------------------- 2 files changed, 55 insertions(+), 71 deletions(-) diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index 25d1307495..71cdaf4ae3 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -24,7 +24,7 @@ pub use frame_support::{ weights::Weight, }; pub use pallet_evm::AddressMapping; -pub use sp_core::H160; +pub use sp_core::{H160, H256, U256}; pub use sp_runtime::{AccountId32, MultiAddress}; pub use astar_primitives::ethereum_checked::AccountMapping; @@ -61,6 +61,49 @@ mod shibuya { pub fn h160_from(account_id: AccountId32) -> H160 { ::AccountMapping::into_h160(account_id) } + + /// Deploy an EVM contract with code. + pub fn deploy_evm_contract(code: &str) -> H160 { + assert_ok!(EVM::create2( + RuntimeOrigin::root(), + alith(), + hex::decode(code).unwrap(), + H256::zero(), + U256::zero(), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + match System::events().iter().last().unwrap().event { + RuntimeEvent::EVM(pallet_evm::Event::Created { address }) => address, + _ => panic!("Deploy failed."), + } + } + + /// Deploy a WASM contract with its name. (The code is in `resource/`.) + pub fn deploy_wasm_contract(name: &str) -> AccountId32 { + let path = format!("resource/{}.wasm", name); + let code = std::fs::read(path).unwrap(); + let instantiate_result = Contracts::bare_instantiate( + ALICE, + 0, + Weight::from_parts(10_000_000_000, 1024 * 1024), + None, + pallet_contracts_primitives::Code::Upload(code), + // `new` constructor + hex::decode("9bae9d5e").unwrap(), + vec![], + pallet_contracts::DebugInfo::Skip, + pallet_contracts::CollectEvents::Skip, + ); + + let address = instantiate_result.result.unwrap().account_id; + // On instantiation, the contract got existential deposit. + assert_eq!(Balances::free_balance(&address), ExistentialDeposit::get(),); + address + } } #[cfg(feature = "shiden")] diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index e30687d0ec..ad1e3116d8 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -20,10 +20,7 @@ use crate::setup::*; use astar_primitives::xvm::{Context, VmId, XvmCall}; use frame_support::{traits::Currency, weights::Weight}; -use pallet_contracts::{CollectEvents, DebugInfo}; -use pallet_contracts_primitives::Code; use parity_scale_codec::Encode; -use sp_core::{H160, H256, U256}; use sp_runtime::MultiAddress; /* @@ -50,14 +47,6 @@ contract Payable { */ const EVM_PAYABLE: &str = "6080604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102d6806100536000396000f3fe6080604052600436106100345760003560e01c80633ccfd60b146100395780638da5cb5b14610050578063d0e30db01461007b575b600080fd5b34801561004557600080fd5b5061004e610085565b005b34801561005c57600080fd5b5061006561015b565b60405161007291906101c2565b60405180910390f35b61008361017f565b005b600047905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16826040516100d19061020e565b60006040518083038185875af1925050503d806000811461010e576040519150601f19603f3d011682016040523d82523d6000602084013e610113565b606091505b5050905080610157576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161014e90610280565b60405180910390fd5b5050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101ac82610181565b9050919050565b6101bc816101a1565b82525050565b60006020820190506101d760008301846101b3565b92915050565b600081905092915050565b50565b60006101f86000836101dd565b9150610203826101e8565b600082019050919050565b6000610219826101eb565b9150819050919050565b600082825260208201905092915050565b7f4661696c656420746f2077697468647261772045746865720000000000000000600082015250565b600061026a601883610223565b915061027582610234565b602082019050919050565b600060208201905081810360008301526102998161025d565b905091905056fea2646970667358221220bd8883b6a524d12ac9c29f105fdd1a0221a0436a79002f2a04e69d252596a62a64736f6c63430008120033"; -const EVM_PAYABLE_ADDR: &str = "4a15f25194b60fd07860b3a20a060fd003cae5a6"; - -fn evm_payable_addr() -> Vec { - hex::decode(EVM_PAYABLE_ADDR).unwrap() -} -fn evm_payable_addr_h160() -> H160 { - H160::from_slice(evm_payable_addr().as_slice()) -} /* WASM payable: @@ -104,14 +93,6 @@ contract CallXVMPayble { */ const CALL_WASM_PAYBLE: &str = "608060405234801561001057600080fd5b506105e6806100206000396000f3fe60806040526004361061001e5760003560e01c80634012b91414610023575b600080fd5b61003d600480360381019061003891906101a3565b610054565b60405161004b9291906102e3565b60405180910390f35b6000606061500573ffffffffffffffffffffffffffffffffffffffff1663e5d9bac0601f89898989896040518763ffffffff1660e01b815260040161009e969594939291906103b0565b6000604051808303816000875af11580156100bd573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906100e69190610554565b915091509550959350505050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f84011261012d5761012c610108565b5b8235905067ffffffffffffffff81111561014a5761014961010d565b5b60208301915083600182028301111561016657610165610112565b5b9250929050565b6000819050919050565b6101808161016d565b811461018b57600080fd5b50565b60008135905061019d81610177565b92915050565b6000806000806000606086880312156101bf576101be6100fe565b5b600086013567ffffffffffffffff8111156101dd576101dc610103565b5b6101e988828901610117565b9550955050602086013567ffffffffffffffff81111561020c5761020b610103565b5b61021888828901610117565b9350935050604061022b8882890161018e565b9150509295509295909350565b60008115159050919050565b61024d81610238565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561028d578082015181840152602081019050610272565b60008484015250505050565b6000601f19601f8301169050919050565b60006102b582610253565b6102bf818561025e565b93506102cf81856020860161026f565b6102d881610299565b840191505092915050565b60006040820190506102f86000830185610244565b818103602083015261030a81846102aa565b90509392505050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b600061034f61034a61034584610313565b61032a565b61031d565b9050919050565b61035f81610334565b82525050565b82818337600083830152505050565b6000610380838561025e565b935061038d838584610365565b61039683610299565b840190509392505050565b6103aa8161016d565b82525050565b60006080820190506103c56000830189610356565b81810360208301526103d8818789610374565b905081810360408301526103ed818587610374565b90506103fc60608301846103a1565b979650505050505050565b61041081610238565b811461041b57600080fd5b50565b60008151905061042d81610407565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61047082610299565b810181811067ffffffffffffffff8211171561048f5761048e610438565b5b80604052505050565b60006104a26100f4565b90506104ae8282610467565b919050565b600067ffffffffffffffff8211156104ce576104cd610438565b5b6104d782610299565b9050602081019050919050565b60006104f76104f2846104b3565b610498565b90508281526020810184848401111561051357610512610433565b5b61051e84828561026f565b509392505050565b600082601f83011261053b5761053a610108565b5b815161054b8482602086016104e4565b91505092915050565b6000806040838503121561056b5761056a6100fe565b5b60006105798582860161041e565b925050602083015167ffffffffffffffff81111561059a57610599610103565b5b6105a685828601610526565b915050925092905056fea264697066735822122047908cecfa9ace275a4ba96e787bb0d1541ec599c370983693c6cd9c1f5b7dbe64736f6c63430008120033"; -const CALL_WASM_PAYBLE_ADDR: &str = "79b3ca9f410de3d98c1dafac0e291871f752bc7a"; - -fn call_wasm_payable_addr() -> Vec { - hex::decode(CALL_WASM_PAYBLE_ADDR).unwrap() -} -fn call_wasm_payable_addr_h160() -> H160 { - H160::from_slice(call_wasm_payable_addr().as_slice()) -} /* Call EVM Payable: @@ -171,50 +152,10 @@ impl Environment for CustomEnvironment { */ -fn deploy_evm_contract(code: &str, assert_addr: H160) { - assert_ok!(EVM::create2( - RuntimeOrigin::root(), - alith(), - hex::decode(code).unwrap(), - H256::zero(), - U256::zero(), - 1_000_000, - U256::from(DefaultBaseFeePerGas::get()), - None, - None, - vec![], - )); - System::assert_last_event(RuntimeEvent::EVM(pallet_evm::Event::Created { - address: assert_addr, - })); -} - -fn deploy_wasm_contract(name: &str) -> AccountId32 { - let path = format!("resource/{}.wasm", name); - let code = std::fs::read(path).unwrap(); - let instantiate_result = Contracts::bare_instantiate( - ALICE, - 0, - Weight::from_parts(10_000_000_000, 1024 * 1024), - None, - Code::Upload(code), - // `new` constructor - hex::decode("9bae9d5e").unwrap(), - vec![], - DebugInfo::Skip, - CollectEvents::Skip, - ); - - let address = instantiate_result.result.unwrap().account_id; - // On instantiation, the contract got existential deposit. - assert_eq!(Balances::free_balance(&address), ExistentialDeposit::get(),); - address -} - #[test] fn evm_payable_call_via_xvm_works() { new_test_ext().execute_with(|| { - deploy_evm_contract(EVM_PAYABLE, evm_payable_addr_h160()); + let evm_payable_addr = deploy_evm_contract(EVM_PAYABLE); let value = 1_000_000_000; assert_ok!(Xvm::call( @@ -224,13 +165,13 @@ fn evm_payable_call_via_xvm_works() { }, VmId::Evm, ALICE, - evm_payable_addr(), + evm_payable_addr.as_ref().to_vec(), // Calling `deposit` hex::decode("d0e30db0").unwrap(), value, )); assert_eq!( - Balances::free_balance(account_id_from(evm_payable_addr_h160())), + Balances::free_balance(account_id_from(evm_payable_addr)), value ); @@ -241,13 +182,13 @@ fn evm_payable_call_via_xvm_works() { }, VmId::Evm, ALICE, - evm_payable_addr(), + evm_payable_addr.as_ref().to_vec(), // `Calling withdraw` hex::decode("3ccfd60b").unwrap(), 0, )); assert_eq!( - Balances::free_balance(account_id_from(evm_payable_addr_h160())), + Balances::free_balance(account_id_from(evm_payable_addr)), ExistentialDeposit::get(), ); }); @@ -279,13 +220,13 @@ fn wasm_payable_call_via_xvm_works() { fn calling_wasm_payable_from_evm_works() { new_test_ext().execute_with(|| { let wasm_payable_addr = deploy_wasm_contract("payable"); - deploy_evm_contract(CALL_WASM_PAYBLE, call_wasm_payable_addr_h160()); + let call_wasm_payable_addr = deploy_evm_contract(CALL_WASM_PAYBLE); let value = 1_000_000_000; assert_ok!(EVM::call( RuntimeOrigin::root(), alith(), - call_wasm_payable_addr_h160(), + call_wasm_payable_addr.clone(), // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c // input: 0x0000002a (deposit) // value: 1000000000 @@ -304,7 +245,7 @@ fn calling_wasm_payable_from_evm_works() { value, ); assert_eq!( - Balances::free_balance(&account_id_from(call_wasm_payable_addr_h160())), + Balances::free_balance(&account_id_from(call_wasm_payable_addr)), ExistentialDeposit::get(), ); }); @@ -313,7 +254,7 @@ fn calling_wasm_payable_from_evm_works() { #[test] fn calling_evm_payable_from_wasm_works() { new_test_ext().execute_with(|| { - deploy_evm_contract(EVM_PAYABLE, evm_payable_addr_h160()); + let evm_payable_addr = deploy_evm_contract(EVM_PAYABLE); let wasm_address = deploy_wasm_contract("call_xvm_payable"); let value = UNIT; @@ -324,7 +265,7 @@ fn calling_evm_payable_from_wasm_works() { let mock_unified_wasm_account = account_id_from(h160_from(wasm_address.clone())); let _ = Balances::deposit_creating(&mock_unified_wasm_account, value); - let evm_payable = evm_payable_addr_h160().as_ref().to_vec(); + let evm_payable = evm_payable_addr.as_ref().to_vec(); let deposit_func = hex::decode("d0e30db0").unwrap(); let input = hex::decode("0000002a") .unwrap() @@ -343,7 +284,7 @@ fn calling_evm_payable_from_wasm_works() { )); assert_eq!( - Balances::free_balance(account_id_from(evm_payable_addr_h160())), + Balances::free_balance(account_id_from(evm_payable_addr)), value ); From 9a94737b91043d938272b64ee81adb0c71381aa6 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Wed, 2 Aug 2023 23:23:48 +1200 Subject: [PATCH 10/17] Update value adjustment for wasm payable calls. --- pallets/xvm/src/lib.rs | 27 ++++++++++++++---------- pallets/xvm/src/mock.rs | 1 - runtime/local/src/lib.rs | 1 - runtime/shibuya/src/lib.rs | 1 - tests/integration/src/xvm.rs | 40 ++++++++++++++++++++++++++++++++---- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index cd42cd2eba..9e4653fdfa 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -37,10 +37,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - ensure, - traits::{Currency, Get}, -}; +use frame_support::{ensure, traits::Currency}; use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use pallet_evm::GasWeightMapping; use parity_scale_codec::Decode; @@ -87,9 +84,6 @@ pub mod pallet { /// `CheckedEthereumTransact` implementation. type EthereumTransact: CheckedEthereumTransact; - /// Existential deposit of currency for payable calls. - type ExistentialDeposit: Get; - /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -256,10 +250,21 @@ where T::Lookup::lookup(decoded).map_err(|_| error) }?; - // TODO: maybe max(source_balance - existential_deposit, value)? might be overkill - // Adjust value to account for existential deposit, as `pallet-contracts` - // respects it on transfer. - let adjusted_value = value.saturating_sub(T::ExistentialDeposit::get()); + // Adjust `value` if needed, to make sure `source` balance won't be below ED after calling + // a payable contract. This is needed as `pallet-contracts` always respects `source` ED. + // Without the adjustment, the first call from any `source` to a payable contract will + // always fail. + // + // Only the first call to a payable contract results less `value` by ED amount, the following + // ones will not be affected. + let source_balance = T::Currency::free_balance(&source); + let existential_deposit = T::Currency::minimum_balance(); + let killing_source = source_balance.saturating_sub(value) < existential_deposit; + let adjusted_value = if killing_source { + value.saturating_sub(existential_deposit) + } else { + value + }; // With overheads, less weight is available. let weight_limit = context diff --git a/pallets/xvm/src/mock.rs b/pallets/xvm/src/mock.rs index bde6a54aa2..9783b71297 100644 --- a/pallets/xvm/src/mock.rs +++ b/pallets/xvm/src/mock.rs @@ -181,7 +181,6 @@ impl pallet_xvm::Config for TestRuntime { type GasWeightMapping = MockGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = MockEthereumTransact; - type ExistentialDeposit = ConstU128<2>; type WeightInfo = weights::SubstrateWeight; } diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 564907d2bb..765dc47b84 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -475,7 +475,6 @@ impl pallet_xvm::Config for Runtime { type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = EthereumChecked; - type ExistentialDeposit = ExistentialDeposit; type WeightInfo = pallet_xvm::weights::SubstrateWeight; } diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index d33627a336..bc94244c62 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -790,7 +790,6 @@ impl pallet_xvm::Config for Runtime { type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type AccountMapping = HashedAccountMapping; type EthereumTransact = EthereumChecked; - type ExistentialDeposit = ExistentialDeposit; type WeightInfo = pallet_xvm::weights::SubstrateWeight; } diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index ad1e3116d8..cd824dc70c 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -157,7 +157,7 @@ fn evm_payable_call_via_xvm_works() { new_test_ext().execute_with(|| { let evm_payable_addr = deploy_evm_contract(EVM_PAYABLE); - let value = 1_000_000_000; + let value = UNIT; assert_ok!(Xvm::call( Context { source_vm_id: VmId::Wasm, @@ -199,7 +199,8 @@ fn wasm_payable_call_via_xvm_works() { new_test_ext().execute_with(|| { let contract_addr = deploy_wasm_contract("payable"); - let value = 1_000_000_000; + let prev_balance = Balances::free_balance(&contract_addr); + let value = UNIT; assert_ok!(Xvm::call( Context { source_vm_id: VmId::Evm, @@ -212,7 +213,10 @@ fn wasm_payable_call_via_xvm_works() { hex::decode("0000002a").unwrap(), value )); - assert_eq!(Balances::free_balance(contract_addr.clone()), value,); + assert_eq!( + Balances::free_balance(contract_addr.clone()), + value + prev_balance + ); }); } @@ -239,7 +243,7 @@ fn calling_wasm_payable_from_evm_works() { vec![], )); // `pallet-contracts` respects the existential deposit of caller. The actual amount - // it got is `value - ExistentialDeposit`. Adding to its existing balance and we got `value`. + // it got is `value - ExistentialDeposit`. Adding to its existing balance results `value`. assert_eq!( Balances::free_balance(&wasm_payable_addr), value, @@ -248,6 +252,34 @@ fn calling_wasm_payable_from_evm_works() { Balances::free_balance(&account_id_from(call_wasm_payable_addr)), ExistentialDeposit::get(), ); + + let value = 1_000_000_000; + assert_ok!(EVM::call( + RuntimeOrigin::root(), + alith(), + call_wasm_payable_addr.clone(), + // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c + // input: 0x0000002a (deposit) + // value: 1000000000 + hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").unwrap(), + U256::from(value), + 1_000_000, + U256::from(DefaultBaseFeePerGas::get()), + None, + None, + vec![], + )); + // For the second call with the same value, the wasm payable contract will receive + // the full amount, as the EVM contract already has enough balance for existential + // deposit. + assert_eq!( + Balances::free_balance(&wasm_payable_addr), + 2 * value, + ); + assert_eq!( + Balances::free_balance(&account_id_from(call_wasm_payable_addr)), + ExistentialDeposit::get(), + ); }); } From d1ae1cc6fccd22e32fea55f3b95b84c979cb9b91 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Wed, 2 Aug 2023 23:43:11 +1200 Subject: [PATCH 11/17] More pallet-xvm unit tests. --- pallets/xvm/src/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs index 0c948ac23b..896b43c3f1 100644 --- a/pallets/xvm/src/tests.rs +++ b/pallets/xvm/src/tests.rs @@ -157,3 +157,26 @@ fn evm_call_works() { ); }); } + +#[test] +fn wasm_call_fails_if_invalid_target() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Evm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Wasm; + let target = vec![1, 2, 3]; + let input = vec![1, 2, 3]; + let value = 1_000_000u128; + let used_weight: Weight = weights::SubstrateWeight::::wasm_call_overheads(); + + assert_noop!( + Xvm::call(context, vm_id, ALICE, target.encode(), input, value), + CallErrorWithWeight { + error: CallError::InvalidTarget, + used_weight + }, + ); + }); +} From 22dc6e2d20ce24e6a88b75c73f689568bf26443c Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 3 Aug 2023 00:31:51 +1200 Subject: [PATCH 12/17] Update pallet-xvm weight info. --- pallets/xvm/src/tests.rs | 2 +- pallets/xvm/src/weights.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs index 896b43c3f1..500d0de761 100644 --- a/pallets/xvm/src/tests.rs +++ b/pallets/xvm/src/tests.rs @@ -148,7 +148,7 @@ fn evm_call_works() { MockEthereumTransact::assert_transacted( source, CheckedEthereumTx { - gas_limit: U256::from(182000), + gas_limit: U256::from(244000), target: H160::repeat_byte(0xFF), value: U256::from(value), input: EthereumTxInput::try_from(input).unwrap(), diff --git a/pallets/xvm/src/weights.rs b/pallets/xvm/src/weights.rs index 0d458675c2..67e4cd3382 100644 --- a/pallets/xvm/src/weights.rs +++ b/pallets/xvm/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_xvm //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-08-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("shibuya-dev"), DB CACHE: 1024 @@ -59,15 +59,15 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 771_000 picoseconds. - Weight::from_parts(818_000, 0) + // Minimum execution time: 674_000 picoseconds. + Weight::from_parts(756_000, 0) } fn wasm_call_overheads() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 304_000 picoseconds. - Weight::from_parts(337_000, 0) + // Minimum execution time: 1_602_000 picoseconds. + Weight::from_parts(1_649_000, 0) } } @@ -77,14 +77,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 771_000 picoseconds. - Weight::from_parts(818_000, 0) + // Minimum execution time: 674_000 picoseconds. + Weight::from_parts(756_000, 0) } fn wasm_call_overheads() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 304_000 picoseconds. - Weight::from_parts(337_000, 0) + // Minimum execution time: 1_602_000 picoseconds. + Weight::from_parts(1_649_000, 0) } } From d7187d11782c06e9f43b1b0288b8b5b50ff05af0 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 3 Aug 2023 18:54:23 +1200 Subject: [PATCH 13/17] Apply review suggestions. --- pallets/ethereum-checked/src/mock.rs | 2 +- pallets/ethereum-checked/src/tests.rs | 3 +- pallets/xvm/src/mock.rs | 3 +- pallets/xvm/src/tests.rs | 52 ++++++++++++------ tests/integration/ink-contracts/README.md | 8 +++ .../call_xvm_payable.json | 0 .../call_xvm_payable.wasm | Bin .../{resource => ink-contracts}/payable.json | 0 .../{resource => ink-contracts}/payable.wasm | Bin tests/integration/src/setup.rs | 18 ++++-- tests/integration/src/xvm.rs | 14 ++--- 11 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 tests/integration/ink-contracts/README.md rename tests/integration/{resource => ink-contracts}/call_xvm_payable.json (100%) rename tests/integration/{resource => ink-contracts}/call_xvm_payable.wasm (100%) rename tests/integration/{resource => ink-contracts}/payable.json (100%) rename tests/integration/{resource => ink-contracts}/payable.wasm (100%) diff --git a/pallets/ethereum-checked/src/mock.rs b/pallets/ethereum-checked/src/mock.rs index b63a34c23d..f4575e956d 100644 --- a/pallets/ethereum-checked/src/mock.rs +++ b/pallets/ethereum-checked/src/mock.rs @@ -294,7 +294,7 @@ impl ExtBuilder { assert_ok!(Evm::create2( RuntimeOrigin::root(), ALICE_H160, - hex::decode(STORAGE_CONTRACT).unwrap(), + hex::decode(STORAGE_CONTRACT).expect("invalid code hex"), H256::zero(), U256::zero(), 1_000_000, diff --git a/pallets/ethereum-checked/src/tests.rs b/pallets/ethereum-checked/src/tests.rs index fd227a581e..e5c9b8f41c 100644 --- a/pallets/ethereum-checked/src/tests.rs +++ b/pallets/ethereum-checked/src/tests.rs @@ -27,7 +27,8 @@ use frame_support::{assert_noop, assert_ok}; use sp_runtime::DispatchError; fn bounded_input(data: &'static str) -> EthereumTxInput { - EthereumTxInput::try_from(hex::decode(data).unwrap()).unwrap() + EthereumTxInput::try_from(hex::decode(data).expect("invalid input hex")) + .expect("input too large") } #[test] diff --git a/pallets/xvm/src/mock.rs b/pallets/xvm/src/mock.rs index 9783b71297..7f134b4c3c 100644 --- a/pallets/xvm/src/mock.rs +++ b/pallets/xvm/src/mock.rs @@ -139,7 +139,8 @@ thread_local! { pub struct MockEthereumTransact; impl MockEthereumTransact { pub(crate) fn assert_transacted(source: H160, checked_tx: CheckedEthereumTx) { - assert!(TRANSACTED.with(|v| v.eq(&RefCell::new(Some((source, checked_tx)))))); + let transacted = TRANSACTED.with(|v| v.borrow().clone()); + assert_eq!(transacted, Some((source, checked_tx))); } } impl CheckedEthereumTransact for MockEthereumTransact { diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs index 500d0de761..48be255ded 100644 --- a/pallets/xvm/src/tests.rs +++ b/pallets/xvm/src/tests.rs @@ -74,19 +74,17 @@ fn calling_into_same_vm_is_not_allowed() { } #[test] -fn evm_call_works() { +fn evm_call_fails_if_target_not_h160() { ExtBuilder::default().build().execute_with(|| { let context = Context { source_vm_id: VmId::Wasm, weight_limit: Weight::from_parts(1_000_000, 1_000_000), }; let vm_id = VmId::Evm; - let target = H160::repeat_byte(0xFF); let input = vec![1; 65_536]; let value = 1_000_000u128; let used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); - // Invalid target assert_noop!( Xvm::call( context.clone(), @@ -101,24 +99,32 @@ fn evm_call_works() { used_weight }, ); + assert_noop!( - Xvm::call( - context.clone(), - vm_id, - ALICE, - vec![1, 2, 3], - input.clone(), - value - ), + Xvm::call(context, vm_id, ALICE, vec![1, 2, 3], input, value), CallErrorWithWeight { error: CallError::InvalidTarget, used_weight }, ); - // Input too large + }); +} + +#[test] +fn evm_call_fails_if_input_too_large() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Evm; + let target = H160::repeat_byte(0xFF); + let value = 1_000_000u128; + let used_weight: Weight = weights::SubstrateWeight::::evm_call_overheads(); + assert_noop!( Xvm::call( - context.clone(), + context, vm_id, ALICE, target.encode(), @@ -130,6 +136,20 @@ fn evm_call_works() { used_weight }, ); + }); +} + +#[test] +fn evm_call_works() { + ExtBuilder::default().build().execute_with(|| { + let context = Context { + source_vm_id: VmId::Wasm, + weight_limit: Weight::from_parts(1_000_000, 1_000_000), + }; + let vm_id = VmId::Evm; + let target = H160::repeat_byte(0xFF); + let input = vec![1; 65_536]; + let value = 1_000_000u128; assert_ok!(Xvm::call( context, @@ -141,17 +161,17 @@ fn evm_call_works() { )); let source = Decode::decode( &mut hex::decode("f0bd9ffde7f9f4394d8cc1d86bf24d87e5d5a9a9") - .unwrap() + .expect("invalid source hex") .as_ref(), ) - .unwrap(); + .expect("invalid source"); MockEthereumTransact::assert_transacted( source, CheckedEthereumTx { gas_limit: U256::from(244000), target: H160::repeat_byte(0xFF), value: U256::from(value), - input: EthereumTxInput::try_from(input).unwrap(), + input: EthereumTxInput::try_from(input).expect("input too large"), maybe_access_list: None, }, ); diff --git a/tests/integration/ink-contracts/README.md b/tests/integration/ink-contracts/README.md new file mode 100644 index 0000000000..24b9891009 --- /dev/null +++ b/tests/integration/ink-contracts/README.md @@ -0,0 +1,8 @@ +## Ink! Contracts + +This directory contains Ink! contracts which are used in integration tests. + +`.wasm` files in this directory are used by `pallet-contracts` scenarios, typically `src/xvm.rs`. The json +files are for informational purposes only and are not consumed by the tests. + +The source code for the contracts can be found at https://github.com/AstarNetwork/ink-test-contracts diff --git a/tests/integration/resource/call_xvm_payable.json b/tests/integration/ink-contracts/call_xvm_payable.json similarity index 100% rename from tests/integration/resource/call_xvm_payable.json rename to tests/integration/ink-contracts/call_xvm_payable.json diff --git a/tests/integration/resource/call_xvm_payable.wasm b/tests/integration/ink-contracts/call_xvm_payable.wasm similarity index 100% rename from tests/integration/resource/call_xvm_payable.wasm rename to tests/integration/ink-contracts/call_xvm_payable.wasm diff --git a/tests/integration/resource/payable.json b/tests/integration/ink-contracts/payable.json similarity index 100% rename from tests/integration/resource/payable.json rename to tests/integration/ink-contracts/payable.json diff --git a/tests/integration/resource/payable.wasm b/tests/integration/ink-contracts/payable.wasm similarity index 100% rename from tests/integration/resource/payable.wasm rename to tests/integration/ink-contracts/payable.wasm diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index 71cdaf4ae3..af91821744 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -67,7 +67,7 @@ mod shibuya { assert_ok!(EVM::create2( RuntimeOrigin::root(), alith(), - hex::decode(code).unwrap(), + hex::decode(code).expect("invalid code hex"), H256::zero(), U256::zero(), 1_000_000, @@ -76,7 +76,12 @@ mod shibuya { None, vec![], )); - match System::events().iter().last().unwrap().event { + match System::events() + .iter() + .last() + .expect("no event found") + .event + { RuntimeEvent::EVM(pallet_evm::Event::Created { address }) => address, _ => panic!("Deploy failed."), } @@ -85,7 +90,7 @@ mod shibuya { /// Deploy a WASM contract with its name. (The code is in `resource/`.) pub fn deploy_wasm_contract(name: &str) -> AccountId32 { let path = format!("resource/{}.wasm", name); - let code = std::fs::read(path).unwrap(); + let code = std::fs::read(path).expect("invalid path"); let instantiate_result = Contracts::bare_instantiate( ALICE, 0, @@ -93,13 +98,16 @@ mod shibuya { None, pallet_contracts_primitives::Code::Upload(code), // `new` constructor - hex::decode("9bae9d5e").unwrap(), + hex::decode("9bae9d5e").expect("invalid data hex"), vec![], pallet_contracts::DebugInfo::Skip, pallet_contracts::CollectEvents::Skip, ); - let address = instantiate_result.result.unwrap().account_id; + let address = instantiate_result + .result + .expect("instantiation failed") + .account_id; // On instantiation, the contract got existential deposit. assert_eq!(Balances::free_balance(&address), ExistentialDeposit::get(),); address diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index cd824dc70c..a7d96081ba 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -167,7 +167,7 @@ fn evm_payable_call_via_xvm_works() { ALICE, evm_payable_addr.as_ref().to_vec(), // Calling `deposit` - hex::decode("d0e30db0").unwrap(), + hex::decode("d0e30db0").except("invalid selector hex"), value, )); assert_eq!( @@ -184,7 +184,7 @@ fn evm_payable_call_via_xvm_works() { ALICE, evm_payable_addr.as_ref().to_vec(), // `Calling withdraw` - hex::decode("3ccfd60b").unwrap(), + hex::decode("3ccfd60b").expect("invalid selector hex"), 0, )); assert_eq!( @@ -210,7 +210,7 @@ fn wasm_payable_call_via_xvm_works() { ALICE, MultiAddress::::Id(contract_addr.clone()).encode(), // Calling `deposit` - hex::decode("0000002a").unwrap(), + hex::decode("0000002a").expect("invalid selector hex"), value )); assert_eq!( @@ -234,7 +234,7 @@ fn calling_wasm_payable_from_evm_works() { // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c // input: 0x0000002a (deposit) // value: 1000000000 - hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").unwrap(), + hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"), U256::from(value), 1_000_000, U256::from(DefaultBaseFeePerGas::get()), @@ -261,7 +261,7 @@ fn calling_wasm_payable_from_evm_works() { // to: 0x00a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c // input: 0x0000002a (deposit) // value: 1000000000 - hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").unwrap(), + hex::decode("4012b914000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000002100a8f69d59df362b69a8d4acdb9001eb3e1b8d067b8fdaa70081aed945bde5c48c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000002a00000000000000000000000000000000000000000000000000000000").expect("invalid call input hex"), U256::from(value), 1_000_000, U256::from(DefaultBaseFeePerGas::get()), @@ -298,9 +298,9 @@ fn calling_evm_payable_from_wasm_works() { let _ = Balances::deposit_creating(&mock_unified_wasm_account, value); let evm_payable = evm_payable_addr.as_ref().to_vec(); - let deposit_func = hex::decode("d0e30db0").unwrap(); + let deposit_func = hex::decode("d0e30db0").expect("invalid deposit function hex"); let input = hex::decode("0000002a") - .unwrap() + .expect("invalid selector hex") .iter() .chain(evm_payable.encode().iter()) .chain(deposit_func.encode().iter()) From dda5e361b191fd66eb5c074d859f5c8d307ddbb5 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 3 Aug 2023 19:53:58 +1200 Subject: [PATCH 14/17] Fix runtime tests. --- chain-extensions/xvm/src/lib.rs | 2 +- tests/integration/src/setup.rs | 2 +- tests/integration/src/xvm.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chain-extensions/xvm/src/lib.rs b/chain-extensions/xvm/src/lib.rs index 6b3d4c58ca..8b6c48b8f6 100644 --- a/chain-extensions/xvm/src/lib.rs +++ b/chain-extensions/xvm/src/lib.rs @@ -81,7 +81,7 @@ where // Similar to EVM behavior, the `source` should be (limited to) the // contract address. Otherwise contracts would be able to do arbitrary - // things on be half of the caller via XVM. + // things on behalf of the caller via XVM. let source = env.ext().address(); let xvm_context = Context { diff --git a/tests/integration/src/setup.rs b/tests/integration/src/setup.rs index af91821744..3407614818 100644 --- a/tests/integration/src/setup.rs +++ b/tests/integration/src/setup.rs @@ -89,7 +89,7 @@ mod shibuya { /// Deploy a WASM contract with its name. (The code is in `resource/`.) pub fn deploy_wasm_contract(name: &str) -> AccountId32 { - let path = format!("resource/{}.wasm", name); + let path = format!("ink-contracts/{}.wasm", name); let code = std::fs::read(path).expect("invalid path"); let instantiate_result = Contracts::bare_instantiate( ALICE, diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index a7d96081ba..0153e7d5e1 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -167,7 +167,7 @@ fn evm_payable_call_via_xvm_works() { ALICE, evm_payable_addr.as_ref().to_vec(), // Calling `deposit` - hex::decode("d0e30db0").except("invalid selector hex"), + hex::decode("d0e30db0").expect("invalid selector hex"), value, )); assert_eq!( From f9bbe9e567a1ea557133125903cabfe5dfccf564 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Fri, 4 Aug 2023 22:34:51 +1200 Subject: [PATCH 15/17] Calling WASM payable from EVM fails if caller contract balance below ED. --- pallets/xvm/src/lib.rs | 18 +---------------- tests/integration/src/xvm.rs | 38 +++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/pallets/xvm/src/lib.rs b/pallets/xvm/src/lib.rs index 9e4653fdfa..d10d141520 100644 --- a/pallets/xvm/src/lib.rs +++ b/pallets/xvm/src/lib.rs @@ -250,22 +250,6 @@ where T::Lookup::lookup(decoded).map_err(|_| error) }?; - // Adjust `value` if needed, to make sure `source` balance won't be below ED after calling - // a payable contract. This is needed as `pallet-contracts` always respects `source` ED. - // Without the adjustment, the first call from any `source` to a payable contract will - // always fail. - // - // Only the first call to a payable contract results less `value` by ED amount, the following - // ones will not be affected. - let source_balance = T::Currency::free_balance(&source); - let existential_deposit = T::Currency::minimum_balance(); - let killing_source = source_balance.saturating_sub(value) < existential_deposit; - let adjusted_value = if killing_source { - value.saturating_sub(existential_deposit) - } else { - value - }; - // With overheads, less weight is available. let weight_limit = context .weight_limit @@ -283,7 +267,7 @@ where let call_result = pallet_contracts::Pallet::::bare_call( source, dest, - adjusted_value, + value, weight_limit, None, input, diff --git a/tests/integration/src/xvm.rs b/tests/integration/src/xvm.rs index 0153e7d5e1..a92ff4c382 100644 --- a/tests/integration/src/xvm.rs +++ b/tests/integration/src/xvm.rs @@ -221,7 +221,7 @@ fn wasm_payable_call_via_xvm_works() { } #[test] -fn calling_wasm_payable_from_evm_works() { +fn calling_wasm_payable_from_evm_fails_if_caller_contract_balance_below_ed() { new_test_ext().execute_with(|| { let wasm_payable_addr = deploy_wasm_contract("payable"); let call_wasm_payable_addr = deploy_evm_contract(CALL_WASM_PAYBLE); @@ -242,17 +242,32 @@ fn calling_wasm_payable_from_evm_works() { None, vec![], )); - // `pallet-contracts` respects the existential deposit of caller. The actual amount - // it got is `value - ExistentialDeposit`. Adding to its existing balance results `value`. + + // TODO: after XVM error propagation finished, assert `pallet-evm` execution error + // and update balance assertions. + + // Transfer to EVM contract ok. assert_eq!( - Balances::free_balance(&wasm_payable_addr), + Balances::free_balance(&account_id_from(call_wasm_payable_addr)), value, ); + // Transfer from EVM contract to wasm Contract err. assert_eq!( - Balances::free_balance(&account_id_from(call_wasm_payable_addr)), + Balances::free_balance(&wasm_payable_addr), ExistentialDeposit::get(), ); + }); +} +#[test] +fn calling_wasm_payable_from_evm_works() { + new_test_ext().execute_with(|| { + let wasm_payable_addr = deploy_wasm_contract("payable"); + let call_wasm_payable_addr = deploy_evm_contract(CALL_WASM_PAYBLE); + + let _ = Balances::deposit_creating(&account_id_from(call_wasm_payable_addr.clone()), ExistentialDeposit::get()); + + let prev_wasm_payable_balance = Balances::free_balance(&wasm_payable_addr); let value = 1_000_000_000; assert_ok!(EVM::call( RuntimeOrigin::root(), @@ -269,17 +284,8 @@ fn calling_wasm_payable_from_evm_works() { None, vec![], )); - // For the second call with the same value, the wasm payable contract will receive - // the full amount, as the EVM contract already has enough balance for existential - // deposit. - assert_eq!( - Balances::free_balance(&wasm_payable_addr), - 2 * value, - ); - assert_eq!( - Balances::free_balance(&account_id_from(call_wasm_payable_addr)), - ExistentialDeposit::get(), - ); + let recieved = Balances::free_balance(&wasm_payable_addr) - prev_wasm_payable_balance; + assert_eq!(recieved, value); }); } From 102484e05a1eba0edcfa1ca4ee174f239a3545fe Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Fri, 4 Aug 2023 23:42:25 +1200 Subject: [PATCH 16/17] Update weight info. --- pallets/xvm/src/weights.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pallets/xvm/src/weights.rs b/pallets/xvm/src/weights.rs index 67e4cd3382..c25a3b20d5 100644 --- a/pallets/xvm/src/weights.rs +++ b/pallets/xvm/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_xvm //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-08-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `devserver-01`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("shibuya-dev"), DB CACHE: 1024 @@ -59,15 +59,15 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 674_000 picoseconds. - Weight::from_parts(756_000, 0) + // Minimum execution time: 730_000 picoseconds. + Weight::from_parts(754_000, 0) } fn wasm_call_overheads() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_602_000 picoseconds. - Weight::from_parts(1_649_000, 0) + // Minimum execution time: 309_000 picoseconds. + Weight::from_parts(347_000, 0) } } @@ -77,14 +77,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 674_000 picoseconds. - Weight::from_parts(756_000, 0) + // Minimum execution time: 730_000 picoseconds. + Weight::from_parts(754_000, 0) } fn wasm_call_overheads() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_602_000 picoseconds. - Weight::from_parts(1_649_000, 0) + // Minimum execution time: 309_000 picoseconds. + Weight::from_parts(347_000, 0) } } From d6efa700317775fbb7f89e8e26b459b94c94606e Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Sat, 5 Aug 2023 00:37:40 +1200 Subject: [PATCH 17/17] Fix unit tests. --- pallets/xvm/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/xvm/src/tests.rs b/pallets/xvm/src/tests.rs index 48be255ded..aedb8b46d4 100644 --- a/pallets/xvm/src/tests.rs +++ b/pallets/xvm/src/tests.rs @@ -168,7 +168,7 @@ fn evm_call_works() { MockEthereumTransact::assert_transacted( source, CheckedEthereumTx { - gas_limit: U256::from(244000), + gas_limit: U256::from(246000), target: H160::repeat_byte(0xFF), value: U256::from(value), input: EthereumTxInput::try_from(input).expect("input too large"),