-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Contracts: runtime_call and storage_deposit #13990
Changes from 5 commits
909cbae
fb0cc68
81859b6
0ccd804
a5a63da
21e2fc1
c81bde4
60c2bf4
cd177fc
672070b
ead8ba3
826bfa7
faf1b12
ec43399
fe79901
2bed075
e68423d
badc93c
22cea6e
05c9f8c
45aae4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| (module | ||
| (import "seal0" "call_runtime" (func $call_runtime (param i32 i32) (result i32))) | ||
| (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) | ||
| (import "seal0" "seal_input" (func $seal_input (param i32 i32))) | ||
| (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) | ||
| (import "env" "memory" (memory 1 1)) | ||
|
|
||
| (func $assert (param i32) | ||
| (block $ok | ||
| (br_if $ok (get_local 0)) | ||
| (unreachable) | ||
| ) | ||
| ) | ||
|
|
||
| (func (export "call") | ||
| ;; 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) - storage length | ||
| ;; [8..40) - hash code of the callee | ||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;; [40..n) - encoded runtime call | ||
|
|
||
| ;; Invoke call_runtime with the encoded call passed to this contract. | ||
| (call $assert (i32.eqz | ||
| (call $call_runtime | ||
| (i32.const 40) ;; Pointer where the call is stored | ||
| (i32.sub | ||
| (i32.load (i32.const 0)) ;; Size of the call | ||
| (i32.const 36) ;; Size of the call without the call_runtime | ||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) | ||
| ) | ||
| )) | ||
|
|
||
| ;; call passed contract | ||
| (call $assert (i32.eqz | ||
| (call $seal_call | ||
| (i32.const 0) ;; No flags | ||
| (i32.const 8) ;; Pointer to "callee" address. | ||
| (i64.const 0) ;; How much gas to devote for the execution. 0 = all. | ||
| (i32.const 512) ;; Pointer to the buffer with value to transfer | ||
| (i32.const 4) ;; Pointer to input data buffer address | ||
| (i32.const 4) ;; Length of input data buffer | ||
| (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output | ||
| (i32.const 0) ;; Length is ignored in this case | ||
| ) | ||
| )) | ||
| ) | ||
|
|
||
| (func (export "deploy")) | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,7 +82,7 @@ pub trait Ext<T: Config> { | |
| deposit_account: &DepositAccount<T>, | ||
| amount: &DepositOf<T>, | ||
| terminated: bool, | ||
| ); | ||
| ) -> Result<(), DispatchError>; | ||
| } | ||
|
|
||
| /// This [`Ext`] is used for actual on-chain execution when balance needs to be charged. | ||
|
|
@@ -343,14 +343,14 @@ where | |
| /// | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docs above this line could be improved a bit to reflect the fact this is now a fallible fn |
||
| /// This drops the root meter in order to make sure it is only called when the whole | ||
| /// execution did finish. | ||
| pub fn into_deposit(self, origin: &T::AccountId) -> DepositOf<T> { | ||
| pub fn try_into_deposit(self, origin: &T::AccountId) -> Result<DepositOf<T>, DispatchError> { | ||
| for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { | ||
| E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated); | ||
| E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated)?; | ||
| } | ||
| for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { | ||
| E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated); | ||
| E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated)?; | ||
| } | ||
| self.total_deposit | ||
| Ok(self.total_deposit) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -405,7 +405,8 @@ where | |
| info.deposit_account(), | ||
| &deposit.saturating_sub(&Deposit::Charge(ed)), | ||
| false, | ||
| ); | ||
| )?; | ||
|
|
||
| System::<T>::inc_consumers(info.deposit_account())?; | ||
|
|
||
| // We also need to make sure that the contract's account itself exists. | ||
|
|
@@ -476,41 +477,14 @@ impl<T: Config> Ext<T> for ReservingExt { | |
| deposit_account: &DepositAccount<T>, | ||
| amount: &DepositOf<T>, | ||
| terminated: bool, | ||
| ) { | ||
| // There is nothing we can do when this fails as this constitutes a bug in the runtime. | ||
| // We need to settle for emitting an error log in this case. | ||
| // | ||
| // # Note | ||
| // | ||
| // This is infallible because it is called in a part of the execution where we cannot | ||
| // simply roll back. It might make sense to do some refactoring to move the deposit | ||
| // collection to the fallible part of execution. | ||
| ) -> Result<(), DispatchError> { | ||
| match amount { | ||
| Deposit::Charge(amount) => { | ||
| // This will never fail because a deposit account is required to exist | ||
| // at all times. The pallet enforces this invariant by holding a consumer reference | ||
| // on the deposit account as long as the contract exists. | ||
| // | ||
| // The sender always has enough balance because we checked that it had enough | ||
| // balance when instantiating the storage meter. There is no way for the sender | ||
| // which is a plain account to send away this balance in the meantime. | ||
| let result = T::Currency::transfer( | ||
| origin, | ||
| deposit_account, | ||
| *amount, | ||
| ExistenceRequirement::KeepAlive, | ||
| ); | ||
| if let Err(err) = result { | ||
| log::error!( | ||
| target: "runtime::contracts", | ||
| "Failed to transfer storage deposit {:?} from origin {:?} to deposit account {:?}: {:?}", | ||
| amount, origin, deposit_account, err, | ||
| ); | ||
| if cfg!(debug_assertions) { | ||
| panic!("Unable to collect storage deposit. This is a bug."); | ||
| } | ||
| } | ||
| }, | ||
| Deposit::Charge(amount) => T::Currency::transfer( | ||
| origin, | ||
| deposit_account, | ||
| *amount, | ||
| ExistenceRequirement::KeepAlive, | ||
| ), | ||
| // The receiver always exists because the initial value transfer from the | ||
| // origin to the contract has a keep alive existence requirement. When taking a deposit | ||
| // we make sure to leave at least the ed in the free balance. | ||
|
|
@@ -539,8 +513,9 @@ impl<T: Config> Ext<T> for ReservingExt { | |
| panic!("Unable to refund storage deposit. This is a bug."); | ||
| } | ||
| } | ||
| Ok(()) | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -613,7 +588,7 @@ mod tests { | |
| contract: &DepositAccount<Test>, | ||
| amount: &DepositOf<Test>, | ||
| terminated: bool, | ||
| ) { | ||
| ) -> Result<(), DispatchError> { | ||
| TestExtTestValue::mutate(|ext| { | ||
| ext.charges.push(Charge { | ||
| origin: origin.clone(), | ||
|
|
@@ -622,6 +597,7 @@ mod tests { | |
| terminated, | ||
| }) | ||
| }); | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -719,7 +695,7 @@ mod tests { | |
| nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); | ||
| meter.absorb(nested0, DepositAccount(BOB), Some(&mut nested0_info)); | ||
|
|
||
| meter.into_deposit(&ALICE); | ||
| meter.try_into_deposit(&ALICE).unwrap(); | ||
|
|
||
| assert_eq!(nested0_info.extra_deposit(), 112); | ||
| assert_eq!(nested1_info.extra_deposit(), 110); | ||
|
|
@@ -779,7 +755,7 @@ mod tests { | |
| nested0.absorb(nested1, DepositAccount(CHARLIE), None); | ||
|
|
||
| meter.absorb(nested0, DepositAccount(BOB), None); | ||
| meter.into_deposit(&ALICE); | ||
| meter.try_into_deposit(&ALICE).unwrap(); | ||
|
|
||
| assert_eq!( | ||
| TestExtTestValue::get(), | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -70,6 +70,7 @@ frame_support::construct_runtime!( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Randomness: pallet_insecure_randomness_collective_flip::{Pallet, Storage}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Utility: pallet_utility::{Pallet, Call, Storage, Event}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Proxy: pallet_proxy::{Pallet, Call, Storage, Event<T>}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -337,6 +338,22 @@ impl pallet_utility::Config for Test { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type PalletsOrigin = OriginCaller; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type WeightInfo = (); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| impl pallet_proxy::Config for Test { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type RuntimeEvent = RuntimeEvent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type RuntimeCall = RuntimeCall; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Currency = Balances; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ProxyType = (); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ProxyDepositBase = ConstU64<1>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ProxyDepositFactor = ConstU64<1>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type MaxProxies = ConstU32<32>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type WeightInfo = (); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type MaxPending = ConstU32<32>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type CallHasher = BlakeTwo256; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type AnnouncementDepositBase = ConstU64<1>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type AnnouncementDepositFactor = ConstU64<1>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parameter_types! { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub MySchedule: Schedule<Test> = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut schedule = <Schedule<Test>>::default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2966,6 +2983,83 @@ fn sr25519_verify() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn test_failed_charge_should_roll_back_call() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let (wasm_caller, _) = compile_module::<Test>("call_runtime_and_call").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let (wasm_callee, _) = compile_module::<Test>("store").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExtBuilder::default().existential_deposit(200).build().execute_with(|| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let _ = Balances::deposit_creating(&ALICE, 1_000_000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Instantiate both contracts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let addr_caller = Contracts::bare_instantiate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ALICE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GAS_LIMIT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Code::Upload(wasm_caller), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vec![], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vec![], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .account_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let addr_callee = Contracts::bare_instantiate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ALICE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GAS_LIMIT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Code::Upload(wasm_callee), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vec![], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vec![], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .account_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Give caller proxy access to Alice. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(ALICE), addr_caller.clone(), (), 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a Proxy call that will attempt to transfer away's Alice balance, so we can test a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // failed charge. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let min_balance = <Test as Config>::Currency::minimum_balance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let transfer_call = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dest: CHARLIE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: pallet_balances::Pallet::<Test>::free_balance(&ALICE) - min_balance, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wrap the transfer call in a proxy call. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let transfer_proxy_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| real: ALICE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| force_proxy_type: Some(()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| call: transfer_call, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // bump the deposit per byte to a high value to trigger a FundsUnavailable error. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 100); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let data = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 100u32, // storage length | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr_callee, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transfer_proxy_call, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let result = <Pallet<Test>>::call( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RuntimeOrigin::signed(ALICE), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr_caller.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GAS_LIMIT, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data.encode(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pgherveou marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_err_ignore_postinfo!(result, TokenError::FundsUnavailable); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn pre_dispatch( | |
| self, | |
| who: &Self::AccountId, | |
| call: &Self::Call, | |
| info: &DispatchInfoOf<Self::Call>, | |
| len: usize, | |
| ) -> Result<Self::Pre, TransactionValidityError> { | |
| let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?; | |
| Ok((self.0, who.clone(), imbalance)) | |
| } | |
| fn post_dispatch( | |
| maybe_pre: Option<Self::Pre>, | |
| info: &DispatchInfoOf<Self::Call>, | |
| post_info: &PostDispatchInfoOf<Self::Call>, | |
| len: usize, | |
| _result: &DispatchResult, | |
| ) -> Result<(), TransactionValidityError> { | |
| if let Some((tip, who, imbalance)) = maybe_pre { | |
| let actual_fee = Pallet::<T>::compute_actual_fee(len as u32, info, post_info, tip); | |
| T::OnChargeTransaction::correct_and_deposit_fee( | |
| &who, info, post_info, actual_fee, tip, imbalance, | |
| )?; | |
| Pallet::<T>::deposit_event(Event::<T>::TransactionFeePaid { who, actual_fee, tip }); | |
| } | |
| Ok(()) | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can check the events emitted during execution. We do that in other tests. There is a event for transaction fee payment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we would need the transaction_payment pallet for this in test, I don't think we have it 🤔 might have overlooked, will check again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yeah. You are right. We didn't set this up in tests. It is fine. We don't really need to test this.
Uh oh!
There was an error while loading. Please reload this page.