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(|| {}); +}