From c0069e8041e146c1c30b6f4faaf8f5f47b46cff2 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 4 May 2023 16:45:08 +0200 Subject: [PATCH] Add add_dependency / remove_dependency --- frame/contracts/fixtures/add_dependency.wat | 95 ++++++++++++++++++ frame/contracts/src/exec.rs | 50 +++++++++- frame/contracts/src/lib.rs | 6 ++ frame/contracts/src/schedule.rs | 8 ++ frame/contracts/src/storage.rs | 37 ++++++- frame/contracts/src/storage/meter.rs | 17 ++++ frame/contracts/src/tests.rs | 101 +++++++++++++++++++- frame/contracts/src/wasm/mod.rs | 57 ++++++++++- frame/contracts/src/wasm/runtime.rs | 39 +++++++- frame/contracts/src/weights.rs | 2 + 10 files changed, 403 insertions(+), 9 deletions(-) create mode 100644 frame/contracts/fixtures/add_dependency.wat diff --git a/frame/contracts/fixtures/add_dependency.wat b/frame/contracts/fixtures/add_dependency.wat new file mode 100644 index 0000000000000..35d0c9153dcd1 --- /dev/null +++ b/frame/contracts/fixtures/add_dependency.wat @@ -0,0 +1,95 @@ +;; This contract tests the behavior of adding / removing dependencies when delegate calling into a contract. +(module + (import "seal0" "add_dependency" (func $add_dependency (param i32) (result i32))) + (import "seal0" "remove_dependency" (func $remove_dependency (param i32) (result i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + (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. + ;; if the action is 1 we call add_dependency, if the action is 2 we call remove_dependency. + ;; The next 32 bytes specify the code hash to use when calling add_dependency or remove_dependency. + (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 $seal_input (i32.const 4) (i32.const 0)) + + ;; Input data layout. + ;; [0..4) - size of the call + ;; [4..8) - action to perform before calling delegate_call (1: add_dependency, 2: remove_dependency, default: nothing) + ;; [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_dependency when action == 1. + (if (i32.eq (get_local $action) (i32.const 1)) + (then + (call $assert (i32.eqz + (call $add_dependency + (get_local $code_hash_ptr) + ) + )) + ) + (else) + ) + + ;; Call remove_dependency when action == 2. + (if (i32.eq (get_local $action) (i32.const 2)) + (then + (call $assert (i32.eqz + (call $remove_dependency + (get_local $code_hash_ptr) + ) + )) + ) + (else) + ) + ) + + (func (export "deploy") + (call $load_input) + ) + + (func (export "call") + (call $load_input) + + ;; Delegate call into passed code hash. + (call $assert + (i32.eq + (call $seal_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) + ) + ) + ) + +) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index a81a633f740ab..5947fc31045cb 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -18,8 +18,9 @@ use crate::{ gas::GasMeter, storage::{self, DepositAccount, WriteOutcome}, + wasm::{decrement_refcount, increment_refcount}, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error, - Event, Nonce, Origin, Pallet as Contracts, Schedule, System, LOG_TARGET, + Event, Nonce, Origin, OwnerInfoOf, Pallet as Contracts, Schedule, System, LOG_TARGET, }; use frame_support::{ crypto::ecdsa::ECDSAExt, @@ -35,14 +36,17 @@ use frame_support::{ Blake2_128Concat, BoundedVec, StorageHasher, }; use frame_system::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}, }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; -use sp_runtime::traits::{Convert, Hash, Zero}; +use sp_runtime::{ + traits::{Convert, Hash, Zero}, + Perbill, +}; use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec}; pub type AccountIdOf = ::AccountId; @@ -306,6 +310,22 @@ pub trait Ext: sealing::Sealed { /// Returns a nonce that is incremented for every instantiated contract. fn nonce(&mut self) -> u64; + + /// Add a contract dependency, This is useful for contract delegation, to make sure that the + /// delegated contract is not removed while it is still in use. + /// This will increase the reference count of the code hash, and charge a deposit of 30% of the + /// code deposit. + /// + /// Returns an error if we have reached the maximum number of dependencies, or the dependency + /// already exists. + fn add_dependency(&mut self, _code: CodeHash) -> Result<(), DispatchError>; + + /// Remove a contract dependency. + /// This is the counterpart of `add_dependency`. This method will decrease the reference count + /// And refund the deposit that was charged by `add_dependency`. + /// + /// Returns an error if the dependency does not exist. + fn remove_dependency(&mut self, _code: CodeHash) -> Result<(), DispatchError>; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -1461,6 +1481,30 @@ where current } } + fn add_dependency(&mut self, code_hash: CodeHash) -> Result<(), DispatchError> { + let frame = self.top_frame_mut(); + let info = frame.contract_info.get(&frame.account_id); + + increment_refcount::(code_hash)?; + let owner_info = OwnerInfoOf::::get(code_hash).ok_or(Error::::ContractNotFound)?; + let deposit = Perbill::from_percent(30).mul_ceil(owner_info.deposit()); + + frame + .nested_storage + .charge_dependency(info.deposit_account(), &StorageDeposit::Charge(deposit)); + info.add_dependency(code_hash, deposit) + } + + fn remove_dependency(&mut self, code_hash: CodeHash) -> Result<(), DispatchError> { + let frame = self.top_frame_mut(); + let info = frame.contract_info.get(&frame.account_id); + let deposit = info.remove_dependency(code_hash)?; + decrement_refcount::(code_hash); + frame + .nested_storage + .charge_dependency(info.deposit_account(), &StorageDeposit::Refund(deposit)); + Ok(()) + } } mod sealing { diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index e0e8e2d15b06d..eca15ca4d04db 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -861,6 +861,12 @@ pub mod pallet { CodeRejected, /// An indetermistic code was used in a context where this is not permitted. Indeterministic, + /// The contract has reached its maximum number of dependencies. + MaxDependenciesReached, + /// The dependency was not found in the contract's dependencies. + DependencyNotFound, + /// The contract already depends on the given dependency. + DependencyAlreadyExists, } /// A mapping from an original code hash to the original code, untouched by instrumentation. diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index c6eedb155d6a4..52ab1c7eb35e6 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -431,6 +431,12 @@ pub struct HostFnWeights { /// Weight of calling `instantiation_nonce`. pub instantiation_nonce: Weight, + /// Weight of calling `add_dependency`. + pub add_dependency: Weight, + + /// Weight of calling `remove_dependency`. + pub remove_dependency: Weight, + /// The type parameter is used in the default implementation. #[codec(skip)] pub _phantom: PhantomData, @@ -637,6 +643,8 @@ impl Default for HostFnWeights { reentrance_count: cost!(seal_reentrance_count), account_reentrance_count: cost!(seal_account_reentrance_count), instantiation_nonce: cost!(seal_instantiation_nonce), + add_dependency: cost!(add_dependency), + remove_dependency: cost!(remove_dependency), _phantom: PhantomData, } } diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 769caef0736fe..2809b33ff5035 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -33,10 +33,11 @@ use frame_support::{ DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; +use sp_core::ConstU32; use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Saturating, Zero}, - RuntimeDebug, + BoundedBTreeMap, DispatchResult, RuntimeDebug, }; use sp_std::{marker::PhantomData, ops::Deref, prelude::*}; @@ -66,6 +67,10 @@ pub struct ContractInfo { /// 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, + + /// This tracks the code hash and storage deposit paid to ensure that these dependencies don't + /// get removed and thus can be used for delegate calls. + dependencies: BoundedBTreeMap, BalanceOf, ConstU32<32>>, } impl ContractInfo { @@ -101,6 +106,7 @@ impl ContractInfo { storage_byte_deposit: Zero::zero(), storage_item_deposit: Zero::zero(), storage_base_deposit: Zero::zero(), + dependencies: Default::default(), }; Ok(contract) @@ -201,6 +207,35 @@ impl ContractInfo { }) } + /// Add a new `code_hash` 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 dependencies is reached or if + /// the dependency already exists. + pub fn add_dependency( + &mut self, + code_hash: CodeHash, + amount: BalanceOf, + ) -> DispatchResult { + self.dependencies + .try_insert(code_hash, amount) + .map_err(|_| Error::::MaxDependenciesReached)? + .map_or(Ok(()), |_| Err(Error::::DependencyAlreadyExists)) + .map_err(Into::into) + } + + /// Remove a `code_hash` dependency from the contract. + /// + /// Returns an error if the dependency doesn't exist. + pub fn remove_dependency(&mut self, dep: CodeHash) -> Result, DispatchError> { + self.dependencies.remove(&dep).ok_or(Error::::DependencyNotFound.into()) + } + + #[cfg(test)] + pub fn dependencies(&self) -> &BoundedBTreeMap, BalanceOf, ConstU32<32>> { + &self.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. diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 506f4f0d86649..a17bba3414521 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -406,6 +406,22 @@ where }; } + /// Add a charge for updating the contract's dependencies. + pub fn charge_dependency( + &mut self, + deposit_account: &DepositAccount, + amount: &DepositOf, + ) { + let charge = Charge { + deposit_account: deposit_account.clone(), + amount: amount.clone(), + terminated: false, + }; + + self.total_deposit = self.total_deposit.saturating_add(amount); + self.charges.push(charge); + } + /// Charge from `origin` a storage deposit for contract instantiation. /// /// This immediately transfers the balance in order to create the account. @@ -664,6 +680,7 @@ mod tests { storage_byte_deposit: info.bytes_deposit, storage_item_deposit: info.items_deposit, storage_base_deposit: Default::default(), + dependencies: Default::default(), } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index c32999d0ade3a..c2dd398a95a7a 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -44,6 +44,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; @@ -51,7 +52,7 @@ use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ testing::{Header, H256}, traits::{BlakeTwo256, Convert, Hash, IdentityLookup}, - AccountId32, TokenError, + AccountId32, Perbill, TokenError, }; use std::ops::Deref; @@ -5126,6 +5127,104 @@ fn delegate_call_indeterministic_code() { }); } +#[test] +fn add_remove_dependency_works() { + let (wasm_caller, _) = compile_module::("add_dependency").unwrap(); + let (wasm_callee, code_hash) = compile_module::("dummy").unwrap(); + + let input = (0u32, code_hash); + let add_dependency_input = (1u32, code_hash); + let remove_dependency_input = (2u32, 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![], + false, + ) + }; + + // Call contract with the given input. + let call = |addr_caller: &AccountId32, input: &(u32, H256)| { + >::bare_call( + ALICE, + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + input.encode(), + false, + Determinism::Relaxed, + ) + }; + + const ED: u64 = 200; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Instantiate with add_dependency should fail since the code is not yet on chain. + assert_err!(instantiate(&add_dependency_input).result, Error::::CodeNotFound); + + // Upload the delegated code. + let CodeUploadReturnValue { deposit, .. } = + Contracts::bare_upload_code(ALICE, wasm_callee, None, Determinism::Enforced).unwrap(); + + // Instantiate should now works. + let addr_caller = instantiate(&add_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 = &Perbill::from_percent(30).mul_ceil(deposit); + assert_eq!(contract.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), + >::CodeInUse + ); + + // Adding an already existing dependency should fail. + assert_err!( + call(&addr_caller, &add_dependency_input).result, + Error::::DependencyAlreadyExists + ); + + // Calling and removing dependency should work. + assert_ok!(call(&addr_caller, &remove_dependency_input).result); + + // Dependency should be removed, and deposit should be returned. + let contract = test_utils::get_contract(&addr_caller); + assert!(contract.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_dependency_input).result, + Error::::DependencyNotFound + ); + + // Adding a depedendency 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_dependency_input).result, + Error::::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, &input).result, Error::::ContractTrapped); + }); +} + #[test] fn reentrance_count_works_with_call() { let (wasm, _code_hash) = compile_module::("reentrance_count_call").unwrap(); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 224c116946aa4..657115675d788 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -32,6 +32,7 @@ pub use crate::wasm::runtime::api_doc; pub use tests::MockExt; pub use crate::wasm::{ + code_cache::{decrement_refcount, increment_refcount}, prepare::TryInstantiate, runtime::{ AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode, @@ -289,6 +290,11 @@ impl OwnerInfo { pub fn refcount(&self) -> u64 { self.refcount } + + /// Return the deposit of the module. + pub fn deposit(&self) -> BalanceOf { + self.deposit + } } impl Executable for PrefabWasmModule { @@ -383,7 +389,10 @@ mod tests { use std::{ borrow::BorrowMut, cell::RefCell, - collections::hash_map::{Entry, HashMap}, + collections::{ + hash_map::{Entry, HashMap}, + HashSet, + }, }; #[derive(Debug, PartialEq, Eq)] @@ -437,6 +446,7 @@ mod tests { sr25519_verify: RefCell, [u8; 32])>>, code_hashes: Vec>, caller: Origin, + dependencies: RefCell>>, } /// The call is mocked and just returns this hardcoded value. @@ -462,6 +472,7 @@ mod tests { ecdsa_recover: Default::default(), caller: Default::default(), sr25519_verify: Default::default(), + dependencies: Default::default(), } } } @@ -644,6 +655,16 @@ mod tests { fn nonce(&mut self) -> u64 { 995 } + + fn add_dependency(&mut self, code: CodeHash) -> Result<(), DispatchError> { + self.dependencies.borrow_mut().insert(code); + Ok(()) + } + + fn remove_dependency(&mut self, code: CodeHash) -> Result<(), DispatchError> { + self.dependencies.borrow_mut().remove(&code); + Ok(()) + } } /// Execute the supplied code. @@ -3275,4 +3296,38 @@ mod tests { >::CodeRejected, ); } + + #[test] + fn add_remove_dependency() { + const CODE_ADD_REMOVE_DEPENDENCY: &str = r#" +(module + (import "seal0" "add_dependency" (func $add_dependency (param i32) (result i32))) + (import "seal0" "remove_dependency" (func $remove_dependency (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (drop (call $add_dependency (i32.const 0))) + (drop (call $add_dependency (i32.const 32))) + (drop (call $remove_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_DEPENDENCY, vec![], &mut mock_ext)); + let dependencies: Vec<_> = mock_ext.dependencies.into_inner().into_iter().collect(); + assert_eq!(dependencies.len(), 1); + assert_eq!(dependencies[0].as_bytes(), [1; 32]); + } } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 330bf19d65232..071dae6527417 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -111,6 +111,10 @@ pub enum ReturnCode { EcdsaRecoverFailed = 11, /// sr25519 signature verification failed. Sr25519VerifyFailed = 12, + /// Add Contract Dependency failed. + AddDependencyFailed = 13, + /// Remove Contract Dependency failed. + RemoveDependencyFailed = 15, } impl From for ReturnCode { @@ -218,11 +222,17 @@ pub enum RuntimeCosts { /// Weight of calling `seal_random`. It includes the weight for copying the subject. Random, /// Weight of calling `seal_deposit_event` with the given number of topics and event size. - DepositEvent { num_topic: u32, len: u32 }, + DepositEvent { + num_topic: u32, + len: u32, + }, /// Weight of calling `seal_debug_message` per byte of passed message. DebugMessage(u32), /// Weight of calling `seal_set_storage` for the given storage item sizes. - SetStorage { old_bytes: u32, new_bytes: u32 }, + SetStorage { + old_bytes: u32, + new_bytes: u32, + }, /// Weight of calling `seal_clear_storage` per cleared byte. ClearStorage(u32), /// Weight of calling `seal_contains_storage` per byte of the checked item. @@ -242,7 +252,10 @@ pub enum RuntimeCosts { /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. CallInputCloned(u32), /// Weight of calling `seal_instantiate` for the given input length and salt. - InstantiateBase { input_data_len: u32, salt_len: u32 }, + InstantiateBase { + input_data_len: u32, + salt_len: u32, + }, /// Weight of the transfer performed during an instantiate. InstantiateSurchargeTransfer, /// Weight of calling `seal_hash_sha_256` for the given input size. @@ -271,6 +284,8 @@ pub enum RuntimeCosts { AccountEntranceCount, /// Weight of calling `instantiation_nonce` InstantationNonce, + AddDependency, + RemoveDependency, } impl RuntimeCosts { @@ -353,6 +368,8 @@ impl RuntimeCosts { ReentrantCount => s.reentrance_count, AccountEntranceCount => s.account_reentrance_count, InstantationNonce => s.instantiation_nonce, + AddDependency => s.add_dependency, + RemoveDependency => s.remove_dependency, }; RuntimeToken { #[cfg(test)] @@ -2814,4 +2831,20 @@ pub mod env { ctx.charge_gas(RuntimeCosts::InstantationNonce)?; Ok(ctx.ext.nonce()) } + + #[unstable] + fn add_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result { + ctx.charge_gas(RuntimeCosts::AddDependency)?; + let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; + ctx.ext.add_dependency(code_hash)?; + Ok(ReturnCode::Success) + } + + #[unstable] + fn remove_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result { + ctx.charge_gas(RuntimeCosts::RemoveDependency)?; + let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; + ctx.ext.remove_dependency(code_hash)?; + Ok(ReturnCode::Success) + } } diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 0a7f3ddf1ca4f..336cc249bb481 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -169,6 +169,8 @@ pub trait WeightInfo { fn instr_i64shru(r: u32, ) -> Weight; fn instr_i64rotl(r: u32, ) -> Weight; fn instr_i64rotr(r: u32, ) -> Weight; + fn add_dependency(_r: u32, ) -> Weight { Weight::zero() } + fn remove_dependency(_r: u32, ) -> Weight { Weight::zero() } } /// Weights for pallet_contracts using the Substrate node and recommended hardware.