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

contracts: Add storage deposits #10082

Merged
merged 45 commits into from
Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
27599f9
Frame no longer needs to be mutable (refactoring artifact)
athei Sep 16, 2021
d3abb6f
Remove Contract/Tombstone deposit
athei Sep 17, 2021
8b92865
Add StorageMeter
athei Sep 20, 2021
b91e08a
cargo fmt
athei Nov 7, 2021
bc06dbd
Fix weight annotation
athei Nov 7, 2021
a60e70a
Merge branch 'master' into at-storage-deposit
athei Nov 19, 2021
7d76812
Merge branch 'master' of https://github.com/paritytech/substrate into…
Nov 19, 2021
77015a2
cargo run --quiet --release --features=runtime-benchmarks --manifest-…
Nov 19, 2021
a509d78
Merge branch 'master' into at-storage-deposit
athei Nov 21, 2021
69a1793
Simplify keep check for contract accounts
athei Nov 21, 2021
8f5d92b
Remove unused imports and functions
athei Nov 21, 2021
075a013
Merge branch 'master' into at-storage-deposit
athei Nov 25, 2021
0929c0b
Rename storage_limit to storage_deposit_limit
athei Nov 25, 2021
6489f7f
cargo fmt
athei Nov 25, 2021
4d0bb09
Fix typo
athei Nov 26, 2021
2b1723b
Finish up rename of storage_limit
athei Nov 26, 2021
a8f24e8
Fix rpc tests
athei Nov 26, 2021
23474cf
Make use of `StorageDepositLimitTooHigh`
athei Nov 26, 2021
38bb5c2
Add tests and fix bugs discovered by tests
athei Nov 26, 2021
a997978
Merge branch 'master' into at-storage-deposit
athei Nov 28, 2021
1ea021b
Add storage migration
athei Nov 28, 2021
0420871
Don't use u128 in RPC
athei Nov 29, 2021
23674c9
Merge branch 'master' into at-storage-deposit
athei Nov 29, 2021
7887161
Fix weight of migration
athei Nov 29, 2021
8a10478
Rename `endowment` to `value`
athei Nov 29, 2021
434f487
Fix bug where contract couldn't get funded by a storage deposit
athei Nov 30, 2021
bc6c8bf
Merge branch 'master' into at-storage-deposit
athei Dec 1, 2021
bddf165
Apply suggestions from code review
athei Dec 2, 2021
e23c494
Remove unused `fn storage_meter`
athei Dec 2, 2021
5ccd491
Fix copy pasta doc error
athei Dec 2, 2021
f878644
Import `MaxEncodeLen` from codec
athei Dec 2, 2021
c428ea9
Beautify RPC trait bounds
athei Dec 2, 2021
05d275b
Add re-instrument behaviour to dispatchable doc
athei Dec 2, 2021
450cd68
Merge branch 'master' into at-storage-deposit
athei Dec 2, 2021
3a1391a
Make sure a account won't be destroyed a refund after a slash
athei Dec 3, 2021
cee32d3
Merge branch 'master' into at-storage-deposit
athei Dec 3, 2021
c020184
Merge branch 'master' into at-storage-deposit
athei Dec 6, 2021
d6e3295
Apply suggestions from code review
athei Dec 7, 2021
9435231
Update `Storage::write` docs
athei Dec 7, 2021
18de747
Improve doc
athei Dec 7, 2021
9d92ccb
Remove superflous conditional
athei Dec 7, 2021
a3e32a5
Merge branch 'master' into at-storage-deposit
athei Dec 7, 2021
0fdf0cd
Typos
athei Dec 7, 2021
e09c89e
Remove superflous clone (refactoring artifact)
athei Dec 7, 2021
a92d39d
Apply suggestions from code review
athei Dec 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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