Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit c0069e8

Browse files
committed
Add add_dependency / remove_dependency
1 parent 1a6fc1e commit c0069e8

File tree

10 files changed

+403
-9
lines changed

10 files changed

+403
-9
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
;; This contract tests the behavior of adding / removing dependencies when delegate calling into a contract.
2+
(module
3+
(import "seal0" "add_dependency" (func $add_dependency (param i32) (result i32)))
4+
(import "seal0" "remove_dependency" (func $remove_dependency (param i32) (result i32)))
5+
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
6+
(import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32)))
7+
(import "env" "memory" (memory 1 1))
8+
9+
(func $assert (param i32)
10+
(block $ok
11+
(br_if $ok
12+
(get_local 0)
13+
)
14+
(unreachable)
15+
)
16+
)
17+
18+
;; This function loads input data and performs the action specified.
19+
;; The first 4 bytes of the input specify the action to perform.
20+
;; if the action is 1 we call add_dependency, if the action is 2 we call remove_dependency.
21+
;; The next 32 bytes specify the code hash to use when calling add_dependency or remove_dependency.
22+
(func $load_input
23+
(local $action i32)
24+
(local $code_hash_ptr i32)
25+
26+
;; Store available input size at offset 0.
27+
(i32.store (i32.const 0) (i32.const 512))
28+
29+
;; Read input data
30+
(call $seal_input (i32.const 4) (i32.const 0))
31+
32+
;; Input data layout.
33+
;; [0..4) - size of the call
34+
;; [4..8) - action to perform before calling delegate_call (1: add_dependency, 2: remove_dependency, default: nothing)
35+
;; [8..42) - code hash of the callee
36+
(set_local $action (i32.load (i32.const 4)))
37+
(set_local $code_hash_ptr (i32.const 8))
38+
39+
;; Assert input size == 36 (4 for action + 32 for code_hash).
40+
(call $assert
41+
(i32.eq
42+
(i32.load (i32.const 0))
43+
(i32.const 36)
44+
)
45+
)
46+
47+
;; Call add_dependency when action == 1.
48+
(if (i32.eq (get_local $action) (i32.const 1))
49+
(then
50+
(call $assert (i32.eqz
51+
(call $add_dependency
52+
(get_local $code_hash_ptr)
53+
)
54+
))
55+
)
56+
(else)
57+
)
58+
59+
;; Call remove_dependency when action == 2.
60+
(if (i32.eq (get_local $action) (i32.const 2))
61+
(then
62+
(call $assert (i32.eqz
63+
(call $remove_dependency
64+
(get_local $code_hash_ptr)
65+
)
66+
))
67+
)
68+
(else)
69+
)
70+
)
71+
72+
(func (export "deploy")
73+
(call $load_input)
74+
)
75+
76+
(func (export "call")
77+
(call $load_input)
78+
79+
;; Delegate call into passed code hash.
80+
(call $assert
81+
(i32.eq
82+
(call $seal_delegate_call
83+
(i32.const 0) ;; Set no call flags.
84+
(i32.const 8) ;; Pointer to "callee" code_hash.
85+
(i32.const 0) ;; Input is ignored.
86+
(i32.const 0) ;; Length of the input.
87+
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output.
88+
(i32.const 0) ;; Length is ignored in this case.
89+
)
90+
(i32.const 0)
91+
)
92+
)
93+
)
94+
95+
)

frame/contracts/src/exec.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
use crate::{
1919
gas::GasMeter,
2020
storage::{self, DepositAccount, WriteOutcome},
21+
wasm::{decrement_refcount, increment_refcount},
2122
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error,
22-
Event, Nonce, Origin, Pallet as Contracts, Schedule, System, LOG_TARGET,
23+
Event, Nonce, Origin, OwnerInfoOf, Pallet as Contracts, Schedule, System, LOG_TARGET,
2324
};
2425
use frame_support::{
2526
crypto::ecdsa::ECDSAExt,
@@ -35,14 +36,17 @@ use frame_support::{
3536
Blake2_128Concat, BoundedVec, StorageHasher,
3637
};
3738
use frame_system::RawOrigin;
38-
use pallet_contracts_primitives::ExecReturnValue;
39+
use pallet_contracts_primitives::{ExecReturnValue, StorageDeposit};
3940
use smallvec::{Array, SmallVec};
4041
use sp_core::{
4142
ecdsa::Public as ECDSAPublic,
4243
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
4344
};
4445
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
45-
use sp_runtime::traits::{Convert, Hash, Zero};
46+
use sp_runtime::{
47+
traits::{Convert, Hash, Zero},
48+
Perbill,
49+
};
4650
use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec};
4751

4852
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
@@ -306,6 +310,22 @@ pub trait Ext: sealing::Sealed {
306310

307311
/// Returns a nonce that is incremented for every instantiated contract.
308312
fn nonce(&mut self) -> u64;
313+
314+
/// Add a contract dependency, This is useful for contract delegation, to make sure that the
315+
/// delegated contract is not removed while it is still in use.
316+
/// This will increase the reference count of the code hash, and charge a deposit of 30% of the
317+
/// code deposit.
318+
///
319+
/// Returns an error if we have reached the maximum number of dependencies, or the dependency
320+
/// already exists.
321+
fn add_dependency(&mut self, _code: CodeHash<Self::T>) -> Result<(), DispatchError>;
322+
323+
/// Remove a contract dependency.
324+
/// This is the counterpart of `add_dependency`. This method will decrease the reference count
325+
/// And refund the deposit that was charged by `add_dependency`.
326+
///
327+
/// Returns an error if the dependency does not exist.
328+
fn remove_dependency(&mut self, _code: CodeHash<Self::T>) -> Result<(), DispatchError>;
309329
}
310330

311331
/// Describes the different functions that can be exported by an [`Executable`].
@@ -1461,6 +1481,30 @@ where
14611481
current
14621482
}
14631483
}
1484+
fn add_dependency(&mut self, code_hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
1485+
let frame = self.top_frame_mut();
1486+
let info = frame.contract_info.get(&frame.account_id);
1487+
1488+
increment_refcount::<T>(code_hash)?;
1489+
let owner_info = OwnerInfoOf::<T>::get(code_hash).ok_or(Error::<T>::ContractNotFound)?;
1490+
let deposit = Perbill::from_percent(30).mul_ceil(owner_info.deposit());
1491+
1492+
frame
1493+
.nested_storage
1494+
.charge_dependency(info.deposit_account(), &StorageDeposit::Charge(deposit));
1495+
info.add_dependency(code_hash, deposit)
1496+
}
1497+
1498+
fn remove_dependency(&mut self, code_hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
1499+
let frame = self.top_frame_mut();
1500+
let info = frame.contract_info.get(&frame.account_id);
1501+
let deposit = info.remove_dependency(code_hash)?;
1502+
decrement_refcount::<T>(code_hash);
1503+
frame
1504+
.nested_storage
1505+
.charge_dependency(info.deposit_account(), &StorageDeposit::Refund(deposit));
1506+
Ok(())
1507+
}
14641508
}
14651509

14661510
mod sealing {

frame/contracts/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,12 @@ pub mod pallet {
861861
CodeRejected,
862862
/// An indetermistic code was used in a context where this is not permitted.
863863
Indeterministic,
864+
/// The contract has reached its maximum number of dependencies.
865+
MaxDependenciesReached,
866+
/// The dependency was not found in the contract's dependencies.
867+
DependencyNotFound,
868+
/// The contract already depends on the given dependency.
869+
DependencyAlreadyExists,
864870
}
865871

866872
/// A mapping from an original code hash to the original code, untouched by instrumentation.

frame/contracts/src/schedule.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ pub struct HostFnWeights<T: Config> {
431431
/// Weight of calling `instantiation_nonce`.
432432
pub instantiation_nonce: Weight,
433433

434+
/// Weight of calling `add_dependency`.
435+
pub add_dependency: Weight,
436+
437+
/// Weight of calling `remove_dependency`.
438+
pub remove_dependency: Weight,
439+
434440
/// The type parameter is used in the default implementation.
435441
#[codec(skip)]
436442
pub _phantom: PhantomData<T>,
@@ -637,6 +643,8 @@ impl<T: Config> Default for HostFnWeights<T> {
637643
reentrance_count: cost!(seal_reentrance_count),
638644
account_reentrance_count: cost!(seal_account_reentrance_count),
639645
instantiation_nonce: cost!(seal_instantiation_nonce),
646+
add_dependency: cost!(add_dependency),
647+
remove_dependency: cost!(remove_dependency),
640648
_phantom: PhantomData,
641649
}
642650
}

frame/contracts/src/storage.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ use frame_support::{
3333
DefaultNoBound, RuntimeDebugNoBound,
3434
};
3535
use scale_info::TypeInfo;
36+
use sp_core::ConstU32;
3637
use sp_io::KillStorageResult;
3738
use sp_runtime::{
3839
traits::{Hash, Saturating, Zero},
39-
RuntimeDebug,
40+
BoundedBTreeMap, DispatchResult, RuntimeDebug,
4041
};
4142
use sp_std::{marker::PhantomData, ops::Deref, prelude::*};
4243

@@ -66,6 +67,10 @@ pub struct ContractInfo<T: Config> {
6667
/// We need to store this information separately so it is not used when calculating any refunds
6768
/// since the base deposit can only ever be refunded on contract termination.
6869
storage_base_deposit: BalanceOf<T>,
70+
71+
/// This tracks the code hash and storage deposit paid to ensure that these dependencies don't
72+
/// get removed and thus can be used for delegate calls.
73+
dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, ConstU32<32>>,
6974
}
7075

7176
impl<T: Config> ContractInfo<T> {
@@ -101,6 +106,7 @@ impl<T: Config> ContractInfo<T> {
101106
storage_byte_deposit: Zero::zero(),
102107
storage_item_deposit: Zero::zero(),
103108
storage_base_deposit: Zero::zero(),
109+
dependencies: Default::default(),
104110
};
105111

106112
Ok(contract)
@@ -201,6 +207,35 @@ impl<T: Config> ContractInfo<T> {
201207
})
202208
}
203209

210+
/// Add a new `code_hash` dependency to the contract.
211+
/// The `amount` is the amount of funds that will be reserved for the dependency.
212+
///
213+
/// Returns an error if the maximum number of dependencies is reached or if
214+
/// the dependency already exists.
215+
pub fn add_dependency(
216+
&mut self,
217+
code_hash: CodeHash<T>,
218+
amount: BalanceOf<T>,
219+
) -> DispatchResult {
220+
self.dependencies
221+
.try_insert(code_hash, amount)
222+
.map_err(|_| Error::<T>::MaxDependenciesReached)?
223+
.map_or(Ok(()), |_| Err(Error::<T>::DependencyAlreadyExists))
224+
.map_err(Into::into)
225+
}
226+
227+
/// Remove a `code_hash` dependency from the contract.
228+
///
229+
/// Returns an error if the dependency doesn't exist.
230+
pub fn remove_dependency(&mut self, dep: CodeHash<T>) -> Result<BalanceOf<T>, DispatchError> {
231+
self.dependencies.remove(&dep).ok_or(Error::<T>::DependencyNotFound.into())
232+
}
233+
234+
#[cfg(test)]
235+
pub fn dependencies(&self) -> &BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, ConstU32<32>> {
236+
&self.dependencies
237+
}
238+
204239
/// Push a contract's trie to the deletion queue for lazy removal.
205240
///
206241
/// You must make sure that the contract is also removed when queuing the trie for deletion.

frame/contracts/src/storage/meter.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,22 @@ where
406406
};
407407
}
408408

409+
/// Add a charge for updating the contract's dependencies.
410+
pub fn charge_dependency(
411+
&mut self,
412+
deposit_account: &DepositAccount<T>,
413+
amount: &DepositOf<T>,
414+
) {
415+
let charge = Charge {
416+
deposit_account: deposit_account.clone(),
417+
amount: amount.clone(),
418+
terminated: false,
419+
};
420+
421+
self.total_deposit = self.total_deposit.saturating_add(amount);
422+
self.charges.push(charge);
423+
}
424+
409425
/// Charge from `origin` a storage deposit for contract instantiation.
410426
///
411427
/// This immediately transfers the balance in order to create the account.
@@ -664,6 +680,7 @@ mod tests {
664680
storage_byte_deposit: info.bytes_deposit,
665681
storage_item_deposit: info.items_deposit,
666682
storage_base_deposit: Default::default(),
683+
dependencies: Default::default(),
667684
}
668685
}
669686

0 commit comments

Comments
 (0)