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

Commit

Permalink
Make automatic storage deposits resistant against changing deposit pr…
Browse files Browse the repository at this point in the history
…ices (#12083)

* Require `FixedPointOperand` for Balances

* Delay deposit calculation

* Make refunds pro rata of consumed storage

* Add storage migration

* Fix clippy

* Add liquidity checks

* Fixe delayed deposit limit enforcement

* Defer charges

* Import Vec

* Add try-runtime hooks for migration

* Fix warning

* Adapt to new OnRuntimeUpgrade trait

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* fmt

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* More suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
  • Loading branch information
athei and agryaznov authored Sep 21, 2022
1 parent be97615 commit 493b58b
Show file tree
Hide file tree
Showing 12 changed files with 958 additions and 313 deletions.
1 change: 1 addition & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,7 @@ pub type Executive = frame_executive::Executive<
type Migrations = (
pallet_nomination_pools::migration::v2::MigrateToV2<Runtime>,
pallet_alliance::migration::Migration<Runtime>,
pallet_contracts::Migration<Runtime>,
);

/// MMR helper types.
Expand Down
5 changes: 3 additions & 2 deletions frame/balances/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ use sp_runtime::{
AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize,
Saturating, StaticLookup, Zero,
},
ArithmeticError, DispatchError, RuntimeDebug,
ArithmeticError, DispatchError, FixedPointOperand, RuntimeDebug,
};
use sp_std::{cmp, fmt::Debug, mem, ops::BitOr, prelude::*, result};
pub use weights::WeightInfo;
Expand All @@ -212,7 +212,8 @@ pub mod pallet {
+ MaybeSerializeDeserialize
+ Debug
+ MaxEncodedLen
+ TypeInfo;
+ TypeInfo
+ FixedPointOperand;

/// Handler for the unbalanced reduction when removing a dust account.
type DustRemoval: OnUnbalanced<NegativeImbalance<Self, I>>;
Expand Down
55 changes: 55 additions & 0 deletions frame/contracts/fixtures/create_storage_and_call.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
;; This calls another contract as passed as its account id. It also creates some storage.
(module
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
(import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 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)
)
)

(func (export "deploy"))

(func (export "call")
;; store length of input buffer
(i32.store (i32.const 0) (i32.const 512))

;; copy input at address 4
(call $seal_input (i32.const 4) (i32.const 0))

;; create 4 byte of storage before calling
(call $seal_set_storage
(i32.const 0) ;; Pointer to storage key
(i32.const 0) ;; Pointer to value
(i32.const 4) ;; Size of value
)

;; 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
)
))

;; create 8 byte of storage after calling
;; item of 12 bytes because we override 4 bytes
(call $seal_set_storage
(i32.const 0) ;; Pointer to storage key
(i32.const 0) ;; Pointer to value
(i32.const 12) ;; Size of value
)
)
)
18 changes: 9 additions & 9 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ where
module: WasmModule<T>,
data: Vec<u8>,
) -> Result<Contract<T>, &'static str> {
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let salt = vec![0xff];
let addr = Contracts::<T>::contract_address(&caller, &module.hash, &salt);
Expand Down Expand Up @@ -257,7 +257,7 @@ benchmarks! {
let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::sized(c, Location::Deploy), vec![],
)?;
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
let origin = RawOrigin::Signed(instance.caller.clone());
let callee = instance.addr;
}: call(origin, callee, value, Weight::MAX, None, vec![])
Expand All @@ -280,7 +280,7 @@ benchmarks! {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let s in 0 .. code::max_pages::<T>() * 64 * 1024;
let salt = vec![42u8; s as usize];
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
Expand All @@ -307,7 +307,7 @@ benchmarks! {
instantiate {
let s in 0 .. code::max_pages::<T>() * 64 * 1024;
let salt = vec![42u8; s as usize];
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy();
Expand Down Expand Up @@ -338,7 +338,7 @@ benchmarks! {
let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::dummy(), vec![],
)?;
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
let origin = RawOrigin::Signed(instance.caller.clone());
let callee = instance.addr.clone();
let before = T::Currency::free_balance(&instance.account_id);
Expand Down Expand Up @@ -767,13 +767,13 @@ benchmarks! {
let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
assert_eq!(T::Currency::free_balance(&instance.account_id), T::Currency::minimum_balance());
assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::<T>::min_balance());
assert_ne!(T::Currency::reserved_balance(&instance.account_id), 0u32.into());
}: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![])
verify {
if r > 0 {
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
assert_eq!(T::Currency::total_balance(&beneficiary), T::Currency::minimum_balance());
assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::<T>::min_balance());
}
}

Expand Down Expand Up @@ -1469,7 +1469,7 @@ benchmarks! {
.collect::<Vec<_>>();
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
let account_bytes = accounts.iter().flat_map(|x| x.encode()).collect();
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
assert!(value > 0u32.into());
let value_bytes = value.encode();
let value_len = value_bytes.len();
Expand Down Expand Up @@ -1705,7 +1705,7 @@ benchmarks! {
let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
let hashes_len = hashes_bytes.len();
let value = T::Currency::minimum_balance();
let value = Pallet::<T>::min_balance();
assert!(value > 0u32.into());
let value_bytes = value.encode();
let value_len = value_bytes.len();
Expand Down
23 changes: 13 additions & 10 deletions frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,15 @@ where
return Ok(output)
}

// Storage limit is enforced as late as possible (when the last frame returns) so that
// the ordering of storage accesses does not matter.
if self.frames.is_empty() {
let frame = &mut self.first_frame;
frame.contract_info.load(&frame.account_id);
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_limit(contract)?;
}

let frame = self.top_frame();
let account_id = &frame.account_id;
match (entry_point, delegated_code_hash) {
Expand Down Expand Up @@ -911,12 +920,7 @@ where
// it was invalidated.
frame.contract_info.load(account_id);
let mut contract = frame.contract_info.into_contract();
prev.nested_storage.absorb(
frame.nested_storage,
&self.origin,
account_id,
contract.as_mut(),
);
prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut());

// In case the contract wasn't terminated we need to persist changes made to it.
if let Some(contract) = contract {
Expand Down Expand Up @@ -954,7 +958,6 @@ where
let mut contract = self.first_frame.contract_info.as_contract();
self.storage_meter.absorb(
mem::take(&mut self.first_frame.nested_storage),
&self.origin,
&self.first_frame.account_id,
contract.as_deref_mut(),
);
Expand Down Expand Up @@ -2354,10 +2357,10 @@ mod tests {
let code_bob = MockLoader::insert(Call, |ctx, _| {
if ctx.input_data[0] == 0 {
let info = ctx.ext.contract_info();
assert_eq!(info.storage_deposit, 0);
info.storage_deposit = 42;
assert_eq!(info.storage_byte_deposit, 0);
info.storage_byte_deposit = 42;
assert_eq!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), exec_trapped());
assert_eq!(ctx.ext.contract_info().storage_deposit, 42);
assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42);
}
exec_success()
});
Expand Down
33 changes: 24 additions & 9 deletions frame/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@
mod gas;
mod benchmarking;
mod exec;
mod migration;
mod schedule;
mod storage;
mod wasm;

pub mod chain_extension;
pub mod migration;
pub mod weights;

#[cfg(test)]
Expand All @@ -109,7 +109,10 @@ use codec::{Encode, HasCompact};
use frame_support::{
dispatch::{DispatchClass, Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo},
ensure,
traits::{ConstU32, Contains, Currency, Get, Randomness, ReservableCurrency, Time},
traits::{
tokens::fungible::Inspect, ConstU32, Contains, Currency, Get, Randomness,
ReservableCurrency, Time,
},
weights::Weight,
BoundedVec, WeakBoundedVec,
};
Expand All @@ -126,6 +129,7 @@ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};

pub use crate::{
exec::{Frame, VarSizedKey as StorageKey},
migration::Migration,
pallet::*,
schedule::{HostFnWeights, InstructionWeights, Limits, Schedule},
};
Expand Down Expand Up @@ -225,7 +229,7 @@ pub mod pallet {
use frame_system::pallet_prelude::*;

/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(7);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
Expand All @@ -236,11 +240,12 @@ pub mod pallet {
/// The time implementation used to supply timestamps to contracts through `seal_now`.
type Time: Time;

/// The generator used to supply randomness to contracts through `seal_random`.
/// The generator used to supply randomness to contracts through `seal_random`
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;

/// The currency in which fees are paid and contract balances are held.
type Currency: ReservableCurrency<Self::AccountId>;
type Currency: ReservableCurrency<Self::AccountId>
+ Inspect<Self::AccountId, Balance = BalanceOf<Self>>;

/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
Expand Down Expand Up @@ -1036,7 +1041,7 @@ where
};
let schedule = T::Schedule::get();
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
origin,
origin.clone(),
dest,
&mut gas_meter,
&mut storage_meter,
Expand All @@ -1045,7 +1050,11 @@ where
data,
debug_message,
);
InternalCallOutput { result, gas_meter, storage_deposit: storage_meter.into_deposit() }
InternalCallOutput {
result,
gas_meter,
storage_deposit: storage_meter.into_deposit(&origin),
}
}

/// Internal function that does the actual instantiation.
Expand Down Expand Up @@ -1089,7 +1098,7 @@ where
value.saturating_add(extra_deposit),
)?;
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin,
origin.clone(),
executable,
&mut gas_meter,
&mut storage_meter,
Expand All @@ -1100,17 +1109,23 @@ where
debug_message,
);
storage_deposit = storage_meter
.into_deposit()
.into_deposit(&origin)
.saturating_add(&StorageDeposit::Charge(extra_deposit));
result
};
InternalInstantiateOutput { result: try_exec(), gas_meter, storage_deposit }
}

/// Deposit a pallet contracts event. Handles the conversion to the overarching event type.
fn deposit_event(topics: Vec<T::Hash>, event: Event<T>) {
<frame_system::Pallet<T>>::deposit_event_indexed(
&topics,
<T as Config>::RuntimeEvent::from(event).into(),
)
}

/// Return the existential deposit of [`Config::Currency`].
fn min_balance() -> BalanceOf<T> {
<T::Currency as Inspect<AccountIdOf<T>>>::minimum_balance()
}
}
Loading

0 comments on commit 493b58b

Please sign in to comment.