// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. mod builder; mod pallet_dummy; mod test_debug; use self::{ test_debug::TestDebug, test_utils::{ensure_stored, expected_deposit, hash}, }; use crate::{ self as pallet_contracts, chain_extension::{ ChainExtension, Environment, Ext, InitState, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, }, exec::{Frame, Key}, migration::codegen::LATEST_MIGRATION_VERSION, primitives::CodeUploadReturnValue, storage::DeletionQueueManager, tests::test_utils::{get_contract, get_contract_checked}, wasm::{Determinism, LoadingMode, ReturnErrorCode as RuntimeReturnCode}, weights::WeightInfo, Array, BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf, DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, MigrationInProgress, Origin, Pallet, PristineCode, Schedule, }; use assert_matches::assert_matches; use codec::{Decode, Encode}; use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok, derive_impl, dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, pallet_prelude::EnsureOrigin, parameter_types, storage::child, traits::{ fungible::{BalancedHold, Inspect, Mutate, MutateHold}, tokens::Preservation, ConstU32, ConstU64, Contains, OnIdle, OnInitialize, StorageVersion, }, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightMeter}, }; use frame_system::{EventRecord, Phase}; use pallet_contracts_fixtures::compile_module; use pretty_assertions::{assert_eq, assert_ne}; use sp_core::ByteArray; use sp_io::hashing::blake2_256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ testing::H256, traits::{BlakeTwo256, Convert, Hash, IdentityLookup}, AccountId32, BuildStorage, DispatchError, Perbill, TokenError, }; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { System: frame_system, Balances: pallet_balances, Timestamp: pallet_timestamp, Randomness: pallet_insecure_randomness_collective_flip, Utility: pallet_utility, Contracts: pallet_contracts, Proxy: pallet_proxy, Dummy: pallet_dummy } ); macro_rules! assert_return_code { ( $x:expr , $y:expr $(,)? ) => {{ assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); }}; } macro_rules! assert_refcount { ( $code_hash:expr , $should:expr $(,)? ) => {{ let is = crate::CodeInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); assert_eq!(is, $should); }}; } pub mod test_utils { use super::{Contracts, DepositPerByte, DepositPerItem, Hash, SysConfig, Test}; use crate::{ exec::AccountIdOf, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Nonce, PristineCode, }; use codec::{Encode, MaxEncodedLen}; use frame_support::traits::fungible::{InspectHold, Mutate}; pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { let nonce = >::mutate(|counter| { *counter += 1; *counter }); set_balance(address, Contracts::min_balance() * 10); >::insert(code_hash, CodeInfo::new(address.clone())); let contract = >::new(&address, nonce, code_hash).unwrap(); >::insert(address, contract); } pub fn set_balance(who: &AccountIdOf, amount: u64) { let _ = ::Currency::set_balance(who, amount); } pub fn get_balance(who: &AccountIdOf) -> u64 { ::Currency::free_balance(who) } pub fn get_balance_on_hold( reason: &::RuntimeHoldReason, who: &AccountIdOf, ) -> u64 { ::Currency::balance_on_hold(reason.into(), who) } pub fn get_contract(addr: &AccountIdOf) -> ContractInfo { get_contract_checked(addr).unwrap() } pub fn get_contract_checked(addr: &AccountIdOf) -> Option> { ContractInfoOf::::get(addr) } pub fn get_code_deposit(code_hash: &CodeHash) -> BalanceOf { crate::CodeInfoOf::::get(code_hash).unwrap().deposit() } pub fn contract_info_storage_deposit( addr: &::AccountId, ) -> BalanceOf { let contract_info = self::get_contract(&addr); let info_size = contract_info.encoded_size() as u64; DepositPerByte::get() .saturating_mul(info_size) .saturating_add(DepositPerItem::get()) } pub fn hash(s: &S) -> <::Hashing as Hash>::Output { <::Hashing as Hash>::hash_of(s) } pub fn expected_deposit(code_len: usize) -> u64 { // For code_info, the deposit for max_encoded_len is taken. let code_info_len = CodeInfo::::max_encoded_len() as u64; // Calculate deposit to be reserved. // We add 2 storage items: one for code, other for code_info DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) + DepositPerItem::get().saturating_mul(2) } pub fn ensure_stored(code_hash: CodeHash) -> usize { // Assert that code_info is stored assert!(CodeInfoOf::::contains_key(&code_hash)); // Assert that contract code is stored, and get its size. PristineCode::::try_get(&code_hash).unwrap().len() } } impl Test { pub fn set_unstable_interface(unstable_interface: bool) { UNSTABLE_INTERFACE.with(|v| *v.borrow_mut() = unstable_interface); } } parameter_types! { static TestExtensionTestValue: TestExtension = Default::default(); } #[derive(Clone)] pub struct TestExtension { enabled: bool, last_seen_buffer: Vec, last_seen_input_len: u32, } #[derive(Default)] pub struct RevertingExtension; #[derive(Default)] pub struct DisabledExtension; #[derive(Default)] pub struct TempStorageExtension { storage: u32, } impl TestExtension { fn disable() { TestExtensionTestValue::mutate(|e| e.enabled = false) } fn last_seen_buffer() -> Vec { TestExtensionTestValue::get().last_seen_buffer.clone() } fn last_seen_input_len() -> u32 { TestExtensionTestValue::get().last_seen_input_len } } impl Default for TestExtension { fn default() -> Self { Self { enabled: true, last_seen_buffer: vec![], last_seen_input_len: 0 } } } impl ChainExtension for TestExtension { fn call(&mut self, env: Environment) -> ExtensionResult where E: Ext, { let func_id = env.func_id(); let id = env.ext_id() as u32 | func_id as u32; match func_id { 0 => { let mut env = env.buf_in_buf_out(); let input = env.read(8)?; env.write(&input, false, None)?; TestExtensionTestValue::mutate(|e| e.last_seen_buffer = input); Ok(RetVal::Converging(id)) }, 1 => { let env = env.only_in(); TestExtensionTestValue::mutate(|e| e.last_seen_input_len = env.val1()); Ok(RetVal::Converging(id)) }, 2 => { let mut env = env.buf_in_buf_out(); let mut enc = &env.read(9)?[4..8]; let weight = Weight::from_parts( u32::decode(&mut enc).map_err(|_| Error::::ContractTrapped)?.into(), 0, ); env.charge_weight(weight)?; Ok(RetVal::Converging(id)) }, 3 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), _ => { panic!("Passed unknown id to test chain extension: {}", func_id); }, } } fn enabled() -> bool { TestExtensionTestValue::get().enabled } } impl RegisteredChainExtension for TestExtension { const ID: u16 = 0; } impl ChainExtension for RevertingExtension { fn call(&mut self, _env: Environment) -> ExtensionResult where E: Ext, { Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![0x4B, 0x1D] }) } fn enabled() -> bool { TestExtensionTestValue::get().enabled } } impl RegisteredChainExtension for RevertingExtension { const ID: u16 = 1; } impl ChainExtension for DisabledExtension { fn call(&mut self, _env: Environment) -> ExtensionResult where E: Ext, { panic!("Disabled chain extensions are never called") } fn enabled() -> bool { false } } impl RegisteredChainExtension for DisabledExtension { const ID: u16 = 2; } impl ChainExtension for TempStorageExtension { fn call(&mut self, env: Environment) -> ExtensionResult where E: Ext, { let func_id = env.func_id(); match func_id { 0 => self.storage = 42, 1 => assert_eq!(self.storage, 42, "Storage is preserved inside the same call."), 2 => { assert_eq!(self.storage, 0, "Storage is different for different calls."); self.storage = 99; }, 3 => assert_eq!(self.storage, 99, "Storage is preserved inside the same call."), _ => { panic!("Passed unknown id to test chain extension: {}", func_id); }, } Ok(RetVal::Converging(0)) } fn enabled() -> bool { TestExtensionTestValue::get().enabled } } impl RegisteredChainExtension for TempStorageExtension { const ID: u16 = 3; } parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); pub static ExistentialDeposit: u64 = 1; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type AccountId = AccountId32; type Lookup = IdentityLookup; type Block = Block; type AccountData = pallet_balances::AccountData; } impl pallet_insecure_randomness_collective_flip::Config for Test {} impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = u64; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); type FreezeIdentifier = (); type MaxFreezes = (); type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; } impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); type MinimumPeriod = ConstU64<1>; type WeightInfo = (); } impl pallet_utility::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type PalletsOrigin = OriginCaller; type WeightInfo = (); } impl pallet_proxy::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type Currency = Balances; type ProxyType = (); type ProxyDepositBase = ConstU64<1>; type ProxyDepositFactor = ConstU64<1>; type MaxProxies = ConstU32<32>; type WeightInfo = (); type MaxPending = ConstU32<32>; type CallHasher = BlakeTwo256; type AnnouncementDepositBase = ConstU64<1>; type AnnouncementDepositFactor = ConstU64<1>; } impl pallet_dummy::Config for Test {} parameter_types! { pub MySchedule: Schedule = { let schedule = >::default(); schedule }; pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; pub static MaxDelegateDependencies: u32 = 32; pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); // We need this one set high enough for running benchmarks. pub static DefaultDepositLimit: BalanceOf = 10_000_000; } impl Convert> for Test { fn convert(w: Weight) -> BalanceOf { w.ref_time() } } /// A filter whose filter function can be swapped at runtime. pub struct TestFilter; #[derive(Clone)] pub struct Filters { filter: fn(&RuntimeCall) -> bool, } impl Default for Filters { fn default() -> Self { Filters { filter: (|_| true) } } } parameter_types! { static CallFilter: Filters = Default::default(); } impl TestFilter { pub fn set_filter(filter: fn(&RuntimeCall) -> bool) { CallFilter::mutate(|fltr| fltr.filter = filter); } } impl Contains for TestFilter { fn contains(call: &RuntimeCall) -> bool { (CallFilter::get().filter)(call) } } parameter_types! { pub static UploadAccount: Option<::AccountId> = None; pub static InstantiateAccount: Option<::AccountId> = None; } pub struct EnsureAccount(sp_std::marker::PhantomData<(T, A)>); impl>>> EnsureOrigin<::RuntimeOrigin> for EnsureAccount where ::AccountId: From, { type Success = T::AccountId; fn try_origin(o: T::RuntimeOrigin) -> Result { let who = as EnsureOrigin<_>>::try_origin(o.clone())?; if matches!(A::get(), Some(a) if who != a) { return Err(o) } Ok(who) } #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin() -> Result { Err(()) } } parameter_types! { pub static UnstableInterface: bool = true; } impl Config for Test { type Time = Timestamp; type Randomness = Randomness; type Currency = Balances; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type CallFilter = TestFilter; type CallStack = [Frame; 5]; type WeightPrice = Self; type WeightInfo = (); type ChainExtension = (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); type Schedule = MySchedule; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; type DefaultDepositLimit = DefaultDepositLimit; type AddressGenerator = DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = UnstableInterface; type UploadOrigin = EnsureAccount; type InstantiateOrigin = EnsureAccount; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; type RuntimeHoldReason = RuntimeHoldReason; type Migrations = crate::migration::codegen::BenchMigrations; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type MaxDelegateDependencies = MaxDelegateDependencies; type Debug = TestDebug; type Environment = (); type ApiVersion = (); type Xcm = (); } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); pub struct ExtBuilder { existential_deposit: u64, storage_version: Option, code_hashes: Vec>, } impl Default for ExtBuilder { fn default() -> Self { Self { existential_deposit: ExistentialDeposit::get(), storage_version: None, code_hashes: vec![], } } } impl ExtBuilder { pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { self.existential_deposit = existential_deposit; self } pub fn with_code_hashes(mut self, code_hashes: Vec>) -> Self { self.code_hashes = code_hashes; self } pub fn set_associated_consts(&self) { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } pub fn set_storage_version(mut self, version: u16) -> Self { self.storage_version = Some(StorageVersion::new(version)); self } pub fn build(self) -> sp_io::TestExternalities { use env_logger::{Builder, Env}; let env = Env::new().default_filter_or("runtime=debug"); let _ = Builder::from_env(env).is_test(true).try_init(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![] } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); ext.execute_with(|| { use frame_support::traits::OnGenesis; Pallet::::on_genesis(); if let Some(storage_version) = self.storage_version { storage_version.put::>(); } System::set_block_number(1) }); ext.execute_with(|| { for code_hash in self.code_hashes { CodeInfoOf::::insert(code_hash, crate::CodeInfo::new(ALICE)); } }); ext } } fn initialize_block(number: u64) { System::reset_events(); System::initialize(&number, &[0u8; 32].into(), &Default::default()); } struct ExtensionInput<'a> { extension_id: u16, func_id: u16, extra: &'a [u8], } impl<'a> ExtensionInput<'a> { fn to_vec(&self) -> Vec { ((self.extension_id as u32) << 16 | (self.func_id as u32)) .to_le_bytes() .iter() .chain(self.extra) .cloned() .collect() } } impl<'a> From> for Vec { fn from(input: ExtensionInput) -> Vec { input.to_vec() } } impl Default for Origin { fn default() -> Self { Self::Signed(ALICE) } } // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. // Then we check that at least the base costs where charged (no runtime gas costs.) #[test] fn calling_plain_account_fails() { ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000); let base_cost = <::WeightInfo as WeightInfo>::call(); assert_eq!( builder::call(BOB).build(), Err(DispatchErrorWithPostInfo { error: Error::::ContractNotFound.into(), post_info: PostDispatchInfo { actual_weight: Some(base_cost), pays_fee: Default::default(), }, }) ); }); } #[test] fn migration_on_idle_hooks_works() { // Defines expectations of how many migration steps can be done given the weight limit. let tests = [ (Weight::zero(), LATEST_MIGRATION_VERSION - 2), (::WeightInfo::migrate() + 1.into(), LATEST_MIGRATION_VERSION - 1), (Weight::MAX, LATEST_MIGRATION_VERSION), ]; for (weight, expected_version) in tests { ExtBuilder::default() .set_storage_version(LATEST_MIGRATION_VERSION - 2) .build() .execute_with(|| { MigrationInProgress::::set(Some(Default::default())); Contracts::on_idle(System::block_number(), weight); assert_eq!(StorageVersion::get::>(), expected_version); }); } } #[test] fn migration_in_progress_works() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); MigrationInProgress::::set(Some(Default::default())); assert_err!( Contracts::upload_code( RuntimeOrigin::signed(ALICE), vec![], None, Determinism::Enforced ), Error::::MigrationInProgress, ); assert_err!( Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), Error::::MigrationInProgress, ); assert_err!( Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB.clone(), code_hash), Error::::MigrationInProgress, ); assert_err_ignore_postinfo!(builder::call(BOB).build(), Error::::MigrationInProgress); assert_err_ignore_postinfo!( builder::instantiate_with_code(wasm).value(100_000).build(), Error::::MigrationInProgress, ); assert_err_ignore_postinfo!( builder::instantiate(code_hash).value(100_000).build(), Error::::MigrationInProgress, ); }); } #[test] fn instantiate_and_call_and_deposit_event() { let (wasm, code_hash) = compile_module::("event_and_return_on_deploy").unwrap(); ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); let value = 100; // We determine the storage deposit limit after uploading because it depends on ALICEs free // balance which is changed by uploading a module. assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, None, Determinism::Enforced )); // Drop previous events initialize_block(2); // Check at the end to get hash on error easily let addr = builder::bare_instantiate(Code::Existing(code_hash)) .value(value) .build_and_unwrap_account_id(); assert!(ContractInfoOf::::contains_key(&addr)); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { account: addr.clone() }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { account: addr.clone(), free_balance: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: addr.clone(), amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: addr.clone(), amount: value, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { contract: addr.clone(), data: vec![1, 2, 3, 4] }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Instantiated { deployer: ALICE, contract: addr.clone() }), topics: vec![hash(&ALICE), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndHeld { from: ALICE, to: addr.clone(), amount: test_utils::contract_info_storage_deposit(&addr), } ), topics: vec![hash(&ALICE), hash(&addr)], }, ] ); }); } #[test] fn deposit_event_max_value_limit() { let (wasm, _code_hash) = compile_module::("event_size").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(30_000) .build_and_unwrap_account_id(); // Call contract with allowed storage value. assert_ok!(builder::call(addr.clone()) .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer, .data(::Schedule::get().limits.payload_len.encode()) .build()); // Call contract with too large a storage value. assert_err_ignore_postinfo!( builder::call(addr) .data((::Schedule::get().limits.payload_len + 1).encode()) .build(), Error::::ValueTooLarge, ); }); } // Fail out of fuel (ref_time weight) in the engine. #[test] fn run_out_of_fuel_engine() { let (wasm, _code_hash) = compile_module::("run_out_of_gas").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(100 * min_balance) .build_and_unwrap_account_id(); // Call the contract with a fixed gas limit. It must run out of gas because it just // loops forever. assert_err_ignore_postinfo!( builder::call(addr) .gas_limit(Weight::from_parts(1_000_000_000_000, u64::MAX)) .build(), Error::::OutOfGas, ); }); } // Fail out of fuel (ref_time weight) in the host. #[test] fn run_out_of_fuel_host() { let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let gas_limit = Weight::from_parts(u32::MAX as u64, GAS_LIMIT.proof_size()); // Use chain extension to charge more ref_time than it is available. let result = builder::bare_call(addr.clone()) .gas_limit(gas_limit) .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &u32::MAX.encode() }.into()) .build() .result; assert_err!(result, >::OutOfGas); }); } #[test] fn gas_syncs_work() { let (code, _code_hash) = compile_module::("caller_is_origin_n").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_account_id(); let result = builder::bare_call(addr.clone()).data(0u32.encode()).build(); assert_ok!(result.result); let engine_consumed_noop = result.gas_consumed.ref_time(); let result = builder::bare_call(addr.clone()).data(1u32.encode()).build(); assert_ok!(result.result); let gas_consumed_once = result.gas_consumed.ref_time(); let host_consumed_once = ::Schedule::get().host_fn_weights.caller_is_origin.ref_time(); let engine_consumed_once = gas_consumed_once - host_consumed_once - engine_consumed_noop; let result = builder::bare_call(addr).data(2u32.encode()).build(); assert_ok!(result.result); let gas_consumed_twice = result.gas_consumed.ref_time(); let host_consumed_twice = host_consumed_once * 2; let engine_consumed_twice = gas_consumed_twice - host_consumed_twice - engine_consumed_noop; // Second contract just repeats first contract's instructions twice. // If runtime syncs gas with the engine properly, this should pass. assert_eq!(engine_consumed_twice, engine_consumed_once * 2); }); } /// Check that contracts with the same account id have different trie ids. /// Check the `Nonce` storage item for more information. #[test] fn instantiate_unique_trie_id() { let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, None, Determinism::Enforced) .unwrap(); // Instantiate the contract and store its trie id for later comparison. let addr = builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_account_id(); let trie_id = get_contract(&addr).trie_id; // Try to instantiate it again without termination should yield an error. assert_err_ignore_postinfo!( builder::instantiate(code_hash).build(), >::DuplicateContract, ); // Terminate the contract. assert_ok!(builder::call(addr.clone()).build()); // Re-Instantiate after termination. assert_ok!(builder::instantiate(code_hash).build()); // Trie ids shouldn't match or we might have a collision assert_ne!(trie_id, get_contract(&addr).trie_id); }); } #[test] fn storage_work() { let (code, _code_hash) = compile_module::("storage").unwrap(); ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); builder::bare_call(addr).build_and_unwrap_result(); }); } #[test] fn storage_max_value_limit() { let (wasm, _code_hash) = compile_module::("storage_size").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(30_000) .build_and_unwrap_account_id(); get_contract(&addr); // Call contract with allowed storage value. assert_ok!(builder::call(addr.clone()) .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer .data(::Schedule::get().limits.payload_len.encode()) .build()); // Call contract with too large a storage value. assert_err_ignore_postinfo!( builder::call(addr) .data((::Schedule::get().limits.payload_len + 1).encode()) .build(), Error::::ValueTooLarge, ); }); } #[test] fn deploy_and_call_other_contract() { let (caller_wasm, _caller_code_hash) = compile_module::("caller_contract").unwrap(); let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let min_balance = Contracts::min_balance(); // Create let _ = ::Currency::set_balance(&ALICE, 1_000_000); let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)) .value(100_000) .build_and_unwrap_account_id(); Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Enforced).unwrap(); let callee_addr = Contracts::contract_address( &caller_addr, &callee_code_hash, &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in wasm &[], ); // Drop previous events initialize_block(2); // Call BOB contract, which attempts to instantiate and call the callee contract and // makes various assertions on the results from those calls. assert_ok!(builder::call(caller_addr.clone()) .data(callee_code_hash.as_ref().to_vec()) .build()); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { account: callee_addr.clone() }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { account: callee_addr.clone(), free_balance: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: callee_addr.clone(), amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: caller_addr.clone(), to: callee_addr.clone(), amount: 32768 // hardcoded in wasm }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Instantiated { deployer: caller_addr.clone(), contract: callee_addr.clone(), }), topics: vec![hash(&caller_addr), hash(&callee_addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: caller_addr.clone(), to: callee_addr.clone(), amount: 32768, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(caller_addr.clone()), contract: callee_addr.clone(), }), topics: vec![ hash(&Origin::::from_account_id(caller_addr.clone())), hash(&callee_addr) ], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: caller_addr.clone(), }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&caller_addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndHeld { from: ALICE, to: callee_addr.clone(), amount: test_utils::contract_info_storage_deposit(&callee_addr), } ), topics: vec![hash(&ALICE), hash(&callee_addr)], }, ] ); }); } #[test] fn delegate_call() { let (caller_wasm, _caller_code_hash) = compile_module::("delegate_call").unwrap(); let (callee_wasm, callee_code_hash) = compile_module::("delegate_call_lib").unwrap(); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the 'caller' let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)) .value(300_000) .build_and_unwrap_account_id(); // Only upload 'callee' code assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), callee_wasm, Some(codec::Compact(100_000)), Determinism::Enforced, )); assert_ok!(builder::call(caller_addr.clone()) .value(1337) .data(callee_code_hash.as_ref().to_vec()) .build()); }); } #[test] fn track_check_uncheck_module_call() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); Contracts::bare_upload_code(ALICE, wasm, None, Determinism::Enforced).unwrap(); builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_result(); }); // It should have recorded 1 `Checked` for the upload and 1 `Unchecked` for the instantiate. let record = crate::wasm::tracker::LOADED_MODULE.with(|stack| stack.borrow().clone()); assert_eq!(record, vec![LoadingMode::Checked, LoadingMode::Unchecked]); } #[test] fn transfer_expendable_cannot_kill_account() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(1_000) .build_and_unwrap_account_id(); // Check that the BOB contract has been instantiated. get_contract(&addr); let total_balance = ::Currency::total_balance(&addr); assert_eq!( test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), test_utils::contract_info_storage_deposit(&addr) ); // Some ot the total balance is held, so it can't be transferred. assert_err!( <::Currency as Mutate>::transfer( &addr, &ALICE, total_balance, Preservation::Expendable, ), TokenError::FundsUnavailable, ); assert_eq!(::Currency::total_balance(&addr), total_balance); }); } #[test] fn cannot_self_destruct_through_draining() { let (wasm, _code_hash) = compile_module::("drain").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let value = 1_000; let min_balance = Contracts::min_balance(); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(value) .build_and_unwrap_account_id(); // Check that the BOB contract has been instantiated. get_contract(&addr); // Call BOB which makes it send all funds to the zero address // The contract code asserts that the transfer fails with the correct error code assert_ok!(builder::call(addr.clone()).build()); // Make sure the account wasn't remove by sending all free balance away. assert_eq!( ::Currency::total_balance(&addr), value + test_utils::contract_info_storage_deposit(&addr) + min_balance, ); }); } #[test] fn cannot_self_destruct_through_storage_refund_after_price_change() { let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); let info_deposit = test_utils::contract_info_storage_deposit(&addr); // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), info_deposit); assert_eq!(get_contract(&addr).extra_deposit(), 0); assert_eq!(::Currency::total_balance(&addr), info_deposit + min_balance); // Create 100 bytes of storage with a price of per byte and a single storage item of price 2 assert_ok!(builder::call(addr.clone()).data(100u32.to_le_bytes().to_vec()).build()); assert_eq!(get_contract(&addr).total_deposit(), info_deposit + 102); // Increase the byte price and trigger a refund. This should not have any influence because // the removal is pro rata and exactly those 100 bytes should have been removed. DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); assert_ok!(builder::call(addr.clone()).data(0u32.to_le_bytes().to_vec()).build()); // Make sure the account wasn't removed by the refund assert_eq!( ::Currency::total_balance(&addr), get_contract(&addr).total_deposit() + min_balance, ); assert_eq!(get_contract(&addr).extra_deposit(), 2); }); } #[test] fn cannot_self_destruct_while_live() { let (wasm, _code_hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(100_000) .build_and_unwrap_account_id(); // Check that the BOB contract has been instantiated. get_contract(&addr); // Call BOB with input data, forcing it make a recursive call to itself to // self-destruct, resulting in a trap. assert_err_ignore_postinfo!( builder::call(addr.clone()).data(vec![0]).build(), Error::::ContractTrapped, ); // Check that BOB is still there. get_contract(&addr); }); } #[test] fn self_destruct_works() { let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(1_000).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let _ = ::Currency::set_balance(&DJANGO, 1_000_000); let min_balance = Contracts::min_balance(); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(100_000) .build_and_unwrap_account_id(); // Check that the BOB contract has been instantiated. let _ = get_contract(&addr); let info_deposit = test_utils::contract_info_storage_deposit(&addr); // Drop all previous events initialize_block(2); // Call BOB without input data which triggers termination. assert_matches!(builder::call(addr.clone()).build(), Ok(_)); // Check that code is still there but refcount dropped to zero. assert_refcount!(&code_hash, 0); // Check that account is gone assert!(get_contract_checked(&addr).is_none()); assert_eq!(::Currency::total_balance(&addr), 0); // Check that the beneficiary (django) got remaining balance. assert_eq!( ::Currency::free_balance(DJANGO), 1_000_000 + 100_000 + min_balance ); // Check that the Alice is missing Django's benefit. Within ALICE's total balance there's // also the code upload deposit held. assert_eq!( ::Currency::total_balance(&ALICE), 1_000_000 - (100_000 + min_balance) ); pretty_assertions::assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Terminated { contract: addr.clone(), beneficiary: DJANGO }), topics: vec![hash(&addr), hash(&DJANGO)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndReleased { from: addr.clone(), to: ALICE, amount: info_deposit, } ), topics: vec![hash(&addr), hash(&ALICE)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::KilledAccount { account: addr.clone() }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: addr.clone(), to: DJANGO, amount: 100_000 + min_balance, }), topics: vec![], }, ], ); }); } // This tests that one contract cannot prevent another from self-destructing by sending it // additional funds after it has been drained. #[test] fn destroy_contract_and_transfer_funds() { let (callee_wasm, callee_code_hash) = compile_module::("self_destruct").unwrap(); let (caller_wasm, _caller_code_hash) = compile_module::("destroy_and_transfer").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create code hash for bob to instantiate let _ = ::Currency::set_balance(&ALICE, 1_000_000); Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Enforced).unwrap(); // This deploys the BOB contract, which in turn deploys the CHARLIE contract during // construction. let addr_bob = builder::bare_instantiate(Code::Upload(caller_wasm)) .value(200_000) .data(callee_code_hash.as_ref().to_vec()) .build_and_unwrap_account_id(); // Check that the CHARLIE contract has been instantiated. let addr_charlie = Contracts::contract_address(&addr_bob, &callee_code_hash, &[], &[0x47, 0x11]); get_contract(&addr_charlie); // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. assert_ok!(builder::call(addr_bob).data(addr_charlie.encode()).build()); // Check that CHARLIE has moved on to the great beyond (ie. died). assert!(get_contract_checked(&addr_charlie).is_none()); }); } #[test] fn cannot_self_destruct_in_constructor() { let (wasm, _) = compile_module::("self_destructing_constructor").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Fail to instantiate the BOB because the constructor calls seal_terminate. assert_err_ignore_postinfo!( builder::instantiate_with_code(wasm).value(100_000).build(), Error::::TerminatedInConstructor, ); }); } #[test] fn crypto_hashes() { let (wasm, _code_hash) = compile_module::("crypto_hashes").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the CRYPTO_HASHES contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(100_000) .build_and_unwrap_account_id(); // Perform the call. let input = b"_DEAD_BEEF"; use sp_io::hashing::*; // Wraps a hash function into a more dynamic form usable for testing. macro_rules! dyn_hash_fn { ($name:ident) => { Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice()) }; } // All hash functions and their associated output byte lengths. let test_cases: &[(Box Box<[u8]>>, usize)] = &[ (dyn_hash_fn!(sha2_256), 32), (dyn_hash_fn!(keccak_256), 32), (dyn_hash_fn!(blake2_256), 32), (dyn_hash_fn!(blake2_128), 16), ]; // Test the given hash functions for the input: "_DEAD_BEEF" for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() { // We offset data in the contract tables by 1. let mut params = vec![(n + 1) as u8]; params.extend_from_slice(input); let result = builder::bare_call(addr.clone()).data(params).build_and_unwrap_result(); assert!(!result.did_revert()); let expected = hash_fn(input.as_ref()); assert_eq!(&result.data[..*expected_size], &*expected); } }) } #[test] fn transfer_return_code() { let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(min_balance * 100) .build_and_unwrap_account_id(); // Contract has only the minimal balance so any transfer will fail. ::Currency::set_balance(&addr, min_balance); let result = builder::bare_call(addr.clone()).build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); }); } #[test] fn call_return_code() { let (caller_code, _caller_hash) = compile_module::("call_return_code").unwrap(); let (callee_code, _callee_hash) = compile_module::("ok_trap_revert").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); let addr_bob = builder::bare_instantiate(Code::Upload(caller_code)) .value(min_balance * 100) .data(vec![0]) .build_and_unwrap_account_id(); ::Currency::set_balance(&addr_bob, min_balance); // Contract calls into Django which is no valid contract let result = builder::bare_call(addr_bob.clone()) .data(AsRef::<[u8]>::as_ref(&DJANGO).to_vec()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::NotCallable); let addr_django = builder::bare_instantiate(Code::Upload(callee_code)) .origin(CHARLIE) .value(min_balance * 100) .data(vec![0]) .build_and_unwrap_account_id(); ::Currency::set_balance(&addr_django, min_balance); // Contract has only the minimal balance so any transfer will fail. let result = builder::bare_call(addr_bob.clone()) .data( AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&0u32.to_le_bytes()) .cloned() .collect(), ) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but callee reverts because "1" is passed. ::Currency::set_balance(&addr_bob, min_balance + 1000); let result = builder::bare_call(addr_bob.clone()) .data( AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&1u32.to_le_bytes()) .cloned() .collect(), ) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::CalleeReverted); // Contract has enough balance but callee traps because "2" is passed. let result = builder::bare_call(addr_bob) .data( AsRef::<[u8]>::as_ref(&addr_django) .iter() .chain(&2u32.to_le_bytes()) .cloned() .collect(), ) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); }); } #[test] fn instantiate_return_code() { let (caller_code, _caller_hash) = compile_module::("instantiate_return_code").unwrap(); let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); let callee_hash = callee_hash.as_ref().to_vec(); assert_ok!(builder::instantiate_with_code(callee_code).value(min_balance * 100).build()); let addr = builder::bare_instantiate(Code::Upload(caller_code)) .value(min_balance * 100) .build_and_unwrap_account_id(); // Contract has only the minimal balance so any transfer will fail. ::Currency::set_balance(&addr, min_balance); let result = builder::bare_call(addr.clone()) .data(callee_hash.clone()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but the passed code hash is invalid ::Currency::set_balance(&addr, min_balance + 10_000); let result = builder::bare_call(addr.clone()).data(vec![0; 33]).build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. let result = builder::bare_call(addr.clone()) .data(callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::CalleeReverted); // Contract has enough balance but callee traps because "2" is passed. let result = builder::bare_call(addr) .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); }); } #[test] fn disabled_chain_extension_wont_deploy() { let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); TestExtension::disable(); assert_err_ignore_postinfo!( builder::instantiate_with_code(code).value(3 * min_balance).build(), >::CodeRejected, ); }); } #[test] fn disabled_chain_extension_errors_on_call() { let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); TestExtension::disable(); assert_err_ignore_postinfo!( builder::call(addr.clone()).build(), Error::::CodeRejected, ); }); } #[test] fn chain_extension_works() { let (code, _hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); // 0 = read input buffer and pass it through as output let input: Vec = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); let result = builder::bare_call(addr.clone()).data(input.clone()).build(); assert_eq!(TestExtension::last_seen_buffer(), input); assert_eq!(result.result.unwrap().data, input); // 1 = treat inputs as integer primitives and store the supplied integers builder::bare_call(addr.clone()) .data(ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into()) .build_and_unwrap_result(); assert_eq!(TestExtension::last_seen_input_len(), 4); // 2 = charge some extra weight (amount supplied in the fifth byte) let result = builder::bare_call(addr.clone()) .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &0u32.encode() }.into()) .build(); assert_ok!(result.result); let gas_consumed = result.gas_consumed; let result = builder::bare_call(addr.clone()) .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &42u32.encode() }.into()) .build(); assert_ok!(result.result); assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42); let result = builder::bare_call(addr.clone()) .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &95u32.encode() }.into()) .build(); assert_ok!(result.result); assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95); // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer let result = builder::bare_call(addr.clone()) .data(ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into()) .build_and_unwrap_result(); assert_eq!(result.flags, ReturnFlags::REVERT); assert_eq!(result.data, vec![42, 99]); // diverging to second chain extension that sets flags to 0x1 and returns a fixed buffer // We set the MSB part to 1 (instead of 0) which routes the request into the second // extension let result = builder::bare_call(addr.clone()) .data(ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into()) .build_and_unwrap_result(); assert_eq!(result.flags, ReturnFlags::REVERT); assert_eq!(result.data, vec![0x4B, 0x1D]); // Diverging to third chain extension that is disabled // We set the MSB part to 2 (instead of 0) which routes the request into the third extension assert_err_ignore_postinfo!( builder::call(addr.clone()) .data(ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into()) .build(), Error::::NoChainExtension, ); }); } #[test] fn chain_extension_temp_storage_works() { let (code, _hash) = compile_module::("chain_extension_temp_storage").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); // Call func 0 and func 1 back to back. let stop_recursion = 0u8; let mut input: Vec = ExtensionInput { extension_id: 3, func_id: 0, extra: &[] }.into(); input.extend_from_slice( ExtensionInput { extension_id: 3, func_id: 1, extra: &[stop_recursion] } .to_vec() .as_ref(), ); assert_ok!(builder::bare_call(addr.clone()).data(input.clone()).build().result); }) } #[test] fn lazy_removal_works() { let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let info = get_contract(&addr); let trie = &info.child_trie_info(); // Put value into the contracts child trie child::put(trie, &[99], &42); // Terminate the contract assert_ok!(builder::call(addr.clone()).build()); // Contract info should be gone assert!(!>::contains_key(&addr)); // But value should be still there as the lazy removal did not run, yet. assert_matches!(child::get(trie, &[99]), Some(42)); // Run the lazy removal Contracts::on_idle(System::block_number(), Weight::MAX); // Value should be gone now assert_matches!(child::get::(trie, &[99]), None); }); } #[test] fn lazy_batch_removal_works() { let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let mut tries: Vec = vec![]; for i in 0..3u8 { let addr = builder::bare_instantiate(Code::Upload(code.clone())) .value(min_balance * 100) .salt(vec![i]) .build_and_unwrap_account_id(); let info = get_contract(&addr); let trie = &info.child_trie_info(); // Put value into the contracts child trie child::put(trie, &[99], &42); // Terminate the contract. Contract info should be gone, but value should be still there // as the lazy removal did not run, yet. assert_ok!(builder::call(addr.clone()).build()); assert!(!>::contains_key(&addr)); assert_matches!(child::get(trie, &[99]), Some(42)); tries.push(trie.clone()) } // Run single lazy removal Contracts::on_idle(System::block_number(), Weight::MAX); // The single lazy removal should have removed all queued tries for trie in tries.iter() { assert_matches!(child::get::(trie, &[99]), None); } }); } #[test] fn lazy_removal_partial_remove_works() { let (code, _hash) = compile_module::("self_destruct").unwrap(); // We create a contract with some extra keys above the weight limit let extra_keys = 7u32; let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); let vals: Vec<_> = (0..max_keys + extra_keys) .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) .collect(); let mut ext = ExtBuilder::default().existential_deposit(50).build(); let trie = ext.execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let info = get_contract(&addr); // Put value into the contracts child trie for val in &vals { info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); } >::insert(&addr, info.clone()); // Terminate the contract assert_ok!(builder::call(addr.clone()).build()); // Contract info should be gone assert!(!>::contains_key(&addr)); let trie = info.child_trie_info(); // But value should be still there as the lazy removal did not run, yet. for val in &vals { assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); } trie.clone() }); // The lazy removal limit only applies to the backend but not to the overlay. // This commits all keys from the overlay to the backend. ext.commit_all().unwrap(); ext.execute_with(|| { // Run the lazy removal ContractInfo::::process_deletion_queue_batch(&mut meter); // Weight should be exhausted because we could not even delete all keys assert!(!meter.can_consume(weight_per_key)); let mut num_deleted = 0u32; let mut num_remaining = 0u32; for val in &vals { match child::get::(&trie, &blake2_256(&val.0)) { None => num_deleted += 1, Some(x) if x == val.1 => num_remaining += 1, Some(_) => panic!("Unexpected value in contract storage"), } } // All but one key is removed assert_eq!(num_deleted + num_remaining, vals.len() as u32); assert_eq!(num_deleted, max_keys); assert_eq!(num_remaining, extra_keys); }); } #[test] fn lazy_removal_does_no_run_on_low_remaining_weight() { let (code, _hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let info = get_contract(&addr); let trie = &info.child_trie_info(); // Put value into the contracts child trie child::put(trie, &[99], &42); // Terminate the contract assert_ok!(builder::call(addr.clone()).build()); // Contract info should be gone assert!(!>::contains_key(&addr)); // But value should be still there as the lazy removal did not run, yet. assert_matches!(child::get(trie, &[99]), Some(42)); // Assign a remaining weight which is too low for a successful deletion of the contract let low_remaining_weight = <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); // Run the lazy removal Contracts::on_idle(System::block_number(), low_remaining_weight); // Value should still be there, since remaining weight was too low for removal assert_matches!(child::get::(trie, &[99]), Some(42)); // Run the lazy removal while deletion_queue is not full Contracts::on_initialize(System::block_number()); // Value should still be there, since deletion_queue was not full assert_matches!(child::get::(trie, &[99]), Some(42)); // Run on_idle with max remaining weight, this should remove the value Contracts::on_idle(System::block_number(), Weight::MAX); // Value should be gone assert_matches!(child::get::(trie, &[99]), None); }); } #[test] fn lazy_removal_does_not_use_all_weight() { let (code, _hash) = compile_module::("self_destruct").unwrap(); let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); let mut ext = ExtBuilder::default().existential_deposit(50).build(); let (trie, vals, weight_per_key) = ext.execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let info = get_contract(&addr); let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); assert!(max_keys > 0); // We create a contract with one less storage item than we can remove within the limit let vals: Vec<_> = (0..max_keys - 1) .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) .collect(); // Put value into the contracts child trie for val in &vals { info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); } >::insert(&addr, info.clone()); // Terminate the contract assert_ok!(builder::call(addr.clone()).build()); // Contract info should be gone assert!(!>::contains_key(&addr)); let trie = info.child_trie_info(); // But value should be still there as the lazy removal did not run, yet. for val in &vals { assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); } (trie, vals, weight_per_key) }); // The lazy removal limit only applies to the backend but not to the overlay. // This commits all keys from the overlay to the backend. ext.commit_all().unwrap(); ext.execute_with(|| { // Run the lazy removal ContractInfo::::process_deletion_queue_batch(&mut meter); let base_weight = <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); assert_eq!(meter.consumed(), weight_per_key.mul(vals.len() as _) + base_weight); // All the keys are removed for val in vals { assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); } }); } #[test] fn deletion_queue_ring_buffer_overflow() { let (code, _hash) = compile_module::("self_destruct").unwrap(); let mut ext = ExtBuilder::default().existential_deposit(50).build(); // setup the deletion queue with custom counters ext.execute_with(|| { let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); >::set(queue); }); // commit the changes to the storage ext.commit_all().unwrap(); ext.execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let mut tries: Vec = vec![]; // add 3 contracts to the deletion queue for i in 0..3u8 { let addr = builder::bare_instantiate(Code::Upload(code.clone())) .value(min_balance * 100) .salt(vec![i]) .build_and_unwrap_account_id(); let info = get_contract(&addr); let trie = &info.child_trie_info(); // Put value into the contracts child trie child::put(trie, &[99], &42); // Terminate the contract. Contract info should be gone, but value should be still // there as the lazy removal did not run, yet. assert_ok!(builder::call(addr.clone()).build()); assert!(!>::contains_key(&addr)); assert_matches!(child::get(trie, &[99]), Some(42)); tries.push(trie.clone()) } // Run single lazy removal Contracts::on_idle(System::block_number(), Weight::MAX); // The single lazy removal should have removed all queued tries for trie in tries.iter() { assert_matches!(child::get::(trie, &[99]), None); } // insert and delete counter values should go from u32::MAX - 1 to 1 assert_eq!(>::get().as_test_tuple(), (1, 1)); }) } #[test] fn refcounter() { let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); // Create two contracts with the same code and check that they do in fact share it. let addr0 = builder::bare_instantiate(Code::Upload(wasm.clone())) .value(min_balance * 100) .salt(vec![0]) .build_and_unwrap_account_id(); let addr1 = builder::bare_instantiate(Code::Upload(wasm.clone())) .value(min_balance * 100) .salt(vec![1]) .build_and_unwrap_account_id(); assert_refcount!(code_hash, 2); // Sharing should also work with the usual instantiate call let addr2 = builder::bare_instantiate(Code::Existing(code_hash)) .value(min_balance * 100) .salt(vec![2]) .build_and_unwrap_account_id(); assert_refcount!(code_hash, 3); // Terminating one contract should decrement the refcount assert_ok!(builder::call(addr0).build()); assert_refcount!(code_hash, 2); // remove another one assert_ok!(builder::call(addr1).build()); assert_refcount!(code_hash, 1); // Pristine code should still be there PristineCode::::get(code_hash).unwrap(); // remove the last contract assert_ok!(builder::call(addr2).build()); assert_refcount!(code_hash, 0); // refcount is `0` but code should still exists because it needs to be removed manually assert!(crate::PristineCode::::contains_key(&code_hash)); }); } #[test] fn debug_message_works() { let (wasm, _code_hash) = compile_module::("debug_message_works").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(30_000) .build_and_unwrap_account_id(); let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); assert_matches!(result.result, Ok(_)); assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); }); } #[test] fn debug_message_logging_disabled() { let (wasm, _code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(30_000) .build_and_unwrap_account_id(); // disable logging by passing `false` let result = builder::bare_call(addr.clone()).build(); assert_matches!(result.result, Ok(_)); // the dispatchables always run without debugging assert_ok!(Contracts::call(RuntimeOrigin::signed(ALICE), addr, 0, GAS_LIMIT, None, vec![])); assert!(result.debug_message.is_empty()); }); } #[test] fn debug_message_invalid_utf8() { let (wasm, _code_hash) = compile_module::("debug_message_invalid_utf8").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(30_000) .build_and_unwrap_account_id(); let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); assert_ok!(result.result); assert!(result.debug_message.is_empty()); }); } #[test] fn gas_estimation_for_subcalls() { let (caller_code, _caller_hash) = compile_module::("call_with_limit").unwrap(); let (call_runtime_code, _caller_hash) = compile_module::("call_runtime").unwrap(); let (dummy_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let addr_dummy = builder::bare_instantiate(Code::Upload(dummy_code)) .value(min_balance * 100) .build_and_unwrap_account_id(); let addr_call_runtime = builder::bare_instantiate(Code::Upload(call_runtime_code)) .value(min_balance * 100) .build_and_unwrap_account_id(); // Run the test for all of those weight limits for the subcall let weights = [ Weight::zero(), GAS_LIMIT, GAS_LIMIT * 2, GAS_LIMIT / 5, Weight::from_parts(0, GAS_LIMIT.proof_size()), Weight::from_parts(GAS_LIMIT.ref_time(), 0), ]; // This call is passed to the sub call in order to create a large `required_weight` let runtime_call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { pre_charge: Weight::from_parts(10_000_000_000, 512 * 1024), actual_weight: Weight::from_parts(1, 1), }) .encode(); // Encodes which contract should be sub called with which input let sub_calls: [(&[u8], Vec<_>, bool); 2] = [ (addr_dummy.as_ref(), vec![], false), (addr_call_runtime.as_ref(), runtime_call, true), ]; for weight in weights { for (sub_addr, sub_input, out_of_gas_in_subcall) in &sub_calls { let input: Vec = sub_addr .iter() .cloned() .chain(weight.ref_time().to_le_bytes()) .chain(weight.proof_size().to_le_bytes()) .chain(sub_input.clone()) .collect(); // Call in order to determine the gas that is required for this call let result = builder::bare_call(addr_caller.clone()).data(input.clone()).build(); assert_ok!(&result.result); // If the out of gas happens in the subcall the caller contract // will just trap. Otherwise we would need to forward an error // code to signal that the sub contract ran out of gas. let error: DispatchError = if *out_of_gas_in_subcall { assert!(result.gas_required.all_gt(result.gas_consumed)); >::ContractTrapped.into() } else { assert_eq!(result.gas_required, result.gas_consumed); >::OutOfGas.into() }; // Make the same call using the estimated gas. Should succeed. assert_ok!( builder::bare_call(addr_caller.clone()) .gas_limit(result.gas_required) .storage_deposit_limit(Some(result.storage_deposit.charge_or_zero())) .data(input.clone()) .build() .result ); // Check that it fails with too little ref_time assert_err!( builder::bare_call(addr_caller.clone()) .gas_limit(result.gas_required.sub_ref_time(1)) .storage_deposit_limit(Some(result.storage_deposit.charge_or_zero())) .data(input.clone()) .build() .result, error, ); // Check that it fails with too little proof_size assert_err!( builder::bare_call(addr_caller.clone()) .gas_limit(result.gas_required.sub_proof_size(1)) .storage_deposit_limit(Some(result.storage_deposit.charge_or_zero())) .data(input) .build() .result, error, ); } } }); } #[test] fn gas_estimation_call_runtime() { let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) .value(min_balance * 100) .salt(vec![0]) .build_and_unwrap_account_id(); // Call something trivial with a huge gas limit so that we can observe the effects // of pre-charging. This should create a difference between consumed and required. let call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { pre_charge: Weight::from_parts(10_000_000, 1_000), actual_weight: Weight::from_parts(100, 100), }); let result = builder::bare_call(addr_caller.clone()).data(call.encode()).build(); // contract encodes the result of the dispatch runtime let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); assert_eq!(outcome, 0); assert!(result.gas_required.all_gt(result.gas_consumed)); // Make the same call using the required gas. Should succeed. assert_ok!( builder::bare_call(addr_caller) .gas_limit(result.gas_required) .data(call.encode()) .build() .result ); }); } #[test] fn call_runtime_reentrancy_guarded() { let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) .value(min_balance * 100) .salt(vec![0]) .build_and_unwrap_account_id(); let addr_callee = builder::bare_instantiate(Code::Upload(callee_code)) .value(min_balance * 100) .salt(vec![1]) .build_and_unwrap_account_id(); // Call pallet_contracts call() dispatchable let call = RuntimeCall::Contracts(crate::Call::call { dest: addr_callee, value: 0, gas_limit: GAS_LIMIT / 3, storage_deposit_limit: None, data: vec![], }); // Call runtime to re-enter back to contracts engine by // calling dummy contract let result = builder::bare_call(addr_caller.clone()) .data(call.encode()) .build_and_unwrap_result(); // Call to runtime should fail because of the re-entrancy guard assert_return_code!(result, RuntimeReturnCode::CallRuntimeFailed); }); } #[test] fn ecdsa_recover() { let (wasm, _code_hash) = compile_module::("ecdsa_recover").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the ecdsa_recover contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(100_000) .build_and_unwrap_account_id(); #[rustfmt::skip] let signature: [u8; 65] = [ 161, 234, 203, 74, 147, 96, 51, 212, 5, 174, 231, 9, 142, 48, 137, 201, 162, 118, 192, 67, 239, 16, 71, 216, 125, 86, 167, 139, 70, 7, 86, 241, 33, 87, 154, 251, 81, 29, 160, 4, 176, 239, 88, 211, 244, 232, 232, 52, 211, 234, 100, 115, 230, 47, 80, 44, 152, 166, 62, 50, 8, 13, 86, 175, 28, ]; #[rustfmt::skip] let message_hash: [u8; 32] = [ 162, 28, 244, 179, 96, 76, 244, 178, 188, 83, 230, 248, 143, 106, 77, 117, 239, 95, 244, 171, 65, 95, 62, 153, 174, 166, 182, 28, 130, 73, 196, 208 ]; #[rustfmt::skip] const EXPECTED_COMPRESSED_PUBLIC_KEY: [u8; 33] = [ 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, ]; let mut params = vec![]; params.extend_from_slice(&signature); params.extend_from_slice(&message_hash); assert!(params.len() == 65 + 32); let result = builder::bare_call(addr.clone()).data(params).build_and_unwrap_result(); assert!(!result.did_revert()); assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); }) } #[test] fn bare_instantiate_returns_events() { let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let result = builder::bare_instantiate(Code::Upload(wasm)) .value(min_balance * 100) .collect_events(CollectEvents::UnsafeCollect) .build(); let events = result.events.unwrap(); assert!(!events.is_empty()); assert_eq!(events, System::events()); }); } #[test] fn bare_instantiate_does_not_return_events() { let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let result = builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); let events = result.events; assert!(!System::events().is_empty()); assert!(events.is_none()); }); } #[test] fn bare_call_returns_events() { let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(min_balance * 100) .build_and_unwrap_account_id(); let result = builder::bare_call(addr.clone()) .collect_events(CollectEvents::UnsafeCollect) .build(); let events = result.events.unwrap(); assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); assert!(!events.is_empty()); assert_eq!(events, System::events()); }); } #[test] fn bare_call_does_not_return_events() { let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(min_balance * 100) .build_and_unwrap_account_id(); let result = builder::bare_call(addr.clone()).build(); let events = result.events; assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); assert!(!System::events().is_empty()); assert!(events.is_none()); }); } #[test] fn sr25519_verify() { let (wasm, _code_hash) = compile_module::("sr25519_verify").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the sr25519_verify contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(100_000) .build_and_unwrap_account_id(); let call_with = |message: &[u8; 11]| { // Alice's signature for "hello world" #[rustfmt::skip] let signature: [u8; 64] = [ 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255, 228, 54, 115, 63, 30, 207, 205, 131, ]; // Alice's public key #[rustfmt::skip] let public_key: [u8; 32] = [ 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, ]; let mut params = vec![]; params.extend_from_slice(&signature); params.extend_from_slice(&public_key); params.extend_from_slice(message); builder::bare_call(addr.clone()).data(params).build_and_unwrap_result() }; // verification should succeed for "hello world" assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); // verification should fail for other messages assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); }); } #[test] fn failed_deposit_charge_should_roll_back_call() { let (wasm_caller, _) = compile_module::("call_runtime_and_call").unwrap(); let (wasm_callee, _) = compile_module::("store_call").unwrap(); const ED: u64 = 200; let execute = || { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate both contracts. let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller.clone())) .build_and_unwrap_account_id(); let addr_callee = builder::bare_instantiate(Code::Upload(wasm_callee.clone())) .build_and_unwrap_account_id(); // Give caller proxy access to Alice. assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(ALICE), addr_caller.clone(), (), 0)); // Create a Proxy call that will attempt to transfer away Alice's balance. let transfer_call = Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: CHARLIE, value: pallet_balances::Pallet::::free_balance(&ALICE) - 2 * ED, })); // Wrap the transfer call in a proxy call. let transfer_proxy_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { real: ALICE, force_proxy_type: Some(()), call: transfer_call, }); let data = ( (ED - DepositPerItem::get()) as u32, // storage length addr_callee, transfer_proxy_call, ); >::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), 0, GAS_LIMIT, None, data.encode(), ) }) }; // With a low enough deposit per byte, the call should succeed. let result = execute().unwrap(); // Bump the deposit per byte to a high value to trigger a FundsUnavailable error. DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 20); assert_err_with_weight!(execute(), TokenError::FundsUnavailable, result.actual_weight); } #[test] fn upload_code_works() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Drop previous events initialize_block(2); assert!(!PristineCode::::contains_key(&code_hash)); assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), Determinism::Enforced, )); // Ensure the contract was stored and get expected deposit amount to be reserved. let deposit_expected = expected_deposit(ensure_stored(code_hash)); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash, deposit_held: deposit_expected, uploader: ALICE }), topics: vec![code_hash], },] ); }); } #[test] fn upload_code_limit_too_low() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); let deposit_expected = expected_deposit(wasm.len()); let deposit_insufficient = deposit_expected.saturating_sub(1); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Drop previous events initialize_block(2); assert_noop!( Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(deposit_insufficient)), Determinism::Enforced ), >::StorageDepositLimitExhausted, ); assert_eq!(System::events(), vec![]); }); } #[test] fn upload_code_not_enough_balance() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); let deposit_expected = expected_deposit(wasm.len()); let deposit_insufficient = deposit_expected.saturating_sub(1); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); // Drop previous events initialize_block(2); assert_noop!( Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), Determinism::Enforced ), >::StorageDepositNotEnoughFunds, ); assert_eq!(System::events(), vec![]); }); } #[test] fn remove_code_works() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Drop previous events initialize_block(2); assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), Determinism::Enforced, )); // Ensure the contract was stored and get expected deposit amount to be reserved. let deposit_expected = expected_deposit(ensure_stored(code_hash)); assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash, deposit_held: deposit_expected, uploader: ALICE }), topics: vec![code_hash], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { code_hash, deposit_released: deposit_expected, remover: ALICE }), topics: vec![code_hash], }, ] ); }); } #[test] fn remove_code_wrong_origin() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Drop previous events initialize_block(2); assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), Determinism::Enforced, )); // Ensure the contract was stored and get expected deposit amount to be reserved. let deposit_expected = expected_deposit(ensure_stored(code_hash)); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), sp_runtime::traits::BadOrigin, ); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash, deposit_held: deposit_expected, uploader: ALICE }), topics: vec![code_hash], },] ); }); } #[test] fn remove_code_in_use() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); assert_ok!(builder::instantiate_with_code(wasm).build()); // Drop previous events initialize_block(2); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), >::CodeInUse, ); assert_eq!(System::events(), vec![]); }); } #[test] fn remove_code_not_found() { let (_wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Drop previous events initialize_block(2); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), >::CodeNotFound, ); assert_eq!(System::events(), vec![]); }); } #[test] fn instantiate_with_zero_balance_works() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); // Drop previous events initialize_block(2); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); // Ensure the contract was stored and get expected deposit amount to be reserved. let deposit_expected = expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though no free balance was send assert_eq!(::Currency::free_balance(&addr), min_balance); assert_eq!( ::Currency::total_balance(&addr), min_balance + test_utils::contract_info_storage_deposit(&addr) ); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash, deposit_held: deposit_expected, uploader: ALICE }), topics: vec![code_hash], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { account: addr.clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { account: addr.clone(), free_balance: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: addr.clone(), amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Instantiated { deployer: ALICE, contract: addr.clone(), }), topics: vec![hash(&ALICE), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndHeld { from: ALICE, to: addr.clone(), amount: test_utils::contract_info_storage_deposit(&addr), } ), topics: vec![hash(&ALICE), hash(&addr)], }, ] ); }); } #[test] fn instantiate_with_below_existential_deposit_works() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); let value = 50; // Drop previous events initialize_block(2); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(value) .build_and_unwrap_account_id(); // Ensure the contract was stored and get expected deposit amount to be reserved. let deposit_expected = expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though not enough free balance was send assert_eq!(::Currency::free_balance(&addr), min_balance + value); assert_eq!( ::Currency::total_balance(&addr), min_balance + value + test_utils::contract_info_storage_deposit(&addr) ); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash, deposit_held: deposit_expected, uploader: ALICE }), topics: vec![code_hash], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { account: addr.clone() }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { account: addr.clone(), free_balance: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: addr.clone(), amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: addr.clone(), amount: 50, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Instantiated { deployer: ALICE, contract: addr.clone(), }), topics: vec![hash(&ALICE), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndHeld { from: ALICE, to: addr.clone(), amount: test_utils::contract_info_storage_deposit(&addr), } ), topics: vec![hash(&ALICE), hash(&addr)], }, ] ); }); } #[test] fn storage_deposit_works() { let (wasm, _code_hash) = compile_module::("multi_store").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); let mut deposit = test_utils::contract_info_storage_deposit(&addr); // Drop previous events initialize_block(2); // Create storage assert_ok!(builder::call(addr.clone()) .value(42) .data((1_000u32, 5_000u32).encode()) .build()); // 4 is for creating 2 storage items let charged0 = 4 + 1_000 + 5_000; deposit += charged0; assert_eq!(get_contract(&addr).total_deposit(), deposit); // Add more storage (but also remove some) assert_ok!(builder::call(addr.clone()).data((2_000u32, 4_900u32).encode()).build()); let charged1 = 1_000 - 100; deposit += charged1; assert_eq!(get_contract(&addr).total_deposit(), deposit); // Remove more storage (but also add some) assert_ok!(builder::call(addr.clone()).data((2_100u32, 900u32).encode()).build()); // -1 for numeric instability let refunded0 = 4_000 - 100 - 1; deposit -= refunded0; assert_eq!(get_contract(&addr).total_deposit(), deposit); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, to: addr.clone(), amount: 42, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndHeld { from: ALICE, to: addr.clone(), amount: charged0, } ), topics: vec![hash(&ALICE), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndHeld { from: ALICE, to: addr.clone(), amount: charged1, } ), topics: vec![hash(&ALICE), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts( pallet_contracts::Event::StorageDepositTransferredAndReleased { from: addr.clone(), to: ALICE, amount: refunded0, } ), topics: vec![hash(&addr.clone()), hash(&ALICE)], }, ] ); }); } #[test] fn storage_deposit_callee_works() { let (wasm_caller, _code_hash_caller) = compile_module::("call").unwrap(); let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); // Create both contracts: Constructors do nothing. let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); let addr_callee = builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); assert_ok!(builder::call(addr_caller).data((100u32, &addr_callee).encode()).build()); let callee = get_contract(&addr_callee); let deposit = DepositPerByte::get() * 100 + DepositPerItem::get() * 1; assert_eq!(test_utils::get_balance(&addr_callee), min_balance); assert_eq!( callee.total_deposit(), deposit + test_utils::contract_info_storage_deposit(&addr_callee) ); }); } #[test] fn set_code_extrinsic() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); let (new_wasm, new_code_hash) = compile_module::("crypto_hashes").unwrap(); assert_ne!(code_hash, new_code_hash); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), new_wasm, None, Determinism::Enforced )); // Drop previous events initialize_block(2); assert_eq!(get_contract(&addr).code_hash, code_hash); assert_refcount!(&code_hash, 1); assert_refcount!(&new_code_hash, 0); // only root can execute this extrinsic assert_noop!( Contracts::set_code(RuntimeOrigin::signed(ALICE), addr.clone(), new_code_hash), sp_runtime::traits::BadOrigin, ); assert_eq!(get_contract(&addr).code_hash, code_hash); assert_refcount!(&code_hash, 1); assert_refcount!(&new_code_hash, 0); assert_eq!(System::events(), vec![]); // contract must exist assert_noop!( Contracts::set_code(RuntimeOrigin::root(), BOB, new_code_hash), >::ContractNotFound, ); assert_eq!(get_contract(&addr).code_hash, code_hash); assert_refcount!(&code_hash, 1); assert_refcount!(&new_code_hash, 0); assert_eq!(System::events(), vec![]); // new code hash must exist assert_noop!( Contracts::set_code(RuntimeOrigin::root(), addr.clone(), Default::default()), >::CodeNotFound, ); assert_eq!(get_contract(&addr).code_hash, code_hash); assert_refcount!(&code_hash, 1); assert_refcount!(&new_code_hash, 0); assert_eq!(System::events(), vec![]); // successful call assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr.clone(), new_code_hash)); assert_eq!(get_contract(&addr).code_hash, new_code_hash); assert_refcount!(&code_hash, 0); assert_refcount!(&new_code_hash, 1); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(pallet_contracts::Event::ContractCodeUpdated { contract: addr.clone(), new_code_hash, old_code_hash: code_hash, }), topics: vec![hash(&addr), new_code_hash, code_hash], },] ); }); } #[test] fn slash_cannot_kill_account() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let value = 700; let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); let addr = builder::bare_instantiate(Code::Upload(wasm)) .value(value) .build_and_unwrap_account_id(); // Drop previous events initialize_block(2); let info_deposit = test_utils::contract_info_storage_deposit(&addr); assert_eq!( test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), info_deposit ); assert_eq!( ::Currency::total_balance(&addr), info_deposit + value + min_balance ); // Try to destroy the account of the contract by slashing the total balance. // The account does not get destroyed because slashing only affects the balance held under // certain `reason`. Slashing can for example happen if the contract takes part in staking. let _ = ::Currency::slash( &HoldReason::StorageDepositReserve.into(), &addr, ::Currency::total_balance(&addr), ); // Slashing only removed the balance held. assert_eq!(::Currency::total_balance(&addr), value + min_balance); }); } #[test] fn contract_reverted() { let (wasm, code_hash) = compile_module::("return_with_data").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let flags = ReturnFlags::REVERT; let buffer = [4u8, 8, 15, 16, 23, 42]; let input = (flags.bits(), buffer).encode(); // We just upload the code for later use assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm.clone(), None, Determinism::Enforced )); // Calling extrinsic: revert leads to an error assert_err_ignore_postinfo!( builder::instantiate(code_hash).data(input.clone()).build(), >::ContractReverted, ); // Calling extrinsic: revert leads to an error assert_err_ignore_postinfo!( builder::instantiate_with_code(wasm).data(input.clone()).build(), >::ContractReverted, ); // Calling directly: revert leads to success but the flags indicate the error // This is just a different way of transporting the error that allows the read out // the `data` which is only there on success. Obviously, the contract isn't // instantiated. let result = builder::bare_instantiate(Code::Existing(code_hash)) .data(input.clone()) .build_and_unwrap_result(); assert_eq!(result.result.flags, flags); assert_eq!(result.result.data, buffer); assert!(!>::contains_key(result.account_id)); // Pass empty flags and therefore successfully instantiate the contract for later use. let addr = builder::bare_instantiate(Code::Existing(code_hash)) .data(ReturnFlags::empty().bits().encode()) .build_and_unwrap_account_id(); // Calling extrinsic: revert leads to an error assert_err_ignore_postinfo!( builder::call(addr.clone()).data(input.clone()).build(), >::ContractReverted, ); // Calling directly: revert leads to success but the flags indicate the error let result = builder::bare_call(addr.clone()).data(input).build_and_unwrap_result(); assert_eq!(result.flags, flags); assert_eq!(result.data, buffer); }); } #[test] fn set_code_hash() { let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); let (new_wasm, new_code_hash) = compile_module::("new_set_code_hash_contract").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the 'caller' let contract_addr = builder::bare_instantiate(Code::Upload(wasm)) .value(300_000) .build_and_unwrap_account_id(); // upload new code assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), new_wasm.clone(), None, Determinism::Enforced )); System::reset_events(); // First call sets new code_hash and returns 1 let result = builder::bare_call(contract_addr.clone()) .data(new_code_hash.as_ref().to_vec()) .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); assert_return_code!(result, 1); // Second calls new contract code that returns 2 let result = builder::bare_call(contract_addr.clone()) .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); assert_return_code!(result, 2); // Checking for the last event only assert_eq!( &System::events(), &[ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { contract: contract_addr.clone(), new_code_hash, old_code_hash: code_hash, }), topics: vec![hash(&contract_addr), new_code_hash, code_hash], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: contract_addr.clone(), }), topics: vec![ hash(&Origin::::from_account_id(ALICE)), hash(&contract_addr) ], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: contract_addr.clone(), }), topics: vec![ hash(&Origin::::from_account_id(ALICE)), hash(&contract_addr) ], }, ], ); }); } #[test] fn storage_deposit_limit_is_enforced() { let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let min_balance = Contracts::min_balance(); // Setting insufficient storage_deposit should fail. assert_err!( builder::bare_instantiate(Code::Upload(wasm.clone())) // expected deposit is 2 * ed + 3 for the call .storage_deposit_limit(Some((2 * min_balance + 3 - 1).into())) .build() .result, >::StorageDepositLimitExhausted, ); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); let info_deposit = test_utils::contract_info_storage_deposit(&addr); // Check that the BOB contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), info_deposit); assert_eq!(::Currency::total_balance(&addr), info_deposit + min_balance); // Create 1 byte of storage with a price of per byte, // setting insufficient deposit limit, as it requires 3 Balance: // 2 for the item added + 1 for the new storage item. assert_err_ignore_postinfo!( builder::call(addr.clone()) .storage_deposit_limit(Some(codec::Compact(2))) .data(1u32.to_le_bytes().to_vec()) .build(), >::StorageDepositLimitExhausted, ); // To check that deposit limit fallbacks to DefaultDepositLimit, // we customize it here. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 3); // Create 1 byte of storage, should cost 3 Balance: // 2 for the item added + 1 for the new storage item. // Should pass as it fallbacks to DefaultDepositLimit. assert_ok!(builder::call(addr.clone()).data(1u32.to_le_bytes().to_vec()).build()); // Use 4 more bytes of the storage for the same item, which requires 4 Balance. // Should fail as DefaultDepositLimit is 3 and hence isn't enough. assert_err_ignore_postinfo!( builder::call(addr.clone()).data(5u32.to_le_bytes().to_vec()).build(), >::StorageDepositLimitExhausted, ); }); } #[test] fn deposit_limit_in_nested_calls() { let (wasm_caller, _code_hash_caller) = compile_module::("create_storage_and_call").unwrap(); let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Create both contracts: Constructors do nothing. let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); let addr_callee = builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); // Create 100 bytes of storage with a price of per byte // This is 100 Balance + 2 Balance for the item assert_ok!(builder::call(addr_callee.clone()) .storage_deposit_limit(Some(codec::Compact(102))) .data(100u32.to_le_bytes().to_vec()) .build()); // We do not remove any storage but add a storage item of 12 bytes in the caller // contract. This would cost 12 + 2 = 14 Balance. // The nested call doesn't get a special limit, which is set by passing 0 to it. // This should fail as the specified parent's limit is less than the cost: 13 < // 14. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .storage_deposit_limit(Some(codec::Compact(13))) .data((100u32, &addr_callee, 0u64).encode()) .build(), >::StorageDepositLimitExhausted, ); // Now we specify the parent's limit high enough to cover the caller's storage additions. // However, we use a single byte more in the callee, hence the storage deposit should be 15 // Balance. // The nested call doesn't get a special limit, which is set by passing 0 to it. // This should fail as the specified parent's limit is less than the cost: 14 // < 15. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .storage_deposit_limit(Some(codec::Compact(14))) .data((101u32, &addr_callee, 0u64).encode()) .build(), >::StorageDepositLimitExhausted, ); // Now we specify the parent's limit high enough to cover both the caller's and callee's // storage additions. However, we set a special deposit limit of 1 Balance for the nested // call. This should fail as callee adds up 2 bytes to the storage, meaning that the nested // call should have a deposit limit of at least 2 Balance. The sub-call should be rolled // back, which is covered by the next test case. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .storage_deposit_limit(Some(codec::Compact(16))) .data((102u32, &addr_callee, 1u64).encode()) .build(), >::StorageDepositLimitExhausted, ); // Refund in the callee contract but not enough to cover the 14 Balance required by the // caller. Note that if previous sub-call wouldn't roll back, this call would pass making // the test case fail. We don't set a special limit for the nested call here. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .storage_deposit_limit(Some(codec::Compact(0))) .data((87u32, &addr_callee, 0u64).encode()) .build(), >::StorageDepositLimitExhausted, ); let _ = ::Currency::set_balance(&ALICE, 1_000); // Require more than the sender's balance. // We don't set a special limit for the nested call. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .data((1200u32, &addr_callee, 1u64).encode()) .build(), >::StorageDepositLimitExhausted, ); // Same as above but allow for the additional deposit of 1 Balance in parent. // We set the special deposit limit of 1 Balance for the nested call, which isn't // enforced as callee frees up storage. This should pass. assert_ok!(builder::call(addr_caller.clone()) .storage_deposit_limit(Some(codec::Compact(1))) .data((87u32, &addr_callee, 1u64).encode()) .build()); }); } #[test] fn deposit_limit_in_nested_instantiate() { let (wasm_caller, _code_hash_caller) = compile_module::("create_storage_and_instantiate").unwrap(); let (wasm_callee, code_hash_callee) = compile_module::("store_deploy").unwrap(); const ED: u64 = 5; ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let _ = ::Currency::set_balance(&BOB, 1_000_000); // Create caller contract let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller)) .value(10_000u64) // this balance is later passed to the deployed contract .build_and_unwrap_account_id(); // Deploy a contract to get its occupied storage size let addr = builder::bare_instantiate(Code::Upload(wasm_callee)) .data(vec![0, 0, 0, 0]) .build_and_unwrap_account_id(); let callee_info_len = ContractInfoOf::::get(&addr).unwrap().encoded_size() as u64; // We don't set a special deposit limit for the nested instantiation. // // The deposit limit set for the parent is insufficient for the instantiation, which // requires: // - callee_info_len + 2 for storing the new contract info, // - ED for deployed contract account, // - 2 for the storage item of 0 bytes being created in the callee constructor // or (callee_info_len + 2 + ED + 2) Balance in total. // // Provided the limit is set to be 1 Balance less, // this call should fail on the return from the caller contract. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(Some(codec::Compact(callee_info_len + 2 + ED + 1))) .data((0u32, &code_hash_callee, 0u64).encode()) .build(), >::StorageDepositLimitExhausted, ); // The charges made on instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); // Now we give enough limit for the instantiation itself, but require for 1 more storage // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on the // return from constructor. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(Some(codec::Compact(callee_info_len + 2 + ED + 2))) .data((1u32, &code_hash_callee, 0u64).encode()) .build(), >::StorageDepositLimitExhausted, ); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); // Now we set enough limit in parent call, but an insufficient limit for child instantiate. // This should fail during the charging for the instantiation in // `RawMeter::charge_instantiate()` assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(Some(codec::Compact(callee_info_len + 2 + ED + 2))) .data((0u32, &code_hash_callee, callee_info_len + 2 + ED + 1).encode()) .build(), >::StorageDepositLimitExhausted, ); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); // Same as above but requires for single added storage // item of 1 byte to be covered by the limit, which implies 3 more Balance. // Now we set enough limit for the parent call, but insufficient limit for child // instantiate. This should fail right after the constructor execution. assert_err_ignore_postinfo!( builder::call(addr_caller.clone()) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(Some(codec::Compact(callee_info_len + 2 + ED + 3))) // enough parent limit .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 2).encode()) .build(), >::StorageDepositLimitExhausted, ); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); // Set enough deposit limit for the child instantiate. This should succeed. let result = builder::bare_call(addr_caller.clone()) .origin(BOB) .storage_deposit_limit(Some(codec::Compact(callee_info_len + 2 + ED + 4).into())) .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 3).encode()) .build(); let returned = result.result.unwrap(); // All balance of the caller except ED has been transferred to the callee. // No deposit has been taken from it. assert_eq!(::Currency::free_balance(&addr_caller), ED); // Get address of the deployed contract. let addr_callee = AccountId32::from_slice(&returned.data[0..32]).unwrap(); // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the // origin. assert_eq!(::Currency::free_balance(&addr_callee), 10_000 + ED); // The origin should be charged with: // - callee instantiation deposit = (callee_info_len + 2) // - callee account ED // - for writing an item of 1 byte to storage = 3 Balance assert_eq!( ::Currency::free_balance(&BOB), 1_000_000 - (callee_info_len + 2 + ED + 3) ); // Check that deposit due to be charged still includes these 3 Balance assert_eq!(result.storage_deposit.charge_or_zero(), (callee_info_len + 2 + ED + 3)) }); } #[test] fn deposit_limit_honors_liquidity_restrictions() { let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let bobs_balance = 1_000; let _ = ::Currency::set_balance(&ALICE, 1_000_000); let _ = ::Currency::set_balance(&BOB, bobs_balance); let min_balance = Contracts::min_balance(); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); let info_deposit = test_utils::contract_info_storage_deposit(&addr); // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), info_deposit); assert_eq!(::Currency::total_balance(&addr), info_deposit + min_balance); // check that the hold is honored ::Currency::hold( &HoldReason::CodeUploadDepositReserve.into(), &BOB, bobs_balance - min_balance, ) .unwrap(); assert_err_ignore_postinfo!( builder::call(addr.clone()) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(Some(codec::Compact(200))) .data(100u32.to_le_bytes().to_vec()) .build(), >::StorageDepositNotEnoughFunds, ); assert_eq!(::Currency::free_balance(&BOB), min_balance); }); } #[test] fn deposit_limit_honors_existential_deposit() { let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let _ = ::Currency::set_balance(&BOB, 1_000); let min_balance = Contracts::min_balance(); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); let info_deposit = test_utils::contract_info_storage_deposit(&addr); // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), info_deposit); assert_eq!(::Currency::total_balance(&addr), min_balance + info_deposit); // check that the deposit can't bring the account below the existential deposit assert_err_ignore_postinfo!( builder::call(addr.clone()) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(Some(codec::Compact(900))) .data(100u32.to_le_bytes().to_vec()) .build(), >::StorageDepositNotEnoughFunds, ); assert_eq!(::Currency::free_balance(&BOB), 1_000); }); } #[test] fn deposit_limit_honors_min_leftover() { let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let _ = ::Currency::set_balance(&BOB, 1_000); let min_balance = Contracts::min_balance(); // Instantiate the BOB contract. let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); let info_deposit = test_utils::contract_info_storage_deposit(&addr); // Check that the contract has been instantiated and has the minimum balance and the storage // deposit assert_eq!(get_contract(&addr).total_deposit(), info_deposit); assert_eq!(::Currency::total_balance(&addr), info_deposit + min_balance); // check that the minimum leftover (value send) is considered assert_err_ignore_postinfo!( builder::call(addr.clone()) .origin(RuntimeOrigin::signed(BOB)) .value(400) .storage_deposit_limit(Some(codec::Compact(500))) .data(100u32.to_le_bytes().to_vec()) .build(), >::StorageDepositNotEnoughFunds, ); assert_eq!(::Currency::free_balance(&BOB), 1_000); }); } #[test] fn upload_should_enforce_deterministic_mode_when_possible() { let upload = |fixture, determinism| { let (wasm, code_hash) = compile_module::(fixture).unwrap(); ExtBuilder::default() .build() .execute_with(|| -> Result { let _ = ::Currency::set_balance(&ALICE, 1_000_000); Contracts::bare_upload_code(ALICE, wasm, None, determinism)?; let info = CodeInfoOf::::get(code_hash).unwrap(); Ok(info.determinism()) }) }; assert_eq!(upload("dummy", Determinism::Enforced), Ok(Determinism::Enforced)); assert_eq!(upload("dummy", Determinism::Relaxed), Ok(Determinism::Enforced)); assert_eq!(upload("float_instruction", Determinism::Relaxed), Ok(Determinism::Relaxed)); assert!(upload("float_instruction", Determinism::Enforced).is_err()); } #[test] fn cannot_instantiate_indeterministic_code() { let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); let (caller_wasm, _) = compile_module::("instantiate_return_code").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Try to instantiate directly from code assert_err_ignore_postinfo!( builder::instantiate_with_code(wasm.clone()).build(), >::CodeRejected, ); assert_err!( builder::bare_instantiate(Code::Upload(wasm.clone())).build().result, >::CodeRejected, ); // Try to upload a non-deterministic code as deterministic assert_err!( Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm.clone(), None, Determinism::Enforced ), >::CodeRejected, ); // Try to instantiate from already stored indeterministic code hash assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, None, Determinism::Relaxed, )); assert_err_ignore_postinfo!( builder::instantiate(code_hash).build(), >::Indeterministic, ); assert_err!( builder::bare_instantiate(Code::Existing(code_hash)).build().result, >::Indeterministic, ); // Deploy contract which instantiates another contract let addr = builder::bare_instantiate(Code::Upload(caller_wasm)).build_and_unwrap_account_id(); // Try to instantiate `code_hash` from another contract in deterministic mode assert_err!( builder::bare_call(addr.clone()).data(code_hash.encode()).build().result, >::Indeterministic, ); // Instantiations are not allowed even in non-determinism mode assert_err!( builder::bare_call(addr.clone()).data(code_hash.encode()).build().result, >::Indeterministic, ); }); } #[test] fn cannot_set_code_indeterministic_code() { let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); let (caller_wasm, _) = compile_module::("set_code_hash").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Put the non-deterministic contract on-chain assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, None, Determinism::Relaxed, )); // Create the contract that will call `seal_set_code_hash` let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)).build_and_unwrap_account_id(); // We do not allow to set the code hash to a non-deterministic wasm assert_err!( builder::bare_call(caller_addr.clone()).data(code_hash.encode()).build().result, >::Indeterministic, ); }); } #[test] fn delegate_call_indeterministic_code() { let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); let (caller_wasm, _) = compile_module::("delegate_call_simple").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Put the non-deterministic contract on-chain assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, None, Determinism::Relaxed, )); // Create the contract that will call `seal_delegate_call` let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)).build_and_unwrap_account_id(); // The delegate call will fail in deterministic mode assert_err!( builder::bare_call(caller_addr.clone()).data(code_hash.encode()).build().result, >::Indeterministic, ); // The delegate call will work on non-deterministic mode assert_ok!( builder::bare_call(caller_addr.clone()) .data(code_hash.encode()) .determinism(Determinism::Relaxed) .build() .result ); }); } #[test] fn locking_delegate_dependency_works() { // set hash lock up deposit to 30%, to test deposit calculation. CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); MAX_DELEGATE_DEPENDENCIES.with(|c| *c.borrow_mut() = 1); let (wasm_caller, self_code_hash) = compile_module::("locking_delegate_dependency").unwrap(); let (wasm_callee, code_hash) = compile_module::("dummy").unwrap(); let (wasm_other, other_code_hash) = compile_module::("call").unwrap(); // Define inputs with various actions to test locking / unlocking delegate_dependencies. // See the contract for more details. let noop_input = (0u32, code_hash); let lock_delegate_dependency_input = (1u32, code_hash); let unlock_delegate_dependency_input = (2u32, code_hash); let terminate_input = (3u32, code_hash); // Instantiate the caller contract with the given input. let instantiate = |input: &(u32, H256)| { builder::bare_instantiate(Code::Upload(wasm_caller.clone())) .data(input.encode()) .build() }; // Call contract with the given input. let call = |addr_caller: &AccountId32, input: &(u32, H256)| { builder::bare_call(addr_caller.clone()).data(input.encode()).build() }; const ED: u64 = 2000; ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { let _ = Balances::set_balance(&ALICE, 1_000_000); // Instantiate with lock_delegate_dependency should fail since the code is not yet on chain. assert_err!( instantiate(&lock_delegate_dependency_input).result, Error::::CodeNotFound ); // Upload the delegated code. let CodeUploadReturnValue { deposit, .. } = Contracts::bare_upload_code(ALICE, wasm_callee.clone(), None, Determinism::Enforced) .unwrap(); // Instantiate should now work. let addr_caller = instantiate(&lock_delegate_dependency_input).result.unwrap().account_id; // There should be a dependency and a deposit. let contract = test_utils::get_contract(&addr_caller); let dependency_deposit = &CodeHashLockupDepositPercent::get().mul_ceil(deposit); assert_eq!(contract.delegate_dependencies().get(&code_hash), Some(dependency_deposit)); assert_eq!( test_utils::get_balance_on_hold( &HoldReason::StorageDepositReserve.into(), &addr_caller ), dependency_deposit + contract.storage_base_deposit() - ED ); // Removing the code should fail, since we have added a dependency. assert_err!( Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), >::CodeInUse ); // Locking an already existing dependency should fail. assert_err!( call(&addr_caller, &lock_delegate_dependency_input).result, Error::::DelegateDependencyAlreadyExists ); // Locking self should fail. assert_err!( call(&addr_caller, &(1u32, self_code_hash)).result, Error::::CannotAddSelfAsDelegateDependency ); // Locking more than the maximum allowed delegate_dependencies should fail. Contracts::bare_upload_code(ALICE, wasm_other, None, Determinism::Enforced).unwrap(); assert_err!( call(&addr_caller, &(1u32, other_code_hash)).result, Error::::MaxDelegateDependenciesReached ); // Unlocking dependency should work. assert_ok!(call(&addr_caller, &unlock_delegate_dependency_input).result); // Dependency should be removed, and deposit should be returned. let contract = test_utils::get_contract(&addr_caller); assert!(contract.delegate_dependencies().is_empty()); assert_eq!( test_utils::get_balance_on_hold( &HoldReason::StorageDepositReserve.into(), &addr_caller ), contract.storage_base_deposit() - ED ); // Removing a nonexistent dependency should fail. assert_err!( call(&addr_caller, &unlock_delegate_dependency_input).result, Error::::DelegateDependencyNotFound ); // Locking a dependency with a storage limit too low should fail. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = dependency_deposit - 1); assert_err!( call(&addr_caller, &lock_delegate_dependency_input).result, Error::::StorageDepositLimitExhausted ); // Since we unlocked the dependency we should now be able to remove the code. assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); // Calling should fail since the delegated contract is not on chain anymore. assert_err!(call(&addr_caller, &noop_input).result, Error::::ContractTrapped); // Restore initial deposit limit and add the dependency back. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 10_000_000); Contracts::bare_upload_code(ALICE, wasm_callee, None, Determinism::Enforced).unwrap(); call(&addr_caller, &lock_delegate_dependency_input).result.unwrap(); // Call terminate should work, and return the deposit. let balance_before = test_utils::get_balance(&ALICE); assert_ok!(call(&addr_caller, &terminate_input).result); assert_eq!( test_utils::get_balance(&ALICE), balance_before + contract.storage_base_deposit() + dependency_deposit ); // Terminate should also remove the dependency, so we can remove the code. assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); }); } #[test] fn native_dependency_deposit_works() { let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); let (dummy_wasm, dummy_code_hash) = compile_module::("dummy").unwrap(); // Set hash lock up deposit to 30%, to test deposit calculation. CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); // Set a low existential deposit so that the base storage deposit is based on the contract // storage deposit rather than the existential deposit. const ED: u64 = 10; // Test with both existing and uploaded code for code in [Code::Upload(wasm.clone()), Code::Existing(code_hash)] { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { let _ = Balances::set_balance(&ALICE, 1_000_000); let lockup_deposit_percent = CodeHashLockupDepositPercent::get(); // Upload the dummy contract, Contracts::upload_code( RuntimeOrigin::signed(ALICE), dummy_wasm.clone(), None, Determinism::Enforced, ) .unwrap(); // Upload `set_code_hash` contracts if using Code::Existing. let add_upload_deposit = match code { Code::Existing(_) => { Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm.clone(), None, Determinism::Enforced, ) .unwrap(); false }, Code::Upload(_) => true, }; // Instantiate the set_code_hash contract. let res = builder::bare_instantiate(code).build(); let addr = res.result.unwrap().account_id; let base_deposit = ED + test_utils::contract_info_storage_deposit(&addr); let upload_deposit = test_utils::get_code_deposit(&code_hash); let extra_deposit = add_upload_deposit.then(|| upload_deposit).unwrap_or_default(); // Check initial storage_deposit // The base deposit should be: ED + contract_info_storage_deposit + 30% * deposit let deposit = extra_deposit + base_deposit + lockup_deposit_percent.mul_ceil(upload_deposit); assert_eq!(res.storage_deposit.charge_or_zero(), deposit); // call set_code_hash builder::bare_call(addr.clone()) .data(dummy_code_hash.encode()) .build_and_unwrap_result(); // Check updated storage_deposit let code_deposit = test_utils::get_code_deposit(&dummy_code_hash); let deposit = base_deposit + lockup_deposit_percent.mul_ceil(code_deposit); assert_eq!(test_utils::get_contract(&addr).storage_base_deposit(), deposit); assert_eq!( test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), deposit - ED ); }); } } #[test] fn reentrance_count_works_with_call() { let (wasm, _code_hash) = compile_module::("reentrance_count_call").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let contract_addr = builder::bare_instantiate(Code::Upload(wasm)) .value(300_000) .build_and_unwrap_account_id(); // passing reentrant count to the input let input = 0.encode(); builder::bare_call(contract_addr) .data(input) .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); }); } #[test] fn reentrance_count_works_with_delegated_call() { let (wasm, code_hash) = compile_module::("reentrance_count_delegated_call").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let contract_addr = builder::bare_instantiate(Code::Upload(wasm)) .value(300_000) .build_and_unwrap_account_id(); // adding a callstack height to the input let input = (code_hash, 1).encode(); builder::bare_call(contract_addr.clone()) .data(input) .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); }); } #[test] fn account_reentrance_count_works() { let (wasm, _code_hash) = compile_module::("account_reentrance_count_call").unwrap(); let (wasm_reentrance_count, _code_hash_reentrance_count) = compile_module::("reentrance_count_call").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let contract_addr = builder::bare_instantiate(Code::Upload(wasm)) .value(300_000) .build_and_unwrap_account_id(); let another_contract_addr = builder::bare_instantiate(Code::Upload(wasm_reentrance_count)) .value(300_000) .build_and_unwrap_account_id(); let result1 = builder::bare_call(contract_addr.clone()) .data(contract_addr.encode()) .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); let result2 = builder::bare_call(contract_addr.clone()) .data(another_contract_addr.encode()) .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); assert_eq!(result1.data, 1.encode()); assert_eq!(result2.data, 0.encode()); }); } #[test] fn root_cannot_upload_code() { let (wasm, _) = compile_module::("dummy").unwrap(); ExtBuilder::default().build().execute_with(|| { assert_noop!( Contracts::upload_code(RuntimeOrigin::root(), wasm, None, Determinism::Enforced), DispatchError::BadOrigin, ); }); } #[test] fn root_cannot_remove_code() { let (_, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().build().execute_with(|| { assert_noop!( Contracts::remove_code(RuntimeOrigin::root(), code_hash), DispatchError::BadOrigin, ); }); } #[test] fn signed_cannot_set_code() { let (_, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().build().execute_with(|| { assert_noop!( Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB, code_hash), DispatchError::BadOrigin, ); }); } #[test] fn none_cannot_call_code() { ExtBuilder::default().build().execute_with(|| { assert_noop!( builder::call(BOB).origin(RuntimeOrigin::none()).build(), DispatchError::BadOrigin, ); }); } #[test] fn root_can_call() { let (wasm, _) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); // Call the contract. assert_ok!(builder::call(addr.clone()).origin(RuntimeOrigin::root()).build()); }); } #[test] fn root_cannot_instantiate_with_code() { let (wasm, _) = compile_module::("dummy").unwrap(); ExtBuilder::default().build().execute_with(|| { assert_err_ignore_postinfo!( builder::instantiate_with_code(wasm).origin(RuntimeOrigin::root()).build(), DispatchError::BadOrigin ); }); } #[test] fn root_cannot_instantiate() { let (_, code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().build().execute_with(|| { assert_err_ignore_postinfo!( builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), DispatchError::BadOrigin ); }); } #[test] fn only_upload_origin_can_upload() { let (wasm, _) = compile_module::("dummy").unwrap(); UploadAccount::set(Some(ALICE)); ExtBuilder::default().build().execute_with(|| { let _ = Balances::set_balance(&ALICE, 1_000_000); let _ = Balances::set_balance(&BOB, 1_000_000); assert_err!( Contracts::upload_code( RuntimeOrigin::root(), wasm.clone(), None, Determinism::Enforced, ), DispatchError::BadOrigin ); assert_err!( Contracts::upload_code( RuntimeOrigin::signed(BOB), wasm.clone(), None, Determinism::Enforced, ), DispatchError::BadOrigin ); // Only alice is allowed to upload contract code. assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm.clone(), None, Determinism::Enforced, )); }); } #[test] fn only_instantiation_origin_can_instantiate() { let (code, code_hash) = compile_module::("dummy").unwrap(); InstantiateAccount::set(Some(ALICE)); ExtBuilder::default().build().execute_with(|| { let _ = Balances::set_balance(&ALICE, 1_000_000); let _ = Balances::set_balance(&BOB, 1_000_000); assert_err_ignore_postinfo!( builder::instantiate_with_code(code.clone()) .origin(RuntimeOrigin::root()) .build(), DispatchError::BadOrigin ); assert_err_ignore_postinfo!( builder::instantiate_with_code(code.clone()) .origin(RuntimeOrigin::signed(BOB)) .build(), DispatchError::BadOrigin ); // Only Alice can instantiate assert_ok!(builder::instantiate_with_code(code).build()); // Bob cannot instantiate with either `instantiate_with_code` or `instantiate`. assert_err_ignore_postinfo!( builder::instantiate(code_hash).origin(RuntimeOrigin::signed(BOB)).build(), DispatchError::BadOrigin ); }); } #[test] fn balance_api_returns_free_balance() { let (wasm, _code_hash) = compile_module::("balance").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); // Instantiate the BOB contract without any extra balance. let addr = builder::bare_instantiate(Code::Upload(wasm.to_vec())).build_and_unwrap_account_id(); let value = 0; // Call BOB which makes it call the balance runtime API. // The contract code asserts that the returned balance is 0. assert_ok!(builder::call(addr.clone()).value(value).build()); let value = 1; // Calling with value will trap the contract. assert_err_ignore_postinfo!( builder::call(addr.clone()).value(value).build(), >::ContractTrapped ); }); } #[test] fn gas_consumed_is_linear_for_nested_calls() { let (code, _code_hash) = compile_module::("recurse").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); let addr = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_account_id(); let max_call_depth = ::CallStack::size() as u32; let [gas_0, gas_1, gas_2, gas_max] = { [0u32, 1u32, 2u32, max_call_depth] .iter() .map(|i| { let result = builder::bare_call(addr.clone()).data(i.encode()).build(); assert_ok!(result.result); result.gas_consumed }) .collect::>() .try_into() .unwrap() }; let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); assert_eq!(gas_max, gas_0 + gas_per_recursion * max_call_depth as u64); }); }