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:
PG Herveou
2023-07-26 14:09:02 +02:00
committed by GitHub
parent 47bb475d6d
commit 5a5b1df69b
14 changed files with 3956 additions and 2798 deletions
+3
View File
@@ -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;
+451 -293
View File
@@ -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
+23 -2
View File
@@ -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,
}
}
+82 -5
View File
@@ -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.
+24 -18
View File
@@ -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(),
}
}
+273 -12
View File
@@ -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();
+77 -1
View File
@@ -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]);
}
}
+32 -3
View File
@@ -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(())
}
}
+2605 -2457
View File
File diff suppressed because it is too large Load Diff