Skip to content

Commit

Permalink
contracts: Add storage deposits (paritytech#10082)
Browse files Browse the repository at this point in the history
* Frame no longer needs to be mutable (refactoring artifact)

* Remove Contract/Tombstone deposit

* Add StorageMeter

* cargo fmt

* Fix weight annotation

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Simplify keep check for contract accounts

- Make sure that the "base deposit" for each contract >= ed
- Remove now obsolete checks when sneding away free balance

* Remove unused imports and functions

* Rename storage_limit to storage_deposit_limit

* cargo fmt

* Fix typo

Co-authored-by: Michael Müller <michi@parity.io>

* Finish up rename of storage_limit

* Fix rpc tests

* Make use of `StorageDepositLimitTooHigh`

* Add tests and fix bugs discovered by tests

* Add storage migration

* Don't use u128 in RPC

* Fix weight of migration

* Rename `endowment` to `value`

* Fix bug where contract couldn't get funded by a storage deposit

- Make sure that contract gets funded from deposits before value is transferred
- Don't reserve value at origin because otherwise funding isn't possible
	- Just transfer free balance and reserve it after the transfer
- When refunding make sure that this refund can't dust the contract
	- Can only happen after a runtime upgrade where costs where upped
- Add more tests

* Apply suggestions from code review

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Remove unused `fn storage_meter`

* Fix copy pasta doc error

* Import `MaxEncodeLen` from codec

* Beautify RPC trait bounds

* Add re-instrument behaviour to dispatchable doc

* Make sure a account won't be destroyed a refund after a slash

* Apply suggestions from code review

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Update `Storage::write` docs

* Improve doc

* Remove superflous conditional

* Typos

* Remove superflous clone (refactoring artifact)

* Apply suggestions from code review

Co-authored-by: Andrew Jones <ascjones@gmail.com>

Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: Michael Müller <michi@parity.io>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
  • Loading branch information
4 people authored and ark0f committed Feb 27, 2023
1 parent 2499b86 commit b986852
Show file tree
Hide file tree
Showing 27 changed files with 4,375 additions and 1,894 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/node/executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" }
sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" }
sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore" }
sp-state-machine = { version = "0.10.0-dev", path = "../../../primitives/state-machine" }
sp-tracing = { version = "4.0.0-dev", path = "../../../primitives/tracing" }
sp-trie = { version = "4.0.0-dev", path = "../../../primitives/trie" }
frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" }

Expand Down
6 changes: 3 additions & 3 deletions bin/node/executor/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,6 @@ fn deploying_wasm_contract_should_work() {

let addr = pallet_contracts::Pallet::<Runtime>::contract_address(&charlie(), &transfer_ch, &[]);

let subsistence = pallet_contracts::Pallet::<Runtime>::subsistence_threshold();

let time = 42 * 1000;
let b = construct_block(
&mut new_test_ext(compact_code_unwrap()),
Expand All @@ -701,8 +699,9 @@ fn deploying_wasm_contract_should_work() {
signed: Some((charlie(), signed_extra(0, 0))),
function: Call::Contracts(
pallet_contracts::Call::instantiate_with_code::<Runtime> {
endowment: 1000 * DOLLARS + subsistence,
value: 0,
gas_limit: 500_000_000,
storage_deposit_limit: None,
code: transfer_code,
data: Vec::new(),
salt: Vec::new(),
Expand All @@ -715,6 +714,7 @@ fn deploying_wasm_contract_should_work() {
dest: sp_runtime::MultiAddress::Id(addr.clone()),
value: 10,
gas_limit: 500_000_000,
storage_deposit_limit: None,
data: vec![0x00, 0x01, 0x02, 0x03],
}),
},
Expand Down
2 changes: 1 addition & 1 deletion bin/node/executor/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub fn executor_call<
hash: sp_core::blake2_256(&code).to_vec(),
heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()),
};

sp_tracing::try_init_simple();
executor().call::<R, NC>(&mut t, &runtime_code, method, data, use_native, native_call)
}

Expand Down
30 changes: 20 additions & 10 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,10 +897,8 @@ impl pallet_tips::Config for Runtime {
}

parameter_types! {
pub ContractDeposit: Balance = deposit(
1,
<pallet_contracts::Pallet<Runtime>>::contract_info_size(),
);
pub const DepositPerItem: Balance = deposit(1, 0);
pub const DepositPerByte: Balance = deposit(0, 1);
pub const MaxValueSize: u32 = 16 * 1024;
// The lazy deletion runs inside on_initialize.
pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO *
Expand All @@ -927,7 +925,8 @@ impl pallet_contracts::Config for Runtime {
/// change because that would break already deployed contracts. The `Call` structure itself
/// is not allowed to change the indices of existing pallets, too.
type CallFilter = Nothing;
type ContractDeposit = ContractDeposit;
type DepositPerItem = DepositPerItem;
type DepositPerByte = DepositPerByte;
type CallStack = [pallet_contracts::Frame<Self>; 31];
type WeightPrice = pallet_transaction_payment::Pallet<Self>;
type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
Expand Down Expand Up @@ -1522,21 +1521,32 @@ impl_runtime_apis! {
dest: AccountId,
value: Balance,
gas_limit: u64,
storage_deposit_limit: Option<Balance>,
input_data: Vec<u8>,
) -> pallet_contracts_primitives::ContractExecResult {
Contracts::bare_call(origin, dest, value, gas_limit, input_data, true)
) -> pallet_contracts_primitives::ContractExecResult<Balance> {
Contracts::bare_call(origin, dest, value, gas_limit, storage_deposit_limit, input_data, true)
}

fn instantiate(
origin: AccountId,
endowment: Balance,
value: Balance,
gas_limit: u64,
storage_deposit_limit: Option<Balance>,
code: pallet_contracts_primitives::Code<Hash>,
data: Vec<u8>,
salt: Vec<u8>,
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId>
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, Balance>
{
Contracts::bare_instantiate(origin, value, gas_limit, storage_deposit_limit, code, data, salt, true)
}

fn upload_code(
origin: AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<Balance>,
) -> pallet_contracts_primitives::CodeUploadResult<Hash, Balance>
{
Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true)
Contracts::bare_upload_code(origin, code, storage_deposit_limit)
}

fn get_storage(
Expand Down
1 change: 1 addition & 0 deletions frame/contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primit

[dev-dependencies]
assert_matches = "1"
env_logger = "0.9"
hex-literal = "0.3"
pretty_assertions = "1"
wat = "1"
Expand Down
2 changes: 2 additions & 0 deletions frame/contracts/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ serde = { version = "1", features = ["derive"], optional = true }
# Substrate Dependencies (This crate should not rely on frame)
sp-core = { version = "4.0.0-dev", path = "../../../primitives/core", default-features = false }
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
sp-rpc = { version = "4.0.0-dev", path = "../../../primitives/rpc", optional = true }
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }

[features]
Expand All @@ -31,5 +32,6 @@ std = [
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
"sp-rpc",
"serde",
]
183 changes: 176 additions & 7 deletions frame/contracts/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,32 @@
use bitflags::bitflags;
use codec::{Decode, Encode};
use sp_core::Bytes;
use sp_runtime::{DispatchError, RuntimeDebug};
use sp_runtime::{
traits::{Saturating, Zero},
DispatchError, RuntimeDebug,
};
use sp_std::prelude::*;

#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "std")]
use sp_rpc::number::NumberOrHex;

/// Result type of a `bare_call` or `bare_instantiate` call.
///
/// It contains the execution result together with some auxiliary information.
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
pub struct ContractResult<T> {
#[cfg_attr(
feature = "std",
serde(
rename_all = "camelCase",
bound(serialize = "R: Serialize, Balance: Copy + Into<NumberOrHex>"),
bound(deserialize = "R: Deserialize<'de>, Balance: TryFrom<NumberOrHex>")
)
)]
pub struct ContractResult<R, Balance> {
/// How much gas was consumed during execution.
pub gas_consumed: u64,
/// How much gas is required as gas limit in order to execute this call.
Expand All @@ -45,7 +58,14 @@ pub struct ContractResult<T> {
///
/// This can only different from [`Self::gas_consumed`] when weight pre charging
/// is used. Currently, only `seal_call_runtime` makes use of pre charging.
/// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
/// when a non-zero `gas_limit` argument is supplied.
pub gas_required: u64,
/// How much balance was deposited and reserved during execution in order to pay for storage.
///
/// The storage deposit is never actually charged from the caller in case of [`Self::result`]
/// is `Err`. This is because on error all storage changes are rolled back.
pub storage_deposit: StorageDeposit<Balance>,
/// An optional debug message. This message is only filled when explicitly requested
/// by the code that calls into the contract. Otherwise it is empty.
///
Expand All @@ -63,15 +83,20 @@ pub struct ContractResult<T> {
#[cfg_attr(feature = "std", serde(with = "as_string"))]
pub debug_message: Vec<u8>,
/// The execution result of the wasm code.
pub result: T,
pub result: R,
}

/// Result type of a `bare_call` call.
pub type ContractExecResult = ContractResult<Result<ExecReturnValue, DispatchError>>;
pub type ContractExecResult<Balance> =
ContractResult<Result<ExecReturnValue, DispatchError>, Balance>;

/// Result type of a `bare_instantiate` call.
pub type ContractInstantiateResult<AccountId> =
ContractResult<Result<InstantiateReturnValue<AccountId>, DispatchError>>;
pub type ContractInstantiateResult<AccountId, Balance> =
ContractResult<Result<InstantiateReturnValue<AccountId>, DispatchError>, Balance>;

/// Result type of a `bare_code_upload` call.
pub type CodeUploadResult<CodeHash, Balance> =
Result<CodeUploadReturnValue<CodeHash, Balance>, DispatchError>;

/// Result type of a `get_storage` call.
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
Expand Down Expand Up @@ -123,6 +148,17 @@ pub struct InstantiateReturnValue<AccountId> {
pub account_id: AccountId,
}

/// The result of succesfully uploading a contract.
#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
pub struct CodeUploadReturnValue<CodeHash, Balance> {
/// The key under which the new code is stored.
pub code_hash: CodeHash,
/// The deposit that was reserved at the caller. Is zero when the code already existed.
pub deposit: Balance,
}

/// Reference to an existing code hash or a new wasm module.
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
Expand All @@ -134,6 +170,116 @@ pub enum Code<Hash> {
Existing(Hash),
}

/// The amount of balance that was either charged or refunded in order to pay for storage.
#[derive(Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, Clone)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "std",
serde(
rename_all = "camelCase",
bound(serialize = "Balance: Copy + Into<NumberOrHex>"),
bound(deserialize = "Balance: TryFrom<NumberOrHex>")
)
)]
pub enum StorageDeposit<Balance> {
/// The transaction reduced storage consumption.
///
/// This means that the specified amount of balance was transferred from the involved
/// contracts to the call origin.
#[cfg_attr(feature = "std", serde(with = "as_hex"))]
Refund(Balance),
/// The transaction increased overall storage usage.
///
/// This means that the specified amount of balance was transferred from the call origin
/// to the contracts involved.
#[cfg_attr(feature = "std", serde(with = "as_hex"))]
Charge(Balance),
}

impl<Balance: Zero> Default for StorageDeposit<Balance> {
fn default() -> Self {
Self::Charge(Zero::zero())
}
}

impl<Balance: Zero + Copy> StorageDeposit<Balance> {
/// Returns how much balance is charged or `0` in case of a refund.
pub fn charge_or_zero(&self) -> Balance {
match self {
Self::Charge(amount) => *amount,
Self::Refund(_) => Zero::zero(),
}
}

pub fn is_zero(&self) -> bool {
match self {
Self::Charge(amount) => amount.is_zero(),
Self::Refund(amount) => amount.is_zero(),
}
}
}

impl<Balance> StorageDeposit<Balance>
where
Balance: Saturating + Ord + Copy,
{
/// This is essentially a saturating signed add.
pub fn saturating_add(&self, rhs: &Self) -> Self {
use StorageDeposit::*;
match (self, rhs) {
(Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)),
(Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)),
(Charge(lhs), Refund(rhs)) =>
if lhs >= rhs {
Charge(lhs.saturating_sub(*rhs))
} else {
Refund(rhs.saturating_sub(*lhs))
},
(Refund(lhs), Charge(rhs)) =>
if lhs > rhs {
Refund(lhs.saturating_sub(*rhs))
} else {
Charge(rhs.saturating_sub(*lhs))
},
}
}

/// This is essentially a saturating signed sub.
pub fn saturating_sub(&self, rhs: &Self) -> Self {
use StorageDeposit::*;
match (self, rhs) {
(Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)),
(Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)),
(Charge(lhs), Charge(rhs)) =>
if lhs >= rhs {
Charge(lhs.saturating_sub(*rhs))
} else {
Refund(rhs.saturating_sub(*lhs))
},
(Refund(lhs), Refund(rhs)) =>
if lhs > rhs {
Refund(lhs.saturating_sub(*rhs))
} else {
Charge(rhs.saturating_sub(*lhs))
},
}
}

/// If the amount of deposit (this type) is constrained by a `limit` this calcuates how
/// much balance (if any) is still available from this limit.
///
/// # Note
///
/// In case of a refund the return value can be larger than `limit`.
pub fn available(&self, limit: &Balance) -> Balance {
use StorageDeposit::*;
match self {
Charge(amount) => limit.saturating_sub(*amount),
Refund(amount) => limit.saturating_add(*amount),
}
}
}

#[cfg(feature = "std")]
mod as_string {
use super::*;
Expand All @@ -149,3 +295,26 @@ mod as_string {
Ok(String::deserialize(deserializer)?.into_bytes())
}
}

#[cfg(feature = "std")]
mod as_hex {
use super::*;
use serde::{de::Error as _, Deserializer, Serializer};

pub fn serialize<S, Balance>(balance: &Balance, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Balance: Copy + Into<NumberOrHex>,
{
Into::<NumberOrHex>::into(*balance).serialize(serializer)
}

pub fn deserialize<'de, D, Balance>(deserializer: D) -> Result<Balance, D::Error>
where
D: Deserializer<'de>,
Balance: TryFrom<NumberOrHex>,
{
Balance::try_from(NumberOrHex::deserialize(deserializer)?)
.map_err(|_| D::Error::custom("Cannot decode NumberOrHex to Balance"))
}
}
2 changes: 1 addition & 1 deletion frame/contracts/fixtures/destroy_and_transfer.wat
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
))
(import "env" "memory" (memory 1 1))

;; [0, 8) Endowment to send when creating contract.
;; [0, 8) value to send when creating contract.
(data (i32.const 0) "\00\00\01")

;; [8, 16) Value to send when calling contract.
Expand Down
Loading

0 comments on commit b986852

Please sign in to comment.