mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Contracts Add deposit for dependencies (#14079)
* wip * fixes * rm comment * join fns * clippy * Fix limits * reduce diff * fix * fix * fix typo * refactor store to use self * refactor run to take self by value * pass tests * rm comment * fixes * fix typo * rm * fix fmt * clippy * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * Update frame/contracts/src/lib.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * Update frame/contracts/src/wasm/mod.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * Update frame/contracts/src/wasm/mod.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * PR review, rm duplicate increment_refcount * PR review * Update frame/contracts/src/wasm/prepare.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * Add test for failing storage_deposit * fix lint * wip * Delegate update take 2 * update * fix migration * fix migration * doc * fix lint * update migration * fix warning * reformat comment * regenerate weightInfo trait * fix merge * PR review https://github.com/paritytech/substrate/pull/14079#discussion_r1255904563 * PR review https://github.com/paritytech/substrate/pull/14079/files#r1257521373 * PR review remove optimisation https://github.com/paritytech/substrate/pull/14079/files#r1263312237 * PR review fix return type https://github.com/paritytech/substrate/pull/14079/files#r1263315804 * Apply suggestions from code review Co-authored-by: Alexander Theißen <alex.theissen@me.com> * PR review pass CodeInfo and update docstring https://github.com/paritytech/substrate/pull/14079/files#r1257522327 * PR review add code_info to the executable https://github.com/paritytech/substrate/pull/14079/files#r1263309049 * rename info -> contract_info * Update frame/contracts/src/exec.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/fixtures/add_remove_delegate_dependency.wat Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/migration/v13.rs * fix tests * Fmt & fix tests * Test Result<(), _> return type * Update frame/contracts/src/migration.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Revert "Test Result<(), _> return type" This reverts commit a876168f2054edf84d720c666387583ccbe78dcd. * add / update doc comments * fix backticks * Revert "Revert "Test Result<(), _> return type"" This reverts commit 3cbb6161d1abd9520cd9f8519b4dfbf4f29a2998. * fix bench * fix bench * fix * Update frame/contracts/src/storage/meter.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * rm stale comments * Apply suggestions from code review Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * PR suggestion * Add missing doc * fx lint * ".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_contracts * Update frame/contracts/src/lib.rs Co-authored-by: Juan <juangirini@gmail.com> --------- Co-authored-by: command-bot <> Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Juan <juangirini@gmail.com>
This commit is contained in:
@@ -1223,6 +1223,7 @@ parameter_types! {
|
||||
pub const DepositPerByte: Balance = deposit(0, 1);
|
||||
pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024);
|
||||
pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
|
||||
pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30);
|
||||
}
|
||||
|
||||
impl pallet_contracts::Config for Runtime {
|
||||
@@ -1255,6 +1256,8 @@ impl pallet_contracts::Config for Runtime {
|
||||
type Migrations = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Migrations = (NoopMigration<1>, NoopMigration<2>);
|
||||
type MaxDelegateDependencies = ConstU32<32>;
|
||||
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
|
||||
}
|
||||
|
||||
impl pallet_sudo::Config for Runtime {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
;; This contract tests the behavior of adding / removing delegate_dependencies when delegate calling into a contract.
|
||||
(module
|
||||
(import "seal0" "add_delegate_dependency" (func $add_delegate_dependency (param i32)))
|
||||
(import "seal0" "remove_delegate_dependency" (func $remove_delegate_dependency (param i32)))
|
||||
(import "seal0" "input" (func $input (param i32 i32)))
|
||||
(import "seal1" "terminate" (func $terminate (param i32)))
|
||||
(import "seal0" "delegate_call" (func $delegate_call (param i32 i32 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [100, 132) Address of Alice
|
||||
(data (i32.const 100)
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
)
|
||||
|
||||
(func $assert (param i32)
|
||||
(block $ok
|
||||
(br_if $ok
|
||||
(get_local 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
|
||||
;; This function loads input data and performs the action specified.
|
||||
;; The first 4 bytes of the input specify the action to perform.
|
||||
;; The next 32 bytes specify the code hash to use when calling add_delegate_dependency or remove_delegate_dependency.
|
||||
;; Actions are:
|
||||
;; 1: call add_delegate_dependency
|
||||
;; 2: call remove_delegate_dependency.
|
||||
;; 3: call terminate.
|
||||
;; Any other value is a no-op.
|
||||
(func $load_input
|
||||
(local $action i32)
|
||||
(local $code_hash_ptr i32)
|
||||
|
||||
;; Store available input size at offset 0.
|
||||
(i32.store (i32.const 0) (i32.const 512))
|
||||
|
||||
;; Read input data.
|
||||
(call $input (i32.const 4) (i32.const 0))
|
||||
|
||||
;; Input data layout.
|
||||
;; [0..4) - size of the call
|
||||
;; [4..8) - action to perform
|
||||
;; [8..42) - code hash of the callee
|
||||
(set_local $action (i32.load (i32.const 4)))
|
||||
(set_local $code_hash_ptr (i32.const 8))
|
||||
|
||||
;; Assert input size == 36 (4 for action + 32 for code_hash).
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(i32.load (i32.const 0))
|
||||
(i32.const 36)
|
||||
)
|
||||
)
|
||||
|
||||
;; Call add_delegate_dependency when action == 1.
|
||||
(if (i32.eq (get_local $action) (i32.const 1))
|
||||
(then
|
||||
(call $add_delegate_dependency (get_local $code_hash_ptr))
|
||||
)
|
||||
(else)
|
||||
)
|
||||
|
||||
;; Call remove_delegate_dependency when action == 2.
|
||||
(if (i32.eq (get_local $action) (i32.const 2))
|
||||
(then
|
||||
(call $remove_delegate_dependency
|
||||
(get_local $code_hash_ptr)
|
||||
)
|
||||
)
|
||||
(else)
|
||||
)
|
||||
|
||||
;; Call terminate when action == 3.
|
||||
(if (i32.eq (get_local $action) (i32.const 3))
|
||||
(then
|
||||
(call $terminate
|
||||
(i32.const 100) ;; Pointer to beneficiary address
|
||||
)
|
||||
(unreachable) ;; terminate never returns
|
||||
)
|
||||
(else)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy")
|
||||
(call $load_input)
|
||||
)
|
||||
|
||||
(func (export "call")
|
||||
(call $load_input)
|
||||
|
||||
;; Delegate call into passed code hash.
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(call $delegate_call
|
||||
(i32.const 0) ;; Set no call flags.
|
||||
(i32.const 8) ;; Pointer to "callee" code_hash.
|
||||
(i32.const 0) ;; Input is ignored.
|
||||
(i32.const 0) ;; Length of the input.
|
||||
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output.
|
||||
(i32.const 0) ;; Length is ignored in this case.
|
||||
)
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
@@ -30,7 +30,7 @@ use self::{
|
||||
};
|
||||
use crate::{
|
||||
exec::{AccountIdOf, Key},
|
||||
migration::{v09, v10, v11, v12, MigrationStep},
|
||||
migration::{v09, v10, v11, v12, v13, MigrationStep},
|
||||
wasm::CallFlags,
|
||||
Pallet as Contracts, *,
|
||||
};
|
||||
@@ -257,6 +257,19 @@ benchmarks! {
|
||||
m.step();
|
||||
}
|
||||
|
||||
// This benchmarks the v13 migration step (Add delegate_dependencies field).
|
||||
#[pov_mode = Measured]
|
||||
v13_migration_step {
|
||||
let contract = <Contract<T>>::with_caller(
|
||||
whitelisted_caller(), WasmModule::dummy(), vec![],
|
||||
)?;
|
||||
|
||||
v13::store_old_contract_info::<T>(contract.account_id.clone(), contract.info()?);
|
||||
let mut m = v13::Migration::<T>::default();
|
||||
}: {
|
||||
m.step();
|
||||
}
|
||||
|
||||
// This benchmarks the weight of executing Migration::migrate to execute a noop migration.
|
||||
#[pov_mode = Measured]
|
||||
migration_noop {
|
||||
@@ -832,20 +845,48 @@ benchmarks! {
|
||||
let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
|
||||
let beneficiary_bytes = beneficiary.encode();
|
||||
let beneficiary_len = beneficiary_bytes.len();
|
||||
|
||||
// Maximize the delegate_dependencies to account for the worst-case scenario.
|
||||
let code_hashes = (0..T::MaxDelegateDependencies::get())
|
||||
.map(|i| {
|
||||
let new_code = WasmModule::<T>::dummy_with_bytes(65 + i);
|
||||
Contracts::<T>::store_code_raw(new_code.code, whitelisted_caller())?;
|
||||
Ok(new_code.hash)
|
||||
})
|
||||
.collect::<Result<Vec<_>, &'static str>>()?;
|
||||
let code_hash_len = code_hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
|
||||
let code_hashes_bytes = code_hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
|
||||
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_terminate",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
}],
|
||||
imported_functions: vec![
|
||||
ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_terminate",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
},
|
||||
ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "add_delegate_dependency",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: None,
|
||||
}
|
||||
],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: 0,
|
||||
value: beneficiary_bytes,
|
||||
},
|
||||
DataSegment {
|
||||
offset: beneficiary_len as u32,
|
||||
value: code_hashes_bytes,
|
||||
},
|
||||
],
|
||||
deploy_body: Some(body::repeated_dyn(r, vec![
|
||||
Counter(beneficiary_len as u32, code_hash_len as u32), // code_hash_ptr
|
||||
Regular(Instruction::Call(1)),
|
||||
])),
|
||||
call_body: Some(body::repeated(r, &[
|
||||
Instruction::I32Const(0), // beneficiary_ptr
|
||||
Instruction::I32Const(beneficiary_len as i32), // beneficiary_len
|
||||
@@ -2327,6 +2368,89 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
#[pov_mode = Measured]
|
||||
add_delegate_dependency {
|
||||
let r in 0 .. T::MaxDelegateDependencies::get();
|
||||
let code_hashes = (0..r)
|
||||
.map(|i| {
|
||||
let new_code = WasmModule::<T>::dummy_with_bytes(65 + i);
|
||||
Contracts::<T>::store_code_raw(new_code.code, whitelisted_caller())?;
|
||||
Ok(new_code.hash)
|
||||
})
|
||||
.collect::<Result<Vec<_>, &'static str>>()?;
|
||||
let code_hash_len = code_hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
|
||||
let code_hashes_bytes = code_hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
|
||||
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "add_delegate_dependency",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: None,
|
||||
}],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: 0,
|
||||
value: code_hashes_bytes,
|
||||
},
|
||||
],
|
||||
call_body: Some(body::repeated_dyn(r, vec![
|
||||
Counter(0, code_hash_len as u32), // code_hash_ptr
|
||||
Regular(Instruction::Call(0)),
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
remove_delegate_dependency {
|
||||
let r in 0 .. T::MaxDelegateDependencies::get();
|
||||
let code_hashes = (0..r)
|
||||
.map(|i| {
|
||||
let new_code = WasmModule::<T>::dummy_with_bytes(65 + i);
|
||||
Contracts::<T>::store_code_raw(new_code.code, whitelisted_caller())?;
|
||||
Ok(new_code.hash)
|
||||
})
|
||||
.collect::<Result<Vec<_>, &'static str>>()?;
|
||||
|
||||
let code_hash_len = code_hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
|
||||
let code_hashes_bytes = code_hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
|
||||
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "remove_delegate_dependency",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: None,
|
||||
}, ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "add_delegate_dependency",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: None
|
||||
}],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: 0,
|
||||
value: code_hashes_bytes,
|
||||
},
|
||||
],
|
||||
deploy_body: Some(body::repeated_dyn(r, vec![
|
||||
Counter(0, code_hash_len as u32), // code_hash_ptr
|
||||
Regular(Instruction::Call(1)),
|
||||
])),
|
||||
call_body: Some(body::repeated_dyn(r, vec![
|
||||
Counter(0, code_hash_len as u32), // code_hash_ptr
|
||||
Regular(Instruction::Call(0)),
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
#[pov_mode = Measured]
|
||||
seal_reentrance_count {
|
||||
let r in 0 .. API_BENCHMARK_RUNS;
|
||||
|
||||
@@ -18,14 +18,16 @@
|
||||
use crate::{
|
||||
gas::GasMeter,
|
||||
storage::{self, DepositAccount, WriteOutcome},
|
||||
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error,
|
||||
Event, Nonce, Origin, Pallet as Contracts, Schedule, System, LOG_TARGET,
|
||||
BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf,
|
||||
DebugBufferVec, Determinism, Error, Event, Nonce, Origin, Pallet as Contracts, Schedule,
|
||||
System, WasmBlob, LOG_TARGET,
|
||||
};
|
||||
use frame_support::{
|
||||
crypto::ecdsa::ECDSAExt,
|
||||
dispatch::{
|
||||
fmt::Debug, DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable,
|
||||
},
|
||||
ensure,
|
||||
storage::{with_transaction, TransactionOutcome},
|
||||
traits::{
|
||||
tokens::{Fortitude::Polite, Preservation::Expendable},
|
||||
@@ -35,11 +37,12 @@ use frame_support::{
|
||||
Blake2_128Concat, BoundedVec, StorageHasher,
|
||||
};
|
||||
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
|
||||
use pallet_contracts_primitives::ExecReturnValue;
|
||||
use pallet_contracts_primitives::{ExecReturnValue, StorageDeposit};
|
||||
use smallvec::{Array, SmallVec};
|
||||
use sp_core::{
|
||||
ecdsa::Public as ECDSAPublic,
|
||||
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
|
||||
Get,
|
||||
};
|
||||
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
|
||||
use sp_runtime::traits::{Convert, Hash, Zero};
|
||||
@@ -308,6 +311,35 @@ pub trait Ext: sealing::Sealed {
|
||||
|
||||
/// Returns a nonce that is incremented for every instantiated contract.
|
||||
fn nonce(&mut self) -> u64;
|
||||
|
||||
/// Adds a delegate dependency to [`ContractInfo`]'s `delegate_dependencies` field.
|
||||
///
|
||||
/// This ensures that the delegated contract is not removed while it is still in use. It
|
||||
/// increases the reference count of the code hash and charges a fraction (see
|
||||
/// [`Config::CodeHashLockupDepositPercent`]) of the code deposit.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - [`Error::<T>::MaxDelegateDependenciesReached`]
|
||||
/// - [`Error::<T>::CannotAddSelfAsDelegateDependency`]
|
||||
/// - [`Error::<T>::DelegateDependencyAlreadyExists`]
|
||||
fn add_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
) -> Result<(), DispatchError>;
|
||||
|
||||
/// Removes a delegate dependency from [`ContractInfo`]'s `delegate_dependencies` field.
|
||||
///
|
||||
/// This is the counterpart of [`Self::add_delegate_dependency`]. It decreases the reference
|
||||
/// count and refunds the deposit that was charged by [`Self::add_delegate_dependency`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - [`Error::<T>::DelegateDependencyNotFound`]
|
||||
fn remove_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: &CodeHash<Self::T>,
|
||||
) -> Result<(), DispatchError>;
|
||||
}
|
||||
|
||||
/// Describes the different functions that can be exported by an [`Executable`].
|
||||
@@ -365,6 +397,9 @@ pub trait Executable<T: Config>: Sized {
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult;
|
||||
|
||||
/// The code info of the executable.
|
||||
fn code_info(&self) -> &CodeInfo<T>;
|
||||
|
||||
/// The code hash of the executable.
|
||||
fn code_hash(&self) -> &CodeHash<T>;
|
||||
|
||||
@@ -861,6 +896,7 @@ where
|
||||
origin,
|
||||
&frame.account_id,
|
||||
frame.contract_info.get(&frame.account_id),
|
||||
executable.code_info(),
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -1259,6 +1295,14 @@ where
|
||||
info.queue_trie_for_deletion();
|
||||
ContractInfoOf::<T>::remove(&frame.account_id);
|
||||
E::decrement_refcount(info.code_hash);
|
||||
|
||||
for (code_hash, deposit) in info.delegate_dependencies() {
|
||||
E::decrement_refcount(*code_hash);
|
||||
frame
|
||||
.nested_storage
|
||||
.charge_deposit(info.deposit_account().clone(), StorageDeposit::Refund(*deposit));
|
||||
}
|
||||
|
||||
Contracts::<T>::deposit_event(
|
||||
vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)],
|
||||
Event::Terminated {
|
||||
@@ -1433,10 +1477,24 @@ where
|
||||
if !E::from_storage(hash, &mut frame.nested_gas)?.is_deterministic() {
|
||||
return Err(<Error<T>>::Indeterministic.into())
|
||||
}
|
||||
|
||||
let info = frame.contract_info();
|
||||
|
||||
let prev_hash = info.code_hash;
|
||||
info.code_hash = hash;
|
||||
|
||||
let code_info = CodeInfoOf::<T>::get(hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
|
||||
let old_base_deposit = info.storage_base_deposit();
|
||||
let new_base_deposit = info.update_base_deposit(&code_info);
|
||||
let deposit = StorageDeposit::Charge(new_base_deposit)
|
||||
.saturating_sub(&StorageDeposit::Charge(old_base_deposit));
|
||||
|
||||
let deposit_account = info.deposit_account().clone();
|
||||
frame.nested_storage.charge_deposit(deposit_account, deposit);
|
||||
|
||||
E::increment_refcount(hash)?;
|
||||
let prev_hash = frame.contract_info().code_hash;
|
||||
E::decrement_refcount(prev_hash);
|
||||
frame.contract_info().code_hash = hash;
|
||||
Contracts::<Self::T>::deposit_event(
|
||||
vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash],
|
||||
Event::ContractCodeUpdated {
|
||||
@@ -1468,6 +1526,41 @@ where
|
||||
current
|
||||
}
|
||||
}
|
||||
|
||||
fn add_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: CodeHash<Self::T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let frame = self.top_frame_mut();
|
||||
let info = frame.contract_info.get(&frame.account_id);
|
||||
ensure!(code_hash != info.code_hash, Error::<T>::CannotAddSelfAsDelegateDependency);
|
||||
|
||||
let code_info = CodeInfoOf::<T>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit());
|
||||
|
||||
info.add_delegate_dependency(code_hash, deposit)?;
|
||||
<WasmBlob<T>>::increment_refcount(code_hash)?;
|
||||
frame
|
||||
.nested_storage
|
||||
.charge_deposit(info.deposit_account().clone(), StorageDeposit::Charge(deposit));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: &CodeHash<Self::T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let frame = self.top_frame_mut();
|
||||
let info = frame.contract_info.get(&frame.account_id);
|
||||
|
||||
let deposit = info.remove_delegate_dependency(code_hash)?;
|
||||
<WasmBlob<T>>::decrement_refcount(*code_hash);
|
||||
|
||||
frame
|
||||
.nested_storage
|
||||
.charge_deposit(info.deposit_account().clone(), StorageDeposit::Refund(deposit));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod sealing {
|
||||
@@ -1543,6 +1636,7 @@ mod tests {
|
||||
func: Rc<dyn Fn(MockCtx, &Self) -> ExecResult + 'static>,
|
||||
func_type: ExportedFunction,
|
||||
code_hash: CodeHash<Test>,
|
||||
code_info: CodeInfo<Test>,
|
||||
refcount: u64,
|
||||
}
|
||||
|
||||
@@ -1553,6 +1647,10 @@ mod tests {
|
||||
}
|
||||
|
||||
impl MockLoader {
|
||||
fn code_hashes() -> Vec<CodeHash<Test>> {
|
||||
Loader::get().map.keys().copied().collect()
|
||||
}
|
||||
|
||||
fn insert(
|
||||
func_type: ExportedFunction,
|
||||
f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static,
|
||||
@@ -1563,7 +1661,13 @@ mod tests {
|
||||
loader.counter += 1;
|
||||
loader.map.insert(
|
||||
hash,
|
||||
MockExecutable { func: Rc::new(f), func_type, code_hash: hash, refcount: 1 },
|
||||
MockExecutable {
|
||||
func: Rc::new(f),
|
||||
func_type,
|
||||
code_hash: hash,
|
||||
code_info: CodeInfo::<Test>::new(ALICE),
|
||||
refcount: 1,
|
||||
},
|
||||
);
|
||||
hash
|
||||
})
|
||||
@@ -1633,6 +1737,10 @@ mod tests {
|
||||
&self.code_hash
|
||||
}
|
||||
|
||||
fn code_info(&self) -> &CodeInfo<Test> {
|
||||
&self.code_info
|
||||
}
|
||||
|
||||
fn code_len(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
@@ -1954,29 +2062,33 @@ mod tests {
|
||||
});
|
||||
|
||||
// This one tests passing the input data into a contract via instantiate.
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable =
|
||||
MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
|
||||
|
||||
let result = MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance,
|
||||
vec![1, 2, 3, 4],
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
let result = MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance,
|
||||
vec![1, 2, 3, 4],
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2399,43 +2511,53 @@ mod tests {
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
|
||||
});
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, Some(min_balance * 100), min_balance)
|
||||
.unwrap();
|
||||
|
||||
let instantiated_contract_address = assert_matches!(
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.existential_deposit(15)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter = storage::meter::Meter::new(
|
||||
&contract_origin,
|
||||
Some(min_balance * 100),
|
||||
min_balance,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
),
|
||||
Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Check that the newly created account has the expected code hash and
|
||||
// there are instantiation event.
|
||||
assert_eq!(
|
||||
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
|
||||
dummy_ch
|
||||
);
|
||||
assert_eq!(
|
||||
&events(),
|
||||
&[Event::Instantiated { deployer: ALICE, contract: instantiated_contract_address }]
|
||||
);
|
||||
});
|
||||
let instantiated_contract_address = assert_matches!(
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
),
|
||||
Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
|
||||
);
|
||||
|
||||
// Check that the newly created account has the expected code hash and
|
||||
// there are instantiation event.
|
||||
assert_eq!(
|
||||
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
|
||||
dummy_ch
|
||||
);
|
||||
assert_eq!(
|
||||
&events(),
|
||||
&[Event::Instantiated {
|
||||
deployer: ALICE,
|
||||
contract: instantiated_contract_address
|
||||
}]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2444,36 +2566,45 @@ mod tests {
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
|
||||
});
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, Some(min_balance * 100), min_balance)
|
||||
.unwrap();
|
||||
|
||||
let instantiated_contract_address = assert_matches!(
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.existential_deposit(15)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter = storage::meter::Meter::new(
|
||||
&contract_origin,
|
||||
Some(min_balance * 100),
|
||||
min_balance,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
),
|
||||
Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Check that the account has not been created.
|
||||
assert!(ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).is_none());
|
||||
assert!(events().is_empty());
|
||||
});
|
||||
let instantiated_contract_address = assert_matches!(
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
),
|
||||
Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
|
||||
);
|
||||
|
||||
// Check that the account has not been created.
|
||||
assert!(
|
||||
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).is_none()
|
||||
);
|
||||
assert!(events().is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2501,51 +2632,58 @@ mod tests {
|
||||
}
|
||||
});
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
set_balance(&ALICE, min_balance * 100);
|
||||
place_contract(&BOB, instantiator_ch);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter = storage::meter::Meter::new(
|
||||
&contract_origin,
|
||||
Some(min_balance * 10),
|
||||
min_balance * 10,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
MockStack::run_call(
|
||||
contract_origin,
|
||||
BOB,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.existential_deposit(15)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
set_balance(&ALICE, min_balance * 100);
|
||||
place_contract(&BOB, instantiator_ch);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter = storage::meter::Meter::new(
|
||||
&contract_origin,
|
||||
Some(min_balance * 10),
|
||||
min_balance * 10,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Enforced,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let instantiated_contract_address =
|
||||
instantiated_contract_address.borrow().as_ref().unwrap().clone();
|
||||
assert_matches!(
|
||||
MockStack::run_call(
|
||||
contract_origin,
|
||||
BOB,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 10,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Enforced,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
// Check that the newly created account has the expected code hash and
|
||||
// there are instantiation event.
|
||||
assert_eq!(
|
||||
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
|
||||
dummy_ch
|
||||
);
|
||||
assert_eq!(
|
||||
&events(),
|
||||
&[
|
||||
Event::Instantiated { deployer: BOB, contract: instantiated_contract_address },
|
||||
Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },
|
||||
]
|
||||
);
|
||||
});
|
||||
let instantiated_contract_address =
|
||||
instantiated_contract_address.borrow().as_ref().unwrap().clone();
|
||||
|
||||
// Check that the newly created account has the expected code hash and
|
||||
// there are instantiation event.
|
||||
assert_eq!(
|
||||
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
|
||||
dummy_ch
|
||||
);
|
||||
assert_eq!(
|
||||
&events(),
|
||||
&[
|
||||
Event::Instantiated {
|
||||
deployer: BOB,
|
||||
contract: instantiated_contract_address
|
||||
},
|
||||
Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2573,37 +2711,41 @@ mod tests {
|
||||
}
|
||||
});
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
set_balance(&ALICE, 1000);
|
||||
set_balance(&BOB, 100);
|
||||
place_contract(&BOB, instantiator_ch);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, Some(200), 0).unwrap();
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.existential_deposit(15)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
set_balance(&ALICE, 1000);
|
||||
set_balance(&BOB, 100);
|
||||
place_contract(&BOB, instantiator_ch);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, Some(200), 0).unwrap();
|
||||
|
||||
assert_matches!(
|
||||
MockStack::run_call(
|
||||
contract_origin,
|
||||
BOB,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Enforced,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
assert_matches!(
|
||||
MockStack::run_call(
|
||||
contract_origin,
|
||||
BOB,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Enforced,
|
||||
),
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
// The contract wasn't instantiated so we don't expect to see an instantiation
|
||||
// event here.
|
||||
assert_eq!(
|
||||
&events(),
|
||||
&[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },]
|
||||
);
|
||||
});
|
||||
// The contract wasn't instantiated so we don't expect to see an instantiation
|
||||
// event here.
|
||||
assert_eq!(
|
||||
&events(),
|
||||
&[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2613,32 +2755,37 @@ mod tests {
|
||||
exec_success()
|
||||
});
|
||||
|
||||
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, 100).unwrap();
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.existential_deposit(15)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable =
|
||||
MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, 100).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
100,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
),
|
||||
Err(Error::<Test>::TerminatedInConstructor.into())
|
||||
);
|
||||
assert_eq!(
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
100,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
),
|
||||
Err(Error::<Test>::TerminatedInConstructor.into())
|
||||
);
|
||||
|
||||
assert_eq!(&events(), &[]);
|
||||
});
|
||||
assert_eq!(&events(), &[]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2714,29 +2861,32 @@ mod tests {
|
||||
});
|
||||
|
||||
// This one tests passing the input data into a contract via instantiate.
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
|
||||
|
||||
let result = MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
let result = MockStack::run_instantiate(
|
||||
ALICE,
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3138,75 +3288,79 @@ mod tests {
|
||||
exec_success()
|
||||
});
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let fail_executable = MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap();
|
||||
let success_executable =
|
||||
MockExecutable::from_storage(success_code, &mut gas_meter).unwrap();
|
||||
let succ_fail_executable =
|
||||
MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap();
|
||||
let succ_succ_executable =
|
||||
MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, min_balance * 100).unwrap();
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
let fail_executable =
|
||||
MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap();
|
||||
let success_executable =
|
||||
MockExecutable::from_storage(success_code, &mut gas_meter).unwrap();
|
||||
let succ_fail_executable =
|
||||
MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap();
|
||||
let succ_succ_executable =
|
||||
MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap();
|
||||
set_balance(&ALICE, min_balance * 10_000);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, min_balance * 100).unwrap();
|
||||
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
fail_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 100,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
)
|
||||
.ok();
|
||||
assert_eq!(<Nonce<Test>>::get(), 0);
|
||||
MockStack::run_instantiate(
|
||||
ALICE,
|
||||
fail_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 100,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
)
|
||||
.ok();
|
||||
assert_eq!(<Nonce<Test>>::get(), 0);
|
||||
|
||||
assert_ok!(MockStack::run_instantiate(
|
||||
ALICE,
|
||||
success_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 100,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
));
|
||||
assert_eq!(<Nonce<Test>>::get(), 1);
|
||||
assert_ok!(MockStack::run_instantiate(
|
||||
ALICE,
|
||||
success_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 100,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
));
|
||||
assert_eq!(<Nonce<Test>>::get(), 1);
|
||||
|
||||
assert_ok!(MockStack::run_instantiate(
|
||||
ALICE,
|
||||
succ_fail_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 200,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
));
|
||||
assert_eq!(<Nonce<Test>>::get(), 2);
|
||||
assert_ok!(MockStack::run_instantiate(
|
||||
ALICE,
|
||||
succ_fail_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 200,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
));
|
||||
assert_eq!(<Nonce<Test>>::get(), 2);
|
||||
|
||||
assert_ok!(MockStack::run_instantiate(
|
||||
ALICE,
|
||||
succ_succ_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 200,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
));
|
||||
assert_eq!(<Nonce<Test>>::get(), 4);
|
||||
});
|
||||
assert_ok!(MockStack::run_instantiate(
|
||||
ALICE,
|
||||
succ_succ_executable,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
min_balance * 200,
|
||||
vec![],
|
||||
&[],
|
||||
None,
|
||||
));
|
||||
assert_eq!(<Nonce<Test>>::get(), 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3676,26 +3830,30 @@ mod tests {
|
||||
exec_success()
|
||||
});
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
place_contract(&BOB, code_hash);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
|
||||
assert_ok!(MockStack::run_call(
|
||||
contract_origin,
|
||||
BOB,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Enforced
|
||||
));
|
||||
});
|
||||
ExtBuilder::default()
|
||||
.with_code_hashes(MockLoader::code_hashes())
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
place_contract(&BOB, code_hash);
|
||||
let contract_origin = Origin::from_account_id(ALICE);
|
||||
let mut storage_meter =
|
||||
storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
|
||||
assert_ok!(MockStack::run_call(
|
||||
contract_origin,
|
||||
BOB,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Enforced
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
/// This works even though random interface is deprecated, as the check to ban deprecated
|
||||
|
||||
@@ -183,6 +183,7 @@ pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
/// The current storage version.
|
||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
||||
@@ -290,6 +291,13 @@ pub mod pallet {
|
||||
#[pallet::constant]
|
||||
type DepositPerItem: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The percentage of the storage deposit that should be held for using a code hash.
|
||||
/// Instantiating a contract, or calling [`chain_extension::Ext::add_delegate_dependency`]
|
||||
/// protects the code from being removed. In order to prevent abuse these actions are
|
||||
/// protected with a percentage of the code deposit.
|
||||
#[pallet::constant]
|
||||
type CodeHashLockupDepositPercent: Get<Perbill>;
|
||||
|
||||
/// The address generator used to generate the addresses of contracts.
|
||||
type AddressGenerator: AddressGenerator<Self>;
|
||||
|
||||
@@ -305,6 +313,11 @@ pub mod pallet {
|
||||
#[pallet::constant]
|
||||
type MaxStorageKeyLen: Get<u32>;
|
||||
|
||||
/// The maximum number of delegate_dependencies that a contract can lock with
|
||||
/// [`chain_extension::Ext::add_delegate_dependency`].
|
||||
#[pallet::constant]
|
||||
type MaxDelegateDependencies: Get<u32>;
|
||||
|
||||
/// Make contract callable functions marked as `#[unstable]` available.
|
||||
///
|
||||
/// Contracts that use `#[unstable]` functions won't be able to be uploaded unless
|
||||
@@ -730,7 +743,7 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
output.gas_meter.into_dispatch_result(
|
||||
output.result.map(|(_address, result)| result),
|
||||
output.result.map(|(_address, output)| output),
|
||||
T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len),
|
||||
)
|
||||
}
|
||||
@@ -961,6 +974,14 @@ pub mod pallet {
|
||||
MigrationInProgress,
|
||||
/// Migrate dispatch call was attempted but no migration was performed.
|
||||
NoMigrationPerformed,
|
||||
/// The contract has reached its maximum number of delegate dependencies.
|
||||
MaxDelegateDependenciesReached,
|
||||
/// The dependency was not found in the contract's delegate dependencies.
|
||||
DelegateDependencyNotFound,
|
||||
/// The contract already depends on the given delegate dependency.
|
||||
DelegateDependencyAlreadyExists,
|
||||
/// Can not add a delegate dependency to the code hash of the contract itself.
|
||||
CannotAddSelfAsDelegateDependency,
|
||||
}
|
||||
|
||||
/// A mapping from a contract's code hash to its code.
|
||||
@@ -1001,7 +1022,7 @@ pub mod pallet {
|
||||
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
|
||||
#[pallet::storage]
|
||||
pub(crate) type ContractInfoOf<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, ContractInfo<T>>;
|
||||
StorageMap<_, Identity, T::AccountId, ContractInfo<T>>;
|
||||
|
||||
/// Evicted contracts that await child trie deletion.
|
||||
///
|
||||
|
||||
@@ -60,6 +60,7 @@ pub mod v09;
|
||||
pub mod v10;
|
||||
pub mod v11;
|
||||
pub mod v12;
|
||||
pub mod v13;
|
||||
|
||||
use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET};
|
||||
use codec::{Codec, Decode};
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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.
|
||||
|
||||
//! Add `delegate_dependencies` to `ContractInfo`.
|
||||
//! Use `Identity` instead of `Twox64Concat` for hashing keys of the `ContractInfoOf` storage map
|
||||
//! See <https://github.com/paritytech/substrate/pull/14079>.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
storage::DepositAccount,
|
||||
weights::WeightInfo,
|
||||
BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{codec, pallet_prelude::*, storage_alias, DefaultNoBound};
|
||||
use sp_runtime::BoundedBTreeMap;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
mod old {
|
||||
use crate::storage::DepositAccount;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
pub trie_id: TrieId,
|
||||
pub deposit_account: DepositAccount<T>,
|
||||
pub code_hash: CodeHash<T>,
|
||||
pub storage_bytes: u32,
|
||||
pub storage_items: u32,
|
||||
pub storage_byte_deposit: BalanceOf<T>,
|
||||
pub storage_item_deposit: BalanceOf<T>,
|
||||
pub storage_base_deposit: BalanceOf<T>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config> = StorageMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
ContractInfo<T>,
|
||||
>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) {
|
||||
let info = old::ContractInfo {
|
||||
trie_id: info.trie_id.clone(),
|
||||
deposit_account: info.deposit_account().clone(),
|
||||
code_hash: info.code_hash,
|
||||
storage_bytes: Default::default(),
|
||||
storage_items: Default::default(),
|
||||
storage_byte_deposit: Default::default(),
|
||||
storage_item_deposit: Default::default(),
|
||||
storage_base_deposit: Default::default(),
|
||||
};
|
||||
old::ContractInfoOf::<T>::insert(account, info);
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type ContractInfoOf<T: Config> =
|
||||
StorageMap<Pallet<T>, Identity, <T as frame_system::Config>::AccountId, ContractInfo<T>>;
|
||||
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
trie_id: TrieId,
|
||||
deposit_account: DepositAccount<T>,
|
||||
code_hash: CodeHash<T>,
|
||||
storage_bytes: u32,
|
||||
storage_items: u32,
|
||||
storage_byte_deposit: BalanceOf<T>,
|
||||
storage_item_deposit: BalanceOf<T>,
|
||||
storage_base_deposit: BalanceOf<T>,
|
||||
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
last_account: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 13;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v13_migration_step()
|
||||
}
|
||||
|
||||
fn step(&mut self) -> (IsFinished, Weight) {
|
||||
let mut iter = if let Some(last_account) = self.last_account.take() {
|
||||
old::ContractInfoOf::<T>::iter_from(old::ContractInfoOf::<T>::hashed_key_for(
|
||||
last_account,
|
||||
))
|
||||
} else {
|
||||
old::ContractInfoOf::<T>::iter()
|
||||
};
|
||||
|
||||
if let Some((key, old)) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating contract {:?}", key);
|
||||
let info = ContractInfo {
|
||||
trie_id: old.trie_id,
|
||||
deposit_account: old.deposit_account,
|
||||
code_hash: old.code_hash,
|
||||
storage_bytes: old.storage_bytes,
|
||||
storage_items: old.storage_items,
|
||||
storage_byte_deposit: old.storage_byte_deposit,
|
||||
storage_item_deposit: old.storage_item_deposit,
|
||||
storage_base_deposit: old.storage_base_deposit,
|
||||
delegate_dependencies: Default::default(),
|
||||
};
|
||||
ContractInfoOf::<T>::insert(key.clone(), info);
|
||||
self.last_account = Some(key);
|
||||
(IsFinished::No, T::WeightInfo::v13_migration_step())
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more contracts to migrate");
|
||||
(IsFinished::Yes, T::WeightInfo::v13_migration_step())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,6 +329,12 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `instantiation_nonce`.
|
||||
pub instantiation_nonce: Weight,
|
||||
|
||||
/// Weight of calling `add_delegate_dependency`.
|
||||
pub add_delegate_dependency: Weight,
|
||||
|
||||
/// Weight of calling `remove_delegate_dependency`.
|
||||
pub remove_delegate_dependency: Weight,
|
||||
|
||||
/// The type parameter is used in the default implementation.
|
||||
#[codec(skip)]
|
||||
pub _phantom: PhantomData<T>,
|
||||
@@ -476,6 +482,8 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
reentrance_count: cost!(seal_reentrance_count),
|
||||
account_reentrance_count: cost!(seal_account_reentrance_count),
|
||||
instantiation_nonce: cost!(seal_instantiation_nonce),
|
||||
add_delegate_dependency: cost!(add_delegate_dependency),
|
||||
remove_delegate_dependency: cost!(remove_delegate_dependency),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub mod meter;
|
||||
use crate::{
|
||||
exec::{AccountIdOf, Key},
|
||||
weights::WeightInfo,
|
||||
AddressGenerator, BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue,
|
||||
AddressGenerator, BalanceOf, CodeHash, CodeInfo, Config, ContractInfoOf, DeletionQueue,
|
||||
DeletionQueueCounter, Error, Pallet, TrieId, SENTINEL,
|
||||
};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
@@ -30,19 +30,22 @@ use frame_support::{
|
||||
dispatch::DispatchError,
|
||||
storage::child::{self, ChildInfo},
|
||||
weights::Weight,
|
||||
DefaultNoBound, RuntimeDebugNoBound,
|
||||
CloneNoBound, DefaultNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::Get;
|
||||
use sp_io::KillStorageResult;
|
||||
use sp_runtime::{
|
||||
traits::{Hash, Saturating, Zero},
|
||||
RuntimeDebug,
|
||||
BoundedBTreeMap, DispatchResult, RuntimeDebug,
|
||||
};
|
||||
use sp_std::{marker::PhantomData, ops::Deref, prelude::*};
|
||||
|
||||
use self::meter::Diff;
|
||||
|
||||
/// Information for managing an account and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ContractInfo<T: Config> {
|
||||
/// Unique ID for the subtree encoded as a bytes vector.
|
||||
@@ -66,6 +69,12 @@ pub struct ContractInfo<T: Config> {
|
||||
/// We need to store this information separately so it is not used when calculating any refunds
|
||||
/// since the base deposit can only ever be refunded on contract termination.
|
||||
storage_base_deposit: BalanceOf<T>,
|
||||
/// Map of code hashes and deposit balances.
|
||||
///
|
||||
/// Tracks the code hash and deposit held for adding delegate dependencies. Dependencies added
|
||||
/// to the map can not be removed from the chain state and can be safely used for delegate
|
||||
/// calls.
|
||||
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
|
||||
}
|
||||
|
||||
impl<T: Config> ContractInfo<T> {
|
||||
@@ -101,6 +110,7 @@ impl<T: Config> ContractInfo<T> {
|
||||
storage_byte_deposit: Zero::zero(),
|
||||
storage_item_deposit: Zero::zero(),
|
||||
storage_base_deposit: Zero::zero(),
|
||||
delegate_dependencies: Default::default(),
|
||||
};
|
||||
|
||||
Ok(contract)
|
||||
@@ -123,11 +133,16 @@ impl<T: Config> ContractInfo<T> {
|
||||
.saturating_sub(Pallet::<T>::min_balance())
|
||||
}
|
||||
|
||||
/// Return the account that storage deposits should be deposited into.
|
||||
/// Returns the account that storage deposits should be deposited into.
|
||||
pub fn deposit_account(&self) -> &DepositAccount<T> {
|
||||
&self.deposit_account
|
||||
}
|
||||
|
||||
/// Returns the storage base deposit of the contract.
|
||||
pub fn storage_base_deposit(&self) -> BalanceOf<T> {
|
||||
self.storage_base_deposit
|
||||
}
|
||||
|
||||
/// Reads a storage kv pair of a contract.
|
||||
///
|
||||
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
|
||||
@@ -201,6 +216,68 @@ impl<T: Config> ContractInfo<T> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets and returns the contract base deposit.
|
||||
///
|
||||
/// The base deposit is updated when the `code_hash` of the contract changes, as it depends on
|
||||
/// the deposit paid to upload the contract's code.
|
||||
pub fn update_base_deposit(&mut self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
|
||||
let ed = Pallet::<T>::min_balance();
|
||||
let info_deposit =
|
||||
Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() }
|
||||
.update_contract::<T>(None)
|
||||
.charge_or_zero();
|
||||
|
||||
// Instantiating the contract prevents its code to be deleted, therefore the base deposit
|
||||
// includes a fraction (`T::CodeHashLockupDepositPercent`) of the original storage deposit
|
||||
// to prevent abuse.
|
||||
let upload_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit());
|
||||
|
||||
// Instantiate needs to transfer at least the minimum balance in order to pull the
|
||||
// deposit account into existence.
|
||||
// We also add another `ed` here which goes to the contract's own account into existence.
|
||||
let deposit = info_deposit.saturating_add(upload_deposit).max(ed).saturating_add(ed);
|
||||
|
||||
self.storage_base_deposit = deposit;
|
||||
deposit
|
||||
}
|
||||
|
||||
/// Adds a new delegate dependency to the contract.
|
||||
/// The `amount` is the amount of funds that will be reserved for the dependency.
|
||||
///
|
||||
/// Returns an error if the maximum number of delegate_dependencies is reached or if
|
||||
/// the delegate dependency already exists.
|
||||
pub fn add_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: CodeHash<T>,
|
||||
amount: BalanceOf<T>,
|
||||
) -> DispatchResult {
|
||||
self.delegate_dependencies
|
||||
.try_insert(code_hash, amount)
|
||||
.map_err(|_| Error::<T>::MaxDelegateDependenciesReached)?
|
||||
.map_or(Ok(()), |_| Err(Error::<T>::DelegateDependencyAlreadyExists))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Removes the delegate dependency from the contract and returns the deposit held for this
|
||||
/// dependency.
|
||||
///
|
||||
/// Returns an error if the entry doesn't exist.
|
||||
pub fn remove_delegate_dependency(
|
||||
&mut self,
|
||||
code_hash: &CodeHash<T>,
|
||||
) -> Result<BalanceOf<T>, DispatchError> {
|
||||
self.delegate_dependencies
|
||||
.remove(code_hash)
|
||||
.ok_or(Error::<T>::DelegateDependencyNotFound.into())
|
||||
}
|
||||
|
||||
/// Returns the delegate_dependencies of the contract.
|
||||
pub fn delegate_dependencies(
|
||||
&self,
|
||||
) -> &BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies> {
|
||||
&self.delegate_dependencies
|
||||
}
|
||||
|
||||
/// Push a contract's trie to the deletion queue for lazy removal.
|
||||
///
|
||||
/// You must make sure that the contract is also removed when queuing the trie for deletion.
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
use crate::{
|
||||
storage::{ContractInfo, DepositAccount},
|
||||
BalanceOf, Config, Error, Inspect, Origin, Pallet, System,
|
||||
BalanceOf, CodeInfo, Config, Error, Inspect, Origin, Pallet, StorageDeposit as Deposit, System,
|
||||
};
|
||||
use codec::Encode;
|
||||
|
||||
use frame_support::{
|
||||
dispatch::{fmt::Debug, DispatchError},
|
||||
ensure,
|
||||
@@ -31,7 +31,6 @@ use frame_support::{
|
||||
},
|
||||
DefaultNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use pallet_contracts_primitives::StorageDeposit as Deposit;
|
||||
use sp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
FixedPointNumber, FixedU128,
|
||||
@@ -398,7 +397,7 @@ where
|
||||
T: Config,
|
||||
E: Ext<T>,
|
||||
{
|
||||
/// Charge `diff` from the meter.
|
||||
/// Charges `diff` from the meter.
|
||||
pub fn charge(&mut self, diff: &Diff) {
|
||||
match &mut self.own_contribution {
|
||||
Contribution::Alive(own) => *own = own.saturating_add(diff),
|
||||
@@ -406,46 +405,52 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
/// Charge from `origin` a storage deposit for contract instantiation.
|
||||
/// Adds a deposit charge.
|
||||
///
|
||||
/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
|
||||
/// change. This is the case when a `delegate_dependency` is added or removed, or when the
|
||||
/// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the
|
||||
/// deposit charge separately from the storage charge.
|
||||
pub fn charge_deposit(&mut self, deposit_account: DepositAccount<T>, amount: DepositOf<T>) {
|
||||
self.total_deposit = self.total_deposit.saturating_add(&amount);
|
||||
self.charges.push(Charge { deposit_account, amount, terminated: false });
|
||||
}
|
||||
|
||||
/// Charges from `origin` a storage deposit for contract instantiation.
|
||||
///
|
||||
/// This immediately transfers the balance in order to create the account.
|
||||
pub fn charge_instantiate(
|
||||
&mut self,
|
||||
origin: &T::AccountId,
|
||||
contract: &T::AccountId,
|
||||
info: &mut ContractInfo<T>,
|
||||
contract_info: &mut ContractInfo<T>,
|
||||
code_info: &CodeInfo<T>,
|
||||
) -> Result<DepositOf<T>, DispatchError> {
|
||||
debug_assert!(self.is_alive());
|
||||
|
||||
let ed = Pallet::<T>::min_balance();
|
||||
let mut deposit =
|
||||
Diff { bytes_added: info.encoded_size() as u32, items_added: 1, ..Default::default() }
|
||||
.update_contract::<T>(None);
|
||||
|
||||
// Instantiate needs to transfer at least the minimum balance in order to pull the
|
||||
// deposit account into existence.
|
||||
// We also add another `ed` here which goes to the contract's own account into existence.
|
||||
deposit = deposit.max(Deposit::Charge(ed)).saturating_add(&Deposit::Charge(ed));
|
||||
if deposit.charge_or_zero() > self.limit {
|
||||
let deposit = contract_info.update_base_deposit(&code_info);
|
||||
if deposit > self.limit {
|
||||
return Err(<Error<T>>::StorageDepositLimitExhausted.into())
|
||||
}
|
||||
|
||||
let deposit = Deposit::Charge(deposit);
|
||||
|
||||
// We do not increase `own_contribution` because this will be charged later when the
|
||||
// contract execution does conclude and hence would lead to a double charge.
|
||||
self.total_deposit = deposit.clone();
|
||||
info.storage_base_deposit = deposit.charge_or_zero();
|
||||
|
||||
// Normally, deposit charges are deferred to be able to coalesce them with refunds.
|
||||
// However, we need to charge immediately so that the account is created before
|
||||
// charges possibly below the ed are collected and fail.
|
||||
E::charge(
|
||||
origin,
|
||||
info.deposit_account(),
|
||||
contract_info.deposit_account(),
|
||||
&deposit.saturating_sub(&Deposit::Charge(ed)),
|
||||
false,
|
||||
)?;
|
||||
|
||||
System::<T>::inc_consumers(info.deposit_account())?;
|
||||
System::<T>::inc_consumers(contract_info.deposit_account())?;
|
||||
|
||||
// We also need to make sure that the contract's account itself exists.
|
||||
T::Currency::transfer(origin, contract, ed, ExistenceRequirement::KeepAlive)?;
|
||||
@@ -664,6 +669,7 @@ mod tests {
|
||||
storage_byte_deposit: info.bytes_deposit,
|
||||
storage_item_deposit: info.items_deposit,
|
||||
storage_base_deposit: Default::default(),
|
||||
delegate_dependencies: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ mod pallet_dummy;
|
||||
// limitations under the License.
|
||||
|
||||
use self::test_utils::{ensure_stored, expected_deposit, hash};
|
||||
use crate as pallet_contracts;
|
||||
use crate::{
|
||||
self as pallet_contracts,
|
||||
chain_extension::{
|
||||
ChainExtension, Environment, Ext, InitState, RegisteredChainExtension,
|
||||
Result as ExtensionResult, RetVal, ReturnFlags, SysConfig,
|
||||
@@ -28,9 +28,9 @@ use crate::{
|
||||
tests::test_utils::{get_contract, get_contract_checked},
|
||||
wasm::{Determinism, ReturnCode as RuntimeReturnCode},
|
||||
weights::WeightInfo,
|
||||
BalanceOf, Code, CollectEvents, Config, ContractInfo, ContractInfoOf, DebugInfo,
|
||||
DefaultAddressGenerator, DeletionQueueCounter, Error, MigrationInProgress, NoopMigration,
|
||||
Origin, Pallet, PristineCode, Schedule,
|
||||
BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf,
|
||||
DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, MigrationInProgress,
|
||||
NoopMigration, Origin, Pallet, PristineCode, Schedule,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::Encode;
|
||||
@@ -46,6 +46,7 @@ use frame_support::{
|
||||
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
|
||||
};
|
||||
use frame_system::{EventRecord, Phase};
|
||||
use pallet_contracts_primitives::CodeUploadReturnValue;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use sp_core::ByteArray;
|
||||
use sp_io::hashing::blake2_256;
|
||||
@@ -53,7 +54,7 @@ use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
|
||||
use sp_runtime::{
|
||||
testing::H256,
|
||||
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
|
||||
AccountId32, BuildStorage, TokenError,
|
||||
AccountId32, BuildStorage, Perbill, TokenError,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
|
||||
@@ -89,8 +90,8 @@ macro_rules! assert_refcount {
|
||||
pub mod test_utils {
|
||||
use super::{Balances, DepositPerByte, DepositPerItem, Hash, SysConfig, Test};
|
||||
use crate::{
|
||||
exec::AccountIdOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf,
|
||||
Nonce, PristineCode,
|
||||
exec::AccountIdOf, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo,
|
||||
ContractInfoOf, Nonce, PristineCode,
|
||||
};
|
||||
use codec::{Encode, MaxEncodedLen};
|
||||
use frame_support::traits::Currency;
|
||||
@@ -101,6 +102,7 @@ pub mod test_utils {
|
||||
*counter
|
||||
});
|
||||
set_balance(address, <Test as Config>::Currency::minimum_balance() * 10);
|
||||
<CodeInfoOf<Test>>::insert(code_hash, CodeInfo::new(address.clone()));
|
||||
let contract = <ContractInfo<Test>>::new(&address, nonce, code_hash).unwrap();
|
||||
<ContractInfoOf<Test>>::insert(address, contract);
|
||||
}
|
||||
@@ -117,6 +119,9 @@ pub mod test_utils {
|
||||
pub fn get_contract_checked(addr: &AccountIdOf<Test>) -> Option<ContractInfo<Test>> {
|
||||
ContractInfoOf::<Test>::get(addr)
|
||||
}
|
||||
pub fn get_code_deposit(code_hash: &CodeHash<Test>) -> BalanceOf<Test> {
|
||||
crate::CodeInfoOf::<Test>::get(code_hash).unwrap().deposit()
|
||||
}
|
||||
pub fn hash<S: Encode>(s: &S) -> <<Test as SysConfig>::Hashing as Hash>::Output {
|
||||
<<Test as SysConfig>::Hashing as Hash>::hash_of(s)
|
||||
}
|
||||
@@ -384,6 +389,9 @@ parameter_types! {
|
||||
};
|
||||
pub static DepositPerByte: BalanceOf<Test> = 1;
|
||||
pub const DepositPerItem: BalanceOf<Test> = 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<Test> = 10_000_000;
|
||||
}
|
||||
@@ -450,6 +458,8 @@ impl Config for Test {
|
||||
type UnsafeUnstableInterface = UnstableInterface;
|
||||
type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
|
||||
type Migrations = (NoopMigration<1>, NoopMigration<2>);
|
||||
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
|
||||
type MaxDelegateDependencies = MaxDelegateDependencies;
|
||||
}
|
||||
|
||||
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
|
||||
@@ -462,17 +472,28 @@ pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 102
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
storage_version: Option<StorageVersion>,
|
||||
code_hashes: Vec<CodeHash<Test>>,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self { existential_deposit: ExistentialDeposit::get(), storage_version: None }
|
||||
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<CodeHash<Test>>) -> Self {
|
||||
self.code_hashes = code_hashes;
|
||||
self
|
||||
}
|
||||
pub fn set_associated_consts(&self) {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
|
||||
}
|
||||
@@ -500,6 +521,11 @@ impl ExtBuilder {
|
||||
}
|
||||
System::set_block_number(1)
|
||||
});
|
||||
ext.execute_with(|| {
|
||||
for code_hash in self.code_hashes {
|
||||
CodeInfoOf::<Test>::insert(code_hash, crate::CodeInfo::new(ALICE));
|
||||
}
|
||||
});
|
||||
ext
|
||||
}
|
||||
}
|
||||
@@ -709,7 +735,7 @@ fn instantiate_and_call_and_deposit_event() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
||||
account: deposit_account.clone(),
|
||||
free_balance: 131,
|
||||
free_balance: 132,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -718,7 +744,7 @@ fn instantiate_and_call_and_deposit_event() {
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||
from: ALICE,
|
||||
to: deposit_account.clone(),
|
||||
amount: 131,
|
||||
amount: 132,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -1191,7 +1217,7 @@ fn deploy_and_call_other_contract() {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
||||
account: deposit_account.clone(),
|
||||
free_balance: 131,
|
||||
free_balance: 132,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -1200,7 +1226,7 @@ fn deploy_and_call_other_contract() {
|
||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||
from: ALICE,
|
||||
to: deposit_account.clone(),
|
||||
amount: 131,
|
||||
amount: 132,
|
||||
}),
|
||||
topics: vec![],
|
||||
},
|
||||
@@ -5358,6 +5384,241 @@ fn delegate_call_indeterministic_code() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_remove_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::<Test>("add_remove_delegate_dependency").unwrap();
|
||||
let (wasm_callee, code_hash) = compile_module::<Test>("dummy").unwrap();
|
||||
let (wasm_other, other_code_hash) = compile_module::<Test>("call").unwrap();
|
||||
|
||||
// Define inputs with various actions to test adding / removing delegate_dependencies.
|
||||
// See the contract for more details.
|
||||
let noop_input = (0u32, code_hash);
|
||||
let add_delegate_dependency_input = (1u32, code_hash);
|
||||
let remove_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)| {
|
||||
Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(wasm_caller.clone()),
|
||||
input.encode(),
|
||||
vec![],
|
||||
DebugInfo::Skip,
|
||||
CollectEvents::Skip,
|
||||
)
|
||||
};
|
||||
|
||||
// Call contract with the given input.
|
||||
let call = |addr_caller: &AccountId32, input: &(u32, H256)| {
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr_caller.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
input.encode(),
|
||||
DebugInfo::UnsafeDebug,
|
||||
CollectEvents::Skip,
|
||||
Determinism::Enforced,
|
||||
)
|
||||
};
|
||||
|
||||
const ED: u64 = 2000;
|
||||
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
// Instantiate with add_delegate_dependency should fail since the code is not yet on chain.
|
||||
assert_err!(
|
||||
instantiate(&add_delegate_dependency_input).result,
|
||||
Error::<Test>::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(&add_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(contract.deposit_account()), ED + dependency_deposit);
|
||||
|
||||
// Removing the code should fail, since we have added a dependency.
|
||||
assert_err!(
|
||||
Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash),
|
||||
<Error<Test>>::CodeInUse
|
||||
);
|
||||
|
||||
// Adding an already existing dependency should fail.
|
||||
assert_err!(
|
||||
call(&addr_caller, &add_delegate_dependency_input).result,
|
||||
Error::<Test>::DelegateDependencyAlreadyExists
|
||||
);
|
||||
|
||||
// Adding a dependency to self should fail.
|
||||
assert_err!(
|
||||
call(&addr_caller, &(1u32, self_code_hash)).result,
|
||||
Error::<Test>::CannotAddSelfAsDelegateDependency
|
||||
);
|
||||
|
||||
// Adding 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::<Test>::MaxDelegateDependenciesReached
|
||||
);
|
||||
|
||||
// Removing dependency should work.
|
||||
assert_ok!(call(&addr_caller, &remove_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(contract.deposit_account()), ED);
|
||||
|
||||
// Removing an unexisting dependency should fail.
|
||||
assert_err!(
|
||||
call(&addr_caller, &remove_delegate_dependency_input).result,
|
||||
Error::<Test>::DelegateDependencyNotFound
|
||||
);
|
||||
|
||||
// Adding 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, &add_delegate_dependency_input).result,
|
||||
Error::<Test>::StorageDepositLimitExhausted
|
||||
);
|
||||
|
||||
// Since we removed 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::<Test>::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, &add_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 + 2 * ED + 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::<Test>("set_code_hash").unwrap();
|
||||
let (dummy_wasm, dummy_code_hash) = compile_module::<Test>("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::deposit_creating(&ALICE, 1_000_000);
|
||||
let lockup_deposit_percent = CodeHashLockupDepositPercent::get();
|
||||
let per_byte = DepositPerByte::get();
|
||||
let per_item = DepositPerItem::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 = Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
code,
|
||||
vec![],
|
||||
vec![],
|
||||
DebugInfo::Skip,
|
||||
CollectEvents::Skip,
|
||||
);
|
||||
|
||||
let addr = res.result.unwrap().account_id;
|
||||
let info = ContractInfoOf::<Test>::get(&addr).unwrap();
|
||||
let info_len = info.encoded_size() as u64;
|
||||
let base_deposit = ED + per_byte * info_len + per_item * 1;
|
||||
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 + info_len * per_byte + 1 * per_item + 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
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
dummy_code_hash.encode(),
|
||||
DebugInfo::Skip,
|
||||
CollectEvents::Skip,
|
||||
Determinism::Enforced,
|
||||
)
|
||||
.result
|
||||
.unwrap();
|
||||
|
||||
// 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(&info.deposit_account()), deposit - ED);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reentrance_count_works_with_call() {
|
||||
let (wasm, _code_hash) = compile_module::<Test>("reentrance_count_call").unwrap();
|
||||
|
||||
@@ -300,6 +300,22 @@ impl<T: Config> CodeInfo<T> {
|
||||
pub fn refcount(&self) -> u64 {
|
||||
self.refcount
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new(owner: T::AccountId) -> Self {
|
||||
CodeInfo {
|
||||
owner,
|
||||
deposit: Default::default(),
|
||||
refcount: 0,
|
||||
code_len: 0,
|
||||
determinism: Determinism::Enforced,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the deposit of the module.
|
||||
pub fn deposit(&self) -> BalanceOf<T> {
|
||||
self.deposit
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Executable<T> for WasmBlob<T> {
|
||||
@@ -396,6 +412,10 @@ impl<T: Config> Executable<T> for WasmBlob<T> {
|
||||
&self.code_hash
|
||||
}
|
||||
|
||||
fn code_info(&self) -> &CodeInfo<T> {
|
||||
&self.code_info
|
||||
}
|
||||
|
||||
fn code_len(&self) -> u32 {
|
||||
self.code.len() as u32
|
||||
}
|
||||
@@ -427,7 +447,10 @@ mod tests {
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
cell::RefCell,
|
||||
collections::hash_map::{Entry, HashMap},
|
||||
collections::{
|
||||
hash_map::{Entry, HashMap},
|
||||
HashSet,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -481,6 +504,7 @@ mod tests {
|
||||
sr25519_verify: RefCell<Vec<([u8; 64], Vec<u8>, [u8; 32])>>,
|
||||
code_hashes: Vec<CodeHash<Test>>,
|
||||
caller: Origin<Test>,
|
||||
delegate_dependencies: RefCell<HashSet<CodeHash<Test>>>,
|
||||
}
|
||||
|
||||
/// The call is mocked and just returns this hardcoded value.
|
||||
@@ -506,6 +530,7 @@ mod tests {
|
||||
ecdsa_recover: Default::default(),
|
||||
caller: Default::default(),
|
||||
sr25519_verify: Default::default(),
|
||||
delegate_dependencies: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -691,6 +716,22 @@ mod tests {
|
||||
fn nonce(&mut self) -> u64 {
|
||||
995
|
||||
}
|
||||
|
||||
fn add_delegate_dependency(
|
||||
&mut self,
|
||||
code: CodeHash<Self::T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.delegate_dependencies.borrow_mut().insert(code);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_delegate_dependency(
|
||||
&mut self,
|
||||
code: &CodeHash<Self::T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.delegate_dependencies.borrow_mut().remove(code);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the supplied code.
|
||||
@@ -3317,4 +3358,39 @@ mod tests {
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_remove_delegate_dependency() {
|
||||
const CODE_ADD_REMOVE_DELEGATE_DEPENDENCY: &str = r#"
|
||||
(module
|
||||
(import "seal0" "add_delegate_dependency" (func $add_delegate_dependency (param i32)))
|
||||
(import "seal0" "remove_delegate_dependency" (func $remove_delegate_dependency (param i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(call $add_delegate_dependency (i32.const 0))
|
||||
(call $add_delegate_dependency (i32.const 32))
|
||||
(call $remove_delegate_dependency (i32.const 32))
|
||||
)
|
||||
(func (export "deploy"))
|
||||
|
||||
;; hash1 (32 bytes)
|
||||
(data (i32.const 0)
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
)
|
||||
|
||||
;; hash2 (32 bytes)
|
||||
(data (i32.const 32)
|
||||
"\02\02\02\02\02\02\02\02\02\02\02\02\02\02\02\02"
|
||||
"\02\02\02\02\02\02\02\02\02\02\02\02\02\02\02\02"
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(&CODE_ADD_REMOVE_DELEGATE_DEPENDENCY, vec![], &mut mock_ext));
|
||||
let delegate_dependencies: Vec<_> =
|
||||
mock_ext.delegate_dependencies.into_inner().into_iter().collect();
|
||||
assert_eq!(delegate_dependencies.len(), 1);
|
||||
assert_eq!(delegate_dependencies[0].as_bytes(), [1; 32]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +267,10 @@ pub enum RuntimeCosts {
|
||||
AccountEntranceCount,
|
||||
/// Weight of calling `instantiation_nonce`
|
||||
InstantationNonce,
|
||||
/// Weight of calling `add_delegate_dependency`
|
||||
AddDelegateDependency,
|
||||
/// Weight of calling `remove_delegate_dependency`
|
||||
RemoveDelegateDependency,
|
||||
}
|
||||
|
||||
impl RuntimeCosts {
|
||||
@@ -348,6 +352,8 @@ impl RuntimeCosts {
|
||||
ReentrantCount => s.reentrance_count,
|
||||
AccountEntranceCount => s.account_reentrance_count,
|
||||
InstantationNonce => s.instantiation_nonce,
|
||||
AddDelegateDependency => s.add_delegate_dependency,
|
||||
RemoveDelegateDependency => s.remove_delegate_dependency,
|
||||
};
|
||||
RuntimeToken {
|
||||
#[cfg(test)]
|
||||
@@ -1573,9 +1579,6 @@ pub mod env {
|
||||
/// length to `output_len_ptr`. The copy of the output buffer and address can be skipped by
|
||||
/// supplying the sentinel value of `SENTINEL` to `output_ptr` or `address_ptr`.
|
||||
///
|
||||
/// `value` must be at least the minimum balance. Otherwise the instantiation fails and the
|
||||
/// contract is not created.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code.
|
||||
@@ -2821,4 +2824,30 @@ pub mod env {
|
||||
ctx.charge_gas(RuntimeCosts::InstantationNonce)?;
|
||||
Ok(ctx.ext.nonce())
|
||||
}
|
||||
|
||||
/// Adds a new delegate dependency to the contract.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `code_hash_ptr`: A pointer to the code hash of the dependency.
|
||||
#[unstable]
|
||||
fn add_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> {
|
||||
ctx.charge_gas(RuntimeCosts::AddDelegateDependency)?;
|
||||
let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
||||
ctx.ext.add_delegate_dependency(code_hash)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the delegate dependency from the contract.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `code_hash_ptr`: A pointer to the code hash of the dependency.
|
||||
#[unstable]
|
||||
fn remove_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> {
|
||||
ctx.charge_gas(RuntimeCosts::RemoveDelegateDependency)?;
|
||||
let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
||||
ctx.ext.remove_delegate_dependency(&code_hash)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2605
-2457
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user