mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-21 01:41:03 +00:00
contracts: Don't rely on reserved balances keeping an account alive (#13369)
* Move storage deposits to their own account * Take ed for contract's account from origin * Apply suggestions from code review Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com> Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * Update stale docs * Use 16 bytes prefix for address derivation * Update frame/contracts/src/address.rs Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com> * Fix merge * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * Update frame/contracts/primitives/src/lib.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> --------- Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com> Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
292e5ee4e7
commit
330484bb19
@@ -16,27 +16,11 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
(func $current_balance (param $sp i32) (result i64)
|
|
||||||
(i32.store
|
|
||||||
(i32.sub (get_local $sp) (i32.const 16))
|
|
||||||
(i32.const 8)
|
|
||||||
)
|
|
||||||
(call $seal_balance
|
|
||||||
(i32.sub (get_local $sp) (i32.const 8))
|
|
||||||
(i32.sub (get_local $sp) (i32.const 16))
|
|
||||||
)
|
|
||||||
(call $assert
|
|
||||||
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 16))) (i32.const 8))
|
|
||||||
)
|
|
||||||
(i64.load (i32.sub (get_local $sp) (i32.const 8)))
|
|
||||||
)
|
|
||||||
|
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
|
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
(local $sp i32)
|
(local $sp i32)
|
||||||
(local $exit_code i32)
|
(local $exit_code i32)
|
||||||
(local $balance i64)
|
|
||||||
|
|
||||||
;; Length of the buffer
|
;; Length of the buffer
|
||||||
(i32.store (i32.const 20) (i32.const 32))
|
(i32.store (i32.const 20) (i32.const 32))
|
||||||
@@ -54,9 +38,6 @@
|
|||||||
|
|
||||||
;; Read current balance into local variable.
|
;; Read current balance into local variable.
|
||||||
(set_local $sp (i32.const 1024))
|
(set_local $sp (i32.const 1024))
|
||||||
(set_local $balance
|
|
||||||
(call $current_balance (get_local $sp))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Fail to deploy the contract since it returns a non-zero exit status.
|
;; Fail to deploy the contract since it returns a non-zero exit status.
|
||||||
(set_local $exit_code
|
(set_local $exit_code
|
||||||
@@ -82,11 +63,6 @@
|
|||||||
(i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
|
(i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Check that balance has not changed.
|
|
||||||
(call $assert
|
|
||||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Fail to deploy the contract due to insufficient gas.
|
;; Fail to deploy the contract due to insufficient gas.
|
||||||
(set_local $exit_code
|
(set_local $exit_code
|
||||||
(call $seal_instantiate
|
(call $seal_instantiate
|
||||||
@@ -112,11 +88,6 @@
|
|||||||
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
|
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Check that balance has not changed.
|
|
||||||
(call $assert
|
|
||||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Length of the output buffer
|
;; Length of the output buffer
|
||||||
(i32.store
|
(i32.store
|
||||||
(i32.sub (get_local $sp) (i32.const 4))
|
(i32.sub (get_local $sp) (i32.const 4))
|
||||||
@@ -153,14 +124,6 @@
|
|||||||
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 4))) (i32.const 32))
|
(i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 4))) (i32.const 32))
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Check that balance has been deducted.
|
|
||||||
(set_local $balance
|
|
||||||
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
|
|
||||||
)
|
|
||||||
(call $assert
|
|
||||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Zero out destination buffer of output
|
;; Zero out destination buffer of output
|
||||||
(i32.store
|
(i32.store
|
||||||
(i32.sub (get_local $sp) (i32.const 4))
|
(i32.sub (get_local $sp) (i32.const 4))
|
||||||
@@ -204,11 +167,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Check that balance has not changed.
|
|
||||||
(call $assert
|
|
||||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Fail to call the contract due to insufficient gas.
|
;; Fail to call the contract due to insufficient gas.
|
||||||
(set_local $exit_code
|
(set_local $exit_code
|
||||||
(call $seal_call
|
(call $seal_call
|
||||||
@@ -229,11 +187,6 @@
|
|||||||
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
|
(i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Check that balance has not changed.
|
|
||||||
(call $assert
|
|
||||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Zero out destination buffer of output
|
;; Zero out destination buffer of output
|
||||||
(i32.store
|
(i32.store
|
||||||
(i32.sub (get_local $sp) (i32.const 4))
|
(i32.sub (get_local $sp) (i32.const 4))
|
||||||
@@ -276,14 +229,6 @@
|
|||||||
(i32.const 0x77665544)
|
(i32.const 0x77665544)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; Check that balance has been deducted.
|
|
||||||
(set_local $balance
|
|
||||||
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
|
|
||||||
)
|
|
||||||
(call $assert
|
|
||||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
(data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.
|
(data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.
|
||||||
|
|||||||
@@ -34,8 +34,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
;; Try to self-destruct by sending full balance to the 0 address.
|
;; Try to self-destruct by sending full balance to the 0 address.
|
||||||
;; All the *free* balance will be send away, which is a valid thing to do
|
;; The call will fail because a contract transfer has a keep alive requirement
|
||||||
;; because the storage deposits will keep the account alive.
|
|
||||||
(call $assert
|
(call $assert
|
||||||
(i32.eq
|
(i32.eq
|
||||||
(call $seal_transfer
|
(call $seal_transfer
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||||
(i32.const 8) ;; Length of the buffer with value to transfer
|
(i32.const 8) ;; Length of the buffer with value to transfer
|
||||||
)
|
)
|
||||||
(i32.const 0) ;; ReturnCode::Success
|
(i32.const 5) ;; ReturnCode::TransferFailed
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,10 +47,12 @@ pub struct ContractResult<R, Balance> {
|
|||||||
/// Additionally, any `seal_call` or `seal_instantiate` 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.
|
/// when a non-zero `gas_limit` argument is supplied.
|
||||||
pub gas_required: Weight,
|
pub gas_required: Weight,
|
||||||
/// How much balance was deposited and reserved during execution in order to pay for storage.
|
/// How much balance was paid by the origin into the contract's deposit account in order to
|
||||||
|
/// pay for storage.
|
||||||
///
|
///
|
||||||
/// The storage deposit is never actually charged from the caller in case of [`Self::result`]
|
/// The storage deposit is never actually charged from the origin in case of [`Self::result`]
|
||||||
/// is `Err`. This is because on error all storage changes are rolled back.
|
/// is `Err`. This is because on error all storage changes are rolled back including the
|
||||||
|
/// payment of the deposit.
|
||||||
pub storage_deposit: StorageDeposit<Balance>,
|
pub storage_deposit: StorageDeposit<Balance>,
|
||||||
/// An optional debug message. This message is only filled when explicitly requested
|
/// An optional debug message. This message is only filled when explicitly requested
|
||||||
/// by the code that calls into the contract. Otherwise it is empty.
|
/// by the code that calls into the contract. Otherwise it is empty.
|
||||||
@@ -159,12 +161,12 @@ pub enum StorageDeposit<Balance> {
|
|||||||
/// The transaction reduced storage consumption.
|
/// The transaction reduced storage consumption.
|
||||||
///
|
///
|
||||||
/// This means that the specified amount of balance was transferred from the involved
|
/// This means that the specified amount of balance was transferred from the involved
|
||||||
/// contracts to the call origin.
|
/// deposit accounts to the origin.
|
||||||
Refund(Balance),
|
Refund(Balance),
|
||||||
/// The transaction increased overall storage usage.
|
/// The transaction increased storage consumption.
|
||||||
///
|
///
|
||||||
/// This means that the specified amount of balance was transferred from the call origin
|
/// This means that the specified amount of balance was transferred from the origin
|
||||||
/// to the contracts involved.
|
/// to the involved deposit accounts.
|
||||||
Charge(Balance),
|
Charge(Balance),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Functions that deal with address derivation.
|
||||||
|
|
||||||
|
use crate::{CodeHash, Config};
|
||||||
|
use codec::{Decode, Encode};
|
||||||
|
use sp_runtime::traits::{Hash, TrailingZeroInput};
|
||||||
|
|
||||||
|
/// Provides the contract address generation method.
|
||||||
|
///
|
||||||
|
/// See [`DefaultAddressGenerator`] for the default implementation.
|
||||||
|
///
|
||||||
|
/// # Note for implementors
|
||||||
|
///
|
||||||
|
/// 1. Make sure that there are no collisions, different inputs never lead to the same output.
|
||||||
|
/// 2. Make sure that the same inputs lead to the same output.
|
||||||
|
pub trait AddressGenerator<T: Config> {
|
||||||
|
/// The address of a contract based on the given instantiate parameters.
|
||||||
|
///
|
||||||
|
/// Changing the formular for an already deployed chain is fine as long as no collisons
|
||||||
|
/// with the old formular. Changes only affect existing contracts.
|
||||||
|
fn contract_address(
|
||||||
|
deploying_address: &T::AccountId,
|
||||||
|
code_hash: &CodeHash<T>,
|
||||||
|
input_data: &[u8],
|
||||||
|
salt: &[u8],
|
||||||
|
) -> T::AccountId;
|
||||||
|
|
||||||
|
/// The address of the deposit account of `contract_addr`.
|
||||||
|
///
|
||||||
|
/// The address is generated once on instantiation and then stored in the contracts
|
||||||
|
/// metadata. Hence changes do only affect newly created contracts.
|
||||||
|
fn deposit_address(contract_addr: &T::AccountId) -> T::AccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default address generator.
|
||||||
|
///
|
||||||
|
/// This is the default address generator used by contract instantiation. Its result
|
||||||
|
/// is only dependent on its inputs. It can therefore be used to reliably predict the
|
||||||
|
/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There
|
||||||
|
/// is no CREATE equivalent because CREATE2 is strictly more powerful.
|
||||||
|
/// Formula:
|
||||||
|
/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
|
||||||
|
pub struct DefaultAddressGenerator;
|
||||||
|
|
||||||
|
impl<T: Config> AddressGenerator<T> for DefaultAddressGenerator {
|
||||||
|
/// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
|
||||||
|
fn contract_address(
|
||||||
|
deploying_address: &T::AccountId,
|
||||||
|
code_hash: &CodeHash<T>,
|
||||||
|
input_data: &[u8],
|
||||||
|
salt: &[u8],
|
||||||
|
) -> T::AccountId {
|
||||||
|
let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt)
|
||||||
|
.using_encoded(T::Hashing::hash);
|
||||||
|
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
||||||
|
.expect("infinite length input; no invalid inputs for type; qed")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formula: `hash("contract_depo_v1" ++ contract_addr)`
|
||||||
|
fn deposit_address(contract_addr: &T::AccountId) -> T::AccountId {
|
||||||
|
let entropy = (b"contract_depo_v1", contract_addr).using_encoded(T::Hashing::hash);
|
||||||
|
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
||||||
|
.expect("infinite length input; no invalid inputs for type; qed")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,6 @@ use self::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
exec::{AccountIdOf, FixSizedKey, VarSizedKey},
|
exec::{AccountIdOf, FixSizedKey, VarSizedKey},
|
||||||
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
|
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
|
||||||
storage::Storage,
|
|
||||||
wasm::CallFlags,
|
wasm::CallFlags,
|
||||||
Pallet as Contracts, *,
|
Pallet as Contracts, *,
|
||||||
};
|
};
|
||||||
@@ -133,14 +132,8 @@ where
|
|||||||
fn store(&self, items: &Vec<(FixSizedKey, Vec<u8>)>) -> Result<(), &'static str> {
|
fn store(&self, items: &Vec<(FixSizedKey, Vec<u8>)>) -> Result<(), &'static str> {
|
||||||
let info = self.info()?;
|
let info = self.info()?;
|
||||||
for item in items {
|
for item in items {
|
||||||
Storage::<T>::write(
|
info.write(&item.0 as &FixSizedKey, Some(item.1.clone()), None, false)
|
||||||
&info.trie_id,
|
.map_err(|_| "Failed to write storage to restoration dest")?;
|
||||||
&item.0 as &FixSizedKey,
|
|
||||||
Some(item.1.clone()),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.map_err(|_| "Failed to write storage to restoration dest")?;
|
|
||||||
}
|
}
|
||||||
<ContractInfoOf<T>>::insert(&self.account_id, info);
|
<ContractInfoOf<T>>::insert(&self.account_id, info);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -207,7 +200,7 @@ benchmarks! {
|
|||||||
// The base weight consumed on processing contracts deletion queue.
|
// The base weight consumed on processing contracts deletion queue.
|
||||||
#[pov_mode = Measured]
|
#[pov_mode = Measured]
|
||||||
on_process_deletion_queue_batch {}: {
|
on_process_deletion_queue_batch {}: {
|
||||||
Storage::<T>::process_deletion_queue_batch(Weight::MAX)
|
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_meta]
|
#[skip_meta]
|
||||||
@@ -215,9 +208,9 @@ benchmarks! {
|
|||||||
on_initialize_per_trie_key {
|
on_initialize_per_trie_key {
|
||||||
let k in 0..1024;
|
let k in 0..1024;
|
||||||
let instance = Contract::<T>::with_storage(WasmModule::dummy(), k, T::Schedule::get().limits.payload_len)?;
|
let instance = Contract::<T>::with_storage(WasmModule::dummy(), k, T::Schedule::get().limits.payload_len)?;
|
||||||
Storage::<T>::queue_trie_for_deletion(&instance.info()?)?;
|
instance.info()?.queue_trie_for_deletion()?;
|
||||||
}: {
|
}: {
|
||||||
Storage::<T>::process_deletion_queue_batch(Weight::MAX)
|
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pov_mode = Measured]
|
#[pov_mode = Measured]
|
||||||
@@ -225,11 +218,11 @@ benchmarks! {
|
|||||||
let q in 0..1024.min(T::DeletionQueueDepth::get());
|
let q in 0..1024.min(T::DeletionQueueDepth::get());
|
||||||
for i in 0 .. q {
|
for i in 0 .. q {
|
||||||
let instance = Contract::<T>::with_index(i, WasmModule::dummy(), vec![])?;
|
let instance = Contract::<T>::with_index(i, WasmModule::dummy(), vec![])?;
|
||||||
Storage::<T>::queue_trie_for_deletion(&instance.info()?)?;
|
instance.info()?.queue_trie_for_deletion()?;
|
||||||
ContractInfoOf::<T>::remove(instance.account_id);
|
ContractInfoOf::<T>::remove(instance.account_id);
|
||||||
}
|
}
|
||||||
}: {
|
}: {
|
||||||
Storage::<T>::process_deletion_queue_batch(Weight::MAX)
|
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This benchmarks the additional weight that is charged when a contract is executed the
|
// This benchmarks the additional weight that is charged when a contract is executed the
|
||||||
@@ -293,18 +286,16 @@ benchmarks! {
|
|||||||
let addr = Contracts::<T>::contract_address(&caller, &hash, &input, &salt);
|
let addr = Contracts::<T>::contract_address(&caller, &hash, &input, &salt);
|
||||||
}: _(origin, value, Weight::MAX, None, code, input, salt)
|
}: _(origin, value, Weight::MAX, None, code, input, salt)
|
||||||
verify {
|
verify {
|
||||||
// the contract itself does not trigger any reserves
|
let deposit_account = Contract::<T>::address_info(&addr)?.deposit_account().clone();
|
||||||
let deposit = T::Currency::reserved_balance(&addr);
|
let deposit = T::Currency::free_balance(&deposit_account);
|
||||||
// uploading the code reserves some balance in the callers account
|
// uploading the code reserves some balance in the callers account
|
||||||
let code_deposit = T::Currency::reserved_balance(&caller);
|
let code_deposit = T::Currency::reserved_balance(&caller);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
T::Currency::free_balance(&caller),
|
T::Currency::free_balance(&caller),
|
||||||
caller_funding::<T>() - value - deposit - code_deposit,
|
caller_funding::<T>() - value - deposit - code_deposit - Pallet::<T>::min_balance(),
|
||||||
);
|
);
|
||||||
// contract has the full value
|
// contract has the full value
|
||||||
assert_eq!(T::Currency::free_balance(&addr), value);
|
assert_eq!(T::Currency::free_balance(&addr), value + Pallet::<T>::min_balance());
|
||||||
// instantiate should leave a contract
|
|
||||||
Contract::<T>::address_info(&addr)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate uses a dummy contract constructor to measure the overhead of the instantiate.
|
// Instantiate uses a dummy contract constructor to measure the overhead of the instantiate.
|
||||||
@@ -325,14 +316,15 @@ benchmarks! {
|
|||||||
Contracts::<T>::store_code_raw(code, caller.clone())?;
|
Contracts::<T>::store_code_raw(code, caller.clone())?;
|
||||||
}: _(origin, value, Weight::MAX, None, hash, input, salt)
|
}: _(origin, value, Weight::MAX, None, hash, input, salt)
|
||||||
verify {
|
verify {
|
||||||
// the contract itself does not trigger any reserves
|
let deposit_account = Contract::<T>::address_info(&addr)?.deposit_account().clone();
|
||||||
let deposit = T::Currency::reserved_balance(&addr);
|
let deposit = T::Currency::free_balance(&deposit_account);
|
||||||
// value was removed from the caller
|
// value was removed from the caller
|
||||||
assert_eq!(T::Currency::free_balance(&caller), caller_funding::<T>() - value - deposit);
|
assert_eq!(
|
||||||
|
T::Currency::free_balance(&caller),
|
||||||
|
caller_funding::<T>() - value - deposit - Pallet::<T>::min_balance(),
|
||||||
|
);
|
||||||
// contract has the full value
|
// contract has the full value
|
||||||
assert_eq!(T::Currency::free_balance(&addr), value);
|
assert_eq!(T::Currency::free_balance(&addr), value + Pallet::<T>::min_balance());
|
||||||
// instantiate should leave a contract
|
|
||||||
Contract::<T>::address_info(&addr)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We just call a dummy contract to measure the overhead of the call extrinsic.
|
// We just call a dummy contract to measure the overhead of the call extrinsic.
|
||||||
@@ -348,18 +340,19 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::with_caller(
|
let instance = Contract::<T>::with_caller(
|
||||||
whitelisted_caller(), WasmModule::dummy(), vec![],
|
whitelisted_caller(), WasmModule::dummy(), vec![],
|
||||||
)?;
|
)?;
|
||||||
|
let deposit_account = instance.info()?.deposit_account().clone();
|
||||||
let value = Pallet::<T>::min_balance();
|
let value = Pallet::<T>::min_balance();
|
||||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||||
let callee = instance.addr.clone();
|
let callee = instance.addr.clone();
|
||||||
let before = T::Currency::free_balance(&instance.account_id);
|
let before = T::Currency::free_balance(&instance.account_id);
|
||||||
|
let before_deposit = T::Currency::free_balance(&deposit_account);
|
||||||
}: _(origin, callee, value, Weight::MAX, None, data)
|
}: _(origin, callee, value, Weight::MAX, None, data)
|
||||||
verify {
|
verify {
|
||||||
// the contract itself does not trigger any reserves
|
let deposit = T::Currency::free_balance(&deposit_account);
|
||||||
let deposit = T::Currency::reserved_balance(&instance.account_id);
|
|
||||||
// value and value transfered via call should be removed from the caller
|
// value and value transfered via call should be removed from the caller
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
T::Currency::free_balance(&instance.caller),
|
T::Currency::free_balance(&instance.caller),
|
||||||
caller_funding::<T>() - instance.value - value - deposit,
|
caller_funding::<T>() - instance.value - value - deposit - Pallet::<T>::min_balance(),
|
||||||
);
|
);
|
||||||
// contract should have received the value
|
// contract should have received the value
|
||||||
assert_eq!(T::Currency::free_balance(&instance.account_id), before + value);
|
assert_eq!(T::Currency::free_balance(&instance.account_id), before + value);
|
||||||
@@ -798,14 +791,16 @@ benchmarks! {
|
|||||||
});
|
});
|
||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||||
|
let deposit_account = instance.info()?.deposit_account().clone();
|
||||||
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
|
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
|
||||||
assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::<T>::min_balance());
|
assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::<T>::min_balance() * 2u32.into());
|
||||||
assert_ne!(T::Currency::reserved_balance(&instance.account_id), 0u32.into());
|
assert_ne!(T::Currency::free_balance(&deposit_account), 0u32.into());
|
||||||
}: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![])
|
}: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![])
|
||||||
verify {
|
verify {
|
||||||
if r > 0 {
|
if r > 0 {
|
||||||
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
|
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
|
||||||
assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::<T>::min_balance());
|
assert_eq!(T::Currency::total_balance(&deposit_account), 0u32.into());
|
||||||
|
assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::<T>::min_balance() * 2u32.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1039,8 +1034,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
@@ -1088,8 +1082,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
@@ -1137,8 +1130,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
@@ -1188,8 +1180,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
@@ -1236,8 +1227,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
@@ -1291,8 +1281,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
@@ -1346,8 +1335,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
@@ -1396,8 +1384,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
@@ -1444,8 +1431,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
@@ -1499,8 +1485,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![]),
|
Some(vec![]),
|
||||||
None,
|
None,
|
||||||
@@ -1554,8 +1539,7 @@ benchmarks! {
|
|||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
let info = instance.info()?;
|
let info = instance.info()?;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
Storage::<T>::write(
|
info.write(
|
||||||
&info.trie_id,
|
|
||||||
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
&VarSizedKey::<T>::try_from(key).map_err(|e| "Key has wrong length")?,
|
||||||
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len
|
||||||
None,
|
None,
|
||||||
@@ -1884,7 +1868,7 @@ benchmarks! {
|
|||||||
.. Default::default()
|
.. Default::default()
|
||||||
});
|
});
|
||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
instance.set_balance(value * (r * API_BENCHMARK_BATCH_SIZE + 1).into());
|
instance.set_balance((value + Pallet::<T>::min_balance()) * (r * API_BENCHMARK_BATCH_SIZE + 1).into());
|
||||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||||
let callee = instance.addr.clone();
|
let callee = instance.addr.clone();
|
||||||
let addresses = hashes
|
let addresses = hashes
|
||||||
@@ -1997,7 +1981,7 @@ benchmarks! {
|
|||||||
.. Default::default()
|
.. Default::default()
|
||||||
});
|
});
|
||||||
let instance = Contract::<T>::new(code, vec![])?;
|
let instance = Contract::<T>::new(code, vec![])?;
|
||||||
instance.set_balance(value * (API_BENCHMARK_BATCH_SIZE + 1).into());
|
instance.set_balance((value + Pallet::<T>::min_balance()) * (API_BENCHMARK_BATCH_SIZE + 1).into());
|
||||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||||
|
|
||||||
@@ -3113,8 +3097,8 @@ benchmarks! {
|
|||||||
{
|
{
|
||||||
let weight_limit = T::DeletionWeightLimit::get();
|
let weight_limit = T::DeletionWeightLimit::get();
|
||||||
let max_queue_depth = T::DeletionQueueDepth::get() as usize;
|
let max_queue_depth = T::DeletionQueueDepth::get() as usize;
|
||||||
let empty_queue_throughput = Storage::<T>::deletion_budget(0, weight_limit);
|
let empty_queue_throughput = ContractInfo::<T>::deletion_budget(0, weight_limit);
|
||||||
let full_queue_throughput = Storage::<T>::deletion_budget(max_queue_depth, weight_limit);
|
let full_queue_throughput = ContractInfo::<T>::deletion_budget(max_queue_depth, weight_limit);
|
||||||
println!("{:#?}", Schedule::<T>::default());
|
println!("{:#?}", Schedule::<T>::default());
|
||||||
println!("###############################################");
|
println!("###############################################");
|
||||||
println!("Lazy deletion weight per key: {}", empty_queue_throughput.0);
|
println!("Lazy deletion weight per key: {}", empty_queue_throughput.0);
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gas::GasMeter,
|
gas::GasMeter,
|
||||||
storage::{self, Storage, WriteOutcome},
|
storage::{self, DepositAccount, WriteOutcome},
|
||||||
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error,
|
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error,
|
||||||
Event, Nonce, Pallet as Contracts, Schedule,
|
Event, Nonce, Pallet as Contracts, Schedule, System,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
crypto::ecdsa::ECDSAExt,
|
crypto::ecdsa::ECDSAExt,
|
||||||
@@ -492,7 +492,7 @@ enum CachedContract<T: Config> {
|
|||||||
///
|
///
|
||||||
/// In this case a reload is neither allowed nor possible. Please note that recursive
|
/// In this case a reload is neither allowed nor possible. Please note that recursive
|
||||||
/// calls cannot remove a contract as this is checked and denied.
|
/// calls cannot remove a contract as this is checked and denied.
|
||||||
Terminated,
|
Terminated(DepositAccount<T>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> CachedContract<T> {
|
impl<T: Config> CachedContract<T> {
|
||||||
@@ -513,6 +513,15 @@ impl<T: Config> CachedContract<T> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `Some` iff the contract is not `Cached::Invalidated`.
|
||||||
|
fn deposit_account(&self) -> Option<&DepositAccount<T>> {
|
||||||
|
match self {
|
||||||
|
CachedContract::Cached(contract) => Some(contract.deposit_account()),
|
||||||
|
CachedContract::Terminated(deposit_account) => Some(&deposit_account),
|
||||||
|
CachedContract::Invalidated => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> Frame<T> {
|
impl<T: Config> Frame<T> {
|
||||||
@@ -591,7 +600,9 @@ impl<T: Config> CachedContract<T> {
|
|||||||
/// Terminate and return the contract info.
|
/// Terminate and return the contract info.
|
||||||
fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo<T> {
|
fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo<T> {
|
||||||
self.load(account_id);
|
self.load(account_id);
|
||||||
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated))
|
let contract = get_cached_or_panic_after_load!(self);
|
||||||
|
let deposit_account = contract.deposit_account().clone();
|
||||||
|
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated(deposit_account)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,9 +762,7 @@ where
|
|||||||
input_data,
|
input_data,
|
||||||
salt,
|
salt,
|
||||||
);
|
);
|
||||||
let trie_id = Storage::<T>::generate_trie_id(&account_id, nonce);
|
let contract = ContractInfo::new(&account_id, nonce, *executable.code_hash())?;
|
||||||
let contract =
|
|
||||||
Storage::<T>::new_contract(&account_id, trie_id, *executable.code_hash())?;
|
|
||||||
(
|
(
|
||||||
account_id,
|
account_id,
|
||||||
contract,
|
contract,
|
||||||
@@ -873,7 +882,7 @@ where
|
|||||||
match (entry_point, delegated_code_hash) {
|
match (entry_point, delegated_code_hash) {
|
||||||
(ExportedFunction::Constructor, _) => {
|
(ExportedFunction::Constructor, _) => {
|
||||||
// It is not allowed to terminate a contract inside its constructor.
|
// It is not allowed to terminate a contract inside its constructor.
|
||||||
if matches!(frame.contract_info, CachedContract::Terminated) {
|
if matches!(frame.contract_info, CachedContract::Terminated(_)) {
|
||||||
return Err(Error::<T>::TerminatedInConstructor.into())
|
return Err(Error::<T>::TerminatedInConstructor.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,11 +971,21 @@ where
|
|||||||
|
|
||||||
// Record the storage meter changes of the nested call into the parent meter.
|
// Record the storage meter changes of the nested call into the parent meter.
|
||||||
// If the dropped frame's contract wasn't terminated we update the deposit counter
|
// If the dropped frame's contract wasn't terminated we update the deposit counter
|
||||||
// in its contract info. The load is necessary to to pull it from storage in case
|
// in its contract info. The load is necessary to pull it from storage in case
|
||||||
// it was invalidated.
|
// it was invalidated.
|
||||||
frame.contract_info.load(account_id);
|
frame.contract_info.load(account_id);
|
||||||
|
let deposit_account = frame
|
||||||
|
.contract_info
|
||||||
|
.deposit_account()
|
||||||
|
.expect(
|
||||||
|
"Is only `None` when the info is invalidated.
|
||||||
|
We just re-loaded from storage which either makes the state `Cached` or `Terminated`.
|
||||||
|
qed",
|
||||||
|
)
|
||||||
|
.clone();
|
||||||
let mut contract = frame.contract_info.into_contract();
|
let mut contract = frame.contract_info.into_contract();
|
||||||
prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut());
|
prev.nested_storage
|
||||||
|
.absorb(frame.nested_storage, deposit_account, contract.as_mut());
|
||||||
|
|
||||||
// In case the contract wasn't terminated we need to persist changes made to it.
|
// In case the contract wasn't terminated we need to persist changes made to it.
|
||||||
if let Some(contract) = contract {
|
if let Some(contract) = contract {
|
||||||
@@ -1001,10 +1020,14 @@ where
|
|||||||
if !persist {
|
if !persist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let deposit_account = self.first_frame.contract_info.deposit_account().expect(
|
||||||
|
"Is only `None` when the info is invalidated. The first frame can't be invalidated.
|
||||||
|
qed",
|
||||||
|
).clone();
|
||||||
let mut contract = self.first_frame.contract_info.as_contract();
|
let mut contract = self.first_frame.contract_info.as_contract();
|
||||||
self.storage_meter.absorb(
|
self.storage_meter.absorb(
|
||||||
mem::take(&mut self.first_frame.nested_storage),
|
mem::take(&mut self.first_frame.nested_storage),
|
||||||
&self.first_frame.account_id,
|
deposit_account,
|
||||||
contract.as_deref_mut(),
|
contract.as_deref_mut(),
|
||||||
);
|
);
|
||||||
if let Some(contract) = contract {
|
if let Some(contract) = contract {
|
||||||
@@ -1182,19 +1205,21 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
|
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
|
||||||
|
use frame_support::traits::fungible::Inspect;
|
||||||
if self.is_recursive() {
|
if self.is_recursive() {
|
||||||
return Err(Error::<T>::TerminatedWhileReentrant.into())
|
return Err(Error::<T>::TerminatedWhileReentrant.into())
|
||||||
}
|
}
|
||||||
let frame = self.top_frame_mut();
|
let frame = self.top_frame_mut();
|
||||||
let info = frame.terminate();
|
let info = frame.terminate();
|
||||||
frame.nested_storage.terminate(&info);
|
frame.nested_storage.terminate(&info);
|
||||||
Storage::<T>::queue_trie_for_deletion(&info)?;
|
System::<T>::dec_consumers(&frame.account_id);
|
||||||
<Stack<'a, T, E>>::transfer(
|
T::Currency::transfer(
|
||||||
ExistenceRequirement::AllowDeath,
|
|
||||||
&frame.account_id,
|
&frame.account_id,
|
||||||
beneficiary,
|
beneficiary,
|
||||||
T::Currency::free_balance(&frame.account_id),
|
T::Currency::reducible_balance(&frame.account_id, false),
|
||||||
|
ExistenceRequirement::AllowDeath,
|
||||||
)?;
|
)?;
|
||||||
|
info.queue_trie_for_deletion()?;
|
||||||
ContractInfoOf::<T>::remove(&frame.account_id);
|
ContractInfoOf::<T>::remove(&frame.account_id);
|
||||||
E::remove_user(info.code_hash);
|
E::remove_user(info.code_hash);
|
||||||
Contracts::<T>::deposit_event(
|
Contracts::<T>::deposit_event(
|
||||||
@@ -1212,19 +1237,19 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_storage(&mut self, key: &FixSizedKey) -> Option<Vec<u8>> {
|
fn get_storage(&mut self, key: &FixSizedKey) -> Option<Vec<u8>> {
|
||||||
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
|
self.top_frame_mut().contract_info().read(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_storage_transparent(&mut self, key: &VarSizedKey<T>) -> Option<Vec<u8>> {
|
fn get_storage_transparent(&mut self, key: &VarSizedKey<T>) -> Option<Vec<u8>> {
|
||||||
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
|
self.top_frame_mut().contract_info().read(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_storage_size(&mut self, key: &FixSizedKey) -> Option<u32> {
|
fn get_storage_size(&mut self, key: &FixSizedKey) -> Option<u32> {
|
||||||
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
|
self.top_frame_mut().contract_info().size(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_storage_size_transparent(&mut self, key: &VarSizedKey<T>) -> Option<u32> {
|
fn get_storage_size_transparent(&mut self, key: &VarSizedKey<T>) -> Option<u32> {
|
||||||
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
|
self.top_frame_mut().contract_info().size(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_storage(
|
fn set_storage(
|
||||||
@@ -1234,8 +1259,7 @@ where
|
|||||||
take_old: bool,
|
take_old: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError> {
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
let frame = self.top_frame_mut();
|
let frame = self.top_frame_mut();
|
||||||
Storage::<T>::write(
|
frame.contract_info.get(&frame.account_id).write(
|
||||||
&frame.contract_info.get(&frame.account_id).trie_id,
|
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
Some(&mut frame.nested_storage),
|
Some(&mut frame.nested_storage),
|
||||||
@@ -1250,8 +1274,7 @@ where
|
|||||||
take_old: bool,
|
take_old: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError> {
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
let frame = self.top_frame_mut();
|
let frame = self.top_frame_mut();
|
||||||
Storage::<T>::write(
|
frame.contract_info.get(&frame.account_id).write(
|
||||||
&frame.contract_info.get(&frame.account_id).trie_id,
|
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
Some(&mut frame.nested_storage),
|
Some(&mut frame.nested_storage),
|
||||||
@@ -1438,7 +1461,6 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
exec::ExportedFunction::*,
|
exec::ExportedFunction::*,
|
||||||
gas::GasMeter,
|
gas::GasMeter,
|
||||||
storage::Storage,
|
|
||||||
tests::{
|
tests::{
|
||||||
test_utils::{get_balance, hash, place_contract, set_balance},
|
test_utils::{get_balance, hash, place_contract, set_balance},
|
||||||
ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB,
|
ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB,
|
||||||
@@ -1891,9 +1913,8 @@ mod tests {
|
|||||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
let executable =
|
let executable =
|
||||||
MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
|
MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
|
||||||
set_balance(&ALICE, min_balance * 1000);
|
set_balance(&ALICE, min_balance * 10_000);
|
||||||
let mut storage_meter =
|
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, min_balance).unwrap();
|
||||||
storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap();
|
|
||||||
|
|
||||||
let result = MockStack::run_instantiate(
|
let result = MockStack::run_instantiate(
|
||||||
ALICE,
|
ALICE,
|
||||||
@@ -2231,7 +2252,7 @@ mod tests {
|
|||||||
// Check that the newly created account has the expected code hash and
|
// Check that the newly created account has the expected code hash and
|
||||||
// there are instantiation event.
|
// there are instantiation event.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(),
|
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
|
||||||
dummy_ch
|
dummy_ch
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2273,7 +2294,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Check that the account has not been created.
|
// Check that the account has not been created.
|
||||||
assert!(Storage::<Test>::code_hash(&instantiated_contract_address).is_none());
|
assert!(ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).is_none());
|
||||||
assert!(events().is_empty());
|
assert!(events().is_empty());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2332,7 +2353,7 @@ mod tests {
|
|||||||
// Check that the newly created account has the expected code hash and
|
// Check that the newly created account has the expected code hash and
|
||||||
// there are instantiation event.
|
// there are instantiation event.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(),
|
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
|
||||||
dummy_ch
|
dummy_ch
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2374,7 +2395,7 @@ mod tests {
|
|||||||
set_balance(&ALICE, 1000);
|
set_balance(&ALICE, 1000);
|
||||||
set_balance(&BOB, 100);
|
set_balance(&BOB, 100);
|
||||||
place_contract(&BOB, instantiator_ch);
|
place_contract(&BOB, instantiator_ch);
|
||||||
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 0).unwrap();
|
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(200), 0).unwrap();
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
MockStack::run_call(
|
MockStack::run_call(
|
||||||
@@ -2409,8 +2430,8 @@ mod tests {
|
|||||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
let executable =
|
let executable =
|
||||||
MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
|
MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
|
||||||
set_balance(&ALICE, 1000);
|
set_balance(&ALICE, 10_000);
|
||||||
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 100).unwrap();
|
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 100).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MockStack::run_instantiate(
|
MockStack::run_instantiate(
|
||||||
@@ -2494,9 +2515,8 @@ mod tests {
|
|||||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||||
let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap();
|
let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap();
|
||||||
set_balance(&ALICE, min_balance * 1000);
|
set_balance(&ALICE, min_balance * 10_000);
|
||||||
let mut storage_meter =
|
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, min_balance).unwrap();
|
||||||
storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap();
|
|
||||||
|
|
||||||
let result = MockStack::run_instantiate(
|
let result = MockStack::run_instantiate(
|
||||||
ALICE,
|
ALICE,
|
||||||
@@ -2902,10 +2922,9 @@ mod tests {
|
|||||||
MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap();
|
MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap();
|
||||||
let succ_succ_executable =
|
let succ_succ_executable =
|
||||||
MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap();
|
MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap();
|
||||||
set_balance(&ALICE, min_balance * 1000);
|
set_balance(&ALICE, min_balance * 10_000);
|
||||||
let mut storage_meter =
|
let mut storage_meter =
|
||||||
storage::meter::Meter::new(&ALICE, Some(min_balance * 500), min_balance * 100)
|
storage::meter::Meter::new(&ALICE, None, min_balance * 100).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
MockStack::run_instantiate(
|
MockStack::run_instantiate(
|
||||||
ALICE,
|
ALICE,
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod gas;
|
mod gas;
|
||||||
|
mod address;
|
||||||
mod benchmarking;
|
mod benchmarking;
|
||||||
mod exec;
|
mod exec;
|
||||||
mod migration;
|
mod migration;
|
||||||
@@ -101,11 +102,11 @@ mod tests;
|
|||||||
use crate::{
|
use crate::{
|
||||||
exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack},
|
exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack},
|
||||||
gas::GasMeter,
|
gas::GasMeter,
|
||||||
storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract, Storage},
|
storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract},
|
||||||
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
|
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
|
||||||
weights::WeightInfo,
|
weights::WeightInfo,
|
||||||
};
|
};
|
||||||
use codec::{Codec, Decode, Encode, HasCompact};
|
use codec::{Codec, Encode, HasCompact};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo},
|
dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo},
|
||||||
ensure,
|
ensure,
|
||||||
@@ -124,10 +125,11 @@ use pallet_contracts_primitives::{
|
|||||||
};
|
};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use smallvec::Array;
|
use smallvec::Array;
|
||||||
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup, TrailingZeroInput};
|
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
|
||||||
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
|
address::{AddressGenerator, DefaultAddressGenerator},
|
||||||
exec::{Frame, VarSizedKey as StorageKey},
|
exec::{Frame, VarSizedKey as StorageKey},
|
||||||
migration::Migration,
|
migration::Migration,
|
||||||
pallet::*,
|
pallet::*,
|
||||||
@@ -155,49 +157,6 @@ type DebugBufferVec<T> = BoundedVec<u8, <T as Config>::MaxDebugBufferLen>;
|
|||||||
/// that this value makes sense for a memory location or length.
|
/// that this value makes sense for a memory location or length.
|
||||||
const SENTINEL: u32 = u32::MAX;
|
const SENTINEL: u32 = u32::MAX;
|
||||||
|
|
||||||
/// Provides the contract address generation method.
|
|
||||||
///
|
|
||||||
/// See [`DefaultAddressGenerator`] for the default implementation.
|
|
||||||
pub trait AddressGenerator<T: Config> {
|
|
||||||
/// Generate the address of a contract based on the given instantiate parameters.
|
|
||||||
///
|
|
||||||
/// # Note for implementors
|
|
||||||
/// 1. Make sure that there are no collisions, different inputs never lead to the same output.
|
|
||||||
/// 2. Make sure that the same inputs lead to the same output.
|
|
||||||
/// 3. Changing the implementation through a runtime upgrade without a proper storage migration
|
|
||||||
/// would lead to catastrophic misbehavior.
|
|
||||||
fn generate_address(
|
|
||||||
deploying_address: &T::AccountId,
|
|
||||||
code_hash: &CodeHash<T>,
|
|
||||||
input_data: &[u8],
|
|
||||||
salt: &[u8],
|
|
||||||
) -> T::AccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default address generator.
|
|
||||||
///
|
|
||||||
/// This is the default address generator used by contract instantiation. Its result
|
|
||||||
/// is only dependant on its inputs. It can therefore be used to reliably predict the
|
|
||||||
/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There
|
|
||||||
/// is no CREATE equivalent because CREATE2 is strictly more powerful.
|
|
||||||
/// Formula:
|
|
||||||
/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
|
|
||||||
pub struct DefaultAddressGenerator;
|
|
||||||
|
|
||||||
impl<T: Config> AddressGenerator<T> for DefaultAddressGenerator {
|
|
||||||
fn generate_address(
|
|
||||||
deploying_address: &T::AccountId,
|
|
||||||
code_hash: &CodeHash<T>,
|
|
||||||
input_data: &[u8],
|
|
||||||
salt: &[u8],
|
|
||||||
) -> T::AccountId {
|
|
||||||
let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt)
|
|
||||||
.using_encoded(T::Hashing::hash);
|
|
||||||
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
|
|
||||||
.expect("infinite length input; no invalid inputs for type; qed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -365,7 +324,7 @@ pub mod pallet {
|
|||||||
#[pallet::hooks]
|
#[pallet::hooks]
|
||||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||||
fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight {
|
fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight {
|
||||||
Storage::<T>::process_deletion_queue_batch(remaining_weight)
|
ContractInfo::<T>::process_deletion_queue_batch(remaining_weight)
|
||||||
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
|
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +340,7 @@ pub mod pallet {
|
|||||||
.max_block
|
.max_block
|
||||||
.saturating_sub(System::<T>::block_weight().total())
|
.saturating_sub(System::<T>::block_weight().total())
|
||||||
.min(T::DeletionWeightLimit::get());
|
.min(T::DeletionWeightLimit::get());
|
||||||
Storage::<T>::process_deletion_queue_batch(weight_limit)
|
ContractInfo::<T>::process_deletion_queue_batch(weight_limit)
|
||||||
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
|
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
|
||||||
} else {
|
} else {
|
||||||
T::WeightInfo::on_process_deletion_queue_batch()
|
T::WeightInfo::on_process_deletion_queue_batch()
|
||||||
@@ -1129,8 +1088,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
let contract_info =
|
let contract_info =
|
||||||
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
|
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
|
||||||
|
|
||||||
let maybe_value = Storage::<T>::read(
|
let maybe_value = contract_info.read(
|
||||||
&contract_info.trie_id,
|
|
||||||
&StorageKey::<T>::try_from(key).map_err(|_| ContractAccessError::KeyDecodingFailed)?,
|
&StorageKey::<T>::try_from(key).map_err(|_| ContractAccessError::KeyDecodingFailed)?,
|
||||||
);
|
);
|
||||||
Ok(maybe_value)
|
Ok(maybe_value)
|
||||||
@@ -1146,12 +1104,12 @@ impl<T: Config> Pallet<T> {
|
|||||||
input_data: &[u8],
|
input_data: &[u8],
|
||||||
salt: &[u8],
|
salt: &[u8],
|
||||||
) -> T::AccountId {
|
) -> T::AccountId {
|
||||||
T::AddressGenerator::generate_address(deploying_address, code_hash, input_data, salt)
|
T::AddressGenerator::contract_address(deploying_address, code_hash, input_data, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the code hash of the contract specified by `account` ID.
|
/// Returns the code hash of the contract specified by `account` ID.
|
||||||
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
|
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
|
||||||
Storage::<T>::code_hash(account)
|
ContractInfo::<T>::load_code_hash(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store code for benchmarks which does not check nor instrument the code.
|
/// Store code for benchmarks which does not check nor instrument the code.
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ pub mod meter;
|
|||||||
use crate::{
|
use crate::{
|
||||||
exec::{AccountIdOf, StorageKey},
|
exec::{AccountIdOf, StorageKey},
|
||||||
weights::WeightInfo,
|
weights::WeightInfo,
|
||||||
BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId, SENTINEL,
|
AddressGenerator, BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, Pallet,
|
||||||
|
TrieId, SENTINEL,
|
||||||
};
|
};
|
||||||
use codec::{Decode, Encode, MaxEncodedLen};
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
dispatch::{DispatchError, DispatchResult},
|
dispatch::{DispatchError, DispatchResult},
|
||||||
storage::child::{self, ChildInfo},
|
storage::child::{self, ChildInfo},
|
||||||
weights::Weight,
|
weights::Weight,
|
||||||
|
RuntimeDebugNoBound,
|
||||||
};
|
};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use sp_io::KillStorageResult;
|
use sp_io::KillStorageResult;
|
||||||
@@ -36,7 +38,7 @@ use sp_runtime::{
|
|||||||
traits::{Hash, Saturating, Zero},
|
traits::{Hash, Saturating, Zero},
|
||||||
RuntimeDebug,
|
RuntimeDebug,
|
||||||
};
|
};
|
||||||
use sp_std::{marker::PhantomData, prelude::*};
|
use sp_std::{ops::Deref, prelude::*};
|
||||||
|
|
||||||
/// Information for managing an account and its sub trie abstraction.
|
/// Information for managing an account and its sub trie abstraction.
|
||||||
/// This is the required info to cache for an account.
|
/// This is the required info to cache for an account.
|
||||||
@@ -45,28 +47,68 @@ use sp_std::{marker::PhantomData, prelude::*};
|
|||||||
pub struct ContractInfo<T: Config> {
|
pub struct ContractInfo<T: Config> {
|
||||||
/// Unique ID for the subtree encoded as a bytes vector.
|
/// Unique ID for the subtree encoded as a bytes vector.
|
||||||
pub trie_id: TrieId,
|
pub trie_id: TrieId,
|
||||||
|
/// The account that holds this contracts storage deposit.
|
||||||
|
///
|
||||||
|
/// This is held in a separate account to prevent the contract from spending it.
|
||||||
|
deposit_account: DepositAccount<T>,
|
||||||
/// The code associated with a given account.
|
/// The code associated with a given account.
|
||||||
pub code_hash: CodeHash<T>,
|
pub code_hash: CodeHash<T>,
|
||||||
/// How many bytes of storage are accumulated in this contract's child trie.
|
/// How many bytes of storage are accumulated in this contract's child trie.
|
||||||
pub storage_bytes: u32,
|
storage_bytes: u32,
|
||||||
/// How many items of storage are accumulated in this contract's child trie.
|
/// How many items of storage are accumulated in this contract's child trie.
|
||||||
pub storage_items: u32,
|
storage_items: u32,
|
||||||
/// This records to how much deposit the accumulated `storage_bytes` amount to.
|
/// This records to how much deposit the accumulated `storage_bytes` amount to.
|
||||||
pub storage_byte_deposit: BalanceOf<T>,
|
pub storage_byte_deposit: BalanceOf<T>,
|
||||||
/// This records to how much deposit the accumulated `storage_items` amount to.
|
/// This records to how much deposit the accumulated `storage_items` amount to.
|
||||||
pub storage_item_deposit: BalanceOf<T>,
|
storage_item_deposit: BalanceOf<T>,
|
||||||
/// This records how much deposit is put down in order to pay for the contract itself.
|
/// This records how much deposit is put down in order to pay for the contract itself.
|
||||||
///
|
///
|
||||||
/// We need to store this information separately so it is not used when calculating any refunds
|
/// We need to store this information separately so it is not used when calculating any refunds
|
||||||
/// since the base deposit can only ever be refunded on contract termination.
|
/// since the base deposit can only ever be refunded on contract termination.
|
||||||
pub storage_base_deposit: BalanceOf<T>,
|
storage_base_deposit: BalanceOf<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> ContractInfo<T> {
|
impl<T: Config> ContractInfo<T> {
|
||||||
|
/// Constructs a new contract info **without** writing it to storage.
|
||||||
|
///
|
||||||
|
/// This returns an `Err` if an contract with the supplied `account` already exists
|
||||||
|
/// in storage.
|
||||||
|
pub fn new(
|
||||||
|
account: &AccountIdOf<T>,
|
||||||
|
nonce: u64,
|
||||||
|
code_hash: CodeHash<T>,
|
||||||
|
) -> Result<Self, DispatchError> {
|
||||||
|
if <ContractInfoOf<T>>::contains_key(account) {
|
||||||
|
return Err(Error::<T>::DuplicateContract.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
let trie_id = {
|
||||||
|
let buf = (account, nonce).using_encoded(T::Hashing::hash);
|
||||||
|
buf.as_ref()
|
||||||
|
.to_vec()
|
||||||
|
.try_into()
|
||||||
|
.expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
|
||||||
|
};
|
||||||
|
|
||||||
|
let deposit_account = DepositAccount(T::AddressGenerator::deposit_address(account));
|
||||||
|
|
||||||
|
let contract = Self {
|
||||||
|
trie_id,
|
||||||
|
deposit_account,
|
||||||
|
code_hash,
|
||||||
|
storage_bytes: 0,
|
||||||
|
storage_items: 0,
|
||||||
|
storage_byte_deposit: Zero::zero(),
|
||||||
|
storage_item_deposit: Zero::zero(),
|
||||||
|
storage_base_deposit: Zero::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(contract)
|
||||||
|
}
|
||||||
|
|
||||||
/// Associated child trie unique id is built from the hash part of the trie id.
|
/// Associated child trie unique id is built from the hash part of the trie id.
|
||||||
#[cfg(test)]
|
|
||||||
pub fn child_trie_info(&self) -> ChildInfo {
|
pub fn child_trie_info(&self) -> ChildInfo {
|
||||||
child_trie_info(&self.trie_id[..])
|
ChildInfo::new_default(self.trie_id.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The deposit paying for the accumulated storage generated within the contract's child trie.
|
/// The deposit paying for the accumulated storage generated within the contract's child trie.
|
||||||
@@ -76,79 +118,30 @@ impl<T: Config> ContractInfo<T> {
|
|||||||
|
|
||||||
/// Same as [`Self::extra_deposit`] but including the base deposit.
|
/// Same as [`Self::extra_deposit`] but including the base deposit.
|
||||||
pub fn total_deposit(&self) -> BalanceOf<T> {
|
pub fn total_deposit(&self) -> BalanceOf<T> {
|
||||||
self.extra_deposit().saturating_add(self.storage_base_deposit)
|
self.extra_deposit()
|
||||||
}
|
.saturating_add(self.storage_base_deposit)
|
||||||
}
|
.saturating_sub(Pallet::<T>::min_balance())
|
||||||
|
|
||||||
/// Associated child trie unique id is built from the hash part of the trie id.
|
|
||||||
fn child_trie_info(trie_id: &[u8]) -> ChildInfo {
|
|
||||||
ChildInfo::new_default(trie_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
|
|
||||||
pub struct DeletedContract {
|
|
||||||
pub(crate) trie_id: TrieId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Information about what happended to the pre-existing value when calling [`Storage::write`].
|
|
||||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
|
||||||
pub enum WriteOutcome {
|
|
||||||
/// No value existed at the specified key.
|
|
||||||
New,
|
|
||||||
/// A value of the returned length was overwritten.
|
|
||||||
Overwritten(u32),
|
|
||||||
/// The returned value was taken out of storage before being overwritten.
|
|
||||||
///
|
|
||||||
/// This is only returned when specifically requested because it causes additional work
|
|
||||||
/// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`]
|
|
||||||
/// is returned instead.
|
|
||||||
Taken(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WriteOutcome {
|
|
||||||
/// Extracts the size of the overwritten value or `0` if there
|
|
||||||
/// was no value in storage.
|
|
||||||
pub fn old_len(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::New => 0,
|
|
||||||
Self::Overwritten(len) => *len,
|
|
||||||
Self::Taken(value) => value.len() as u32,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the size of the overwritten value or `SENTINEL` if there
|
/// Return the account that storage deposits should be deposited into.
|
||||||
/// was no value in storage.
|
pub fn deposit_account(&self) -> &DepositAccount<T> {
|
||||||
///
|
&self.deposit_account
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// We cannot use `0` as sentinel value because there could be a zero sized
|
|
||||||
/// storage entry which is different from a non existing one.
|
|
||||||
pub fn old_len_with_sentinel(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::New => SENTINEL,
|
|
||||||
Self::Overwritten(len) => *len,
|
|
||||||
Self::Taken(value) => value.len() as u32,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Storage<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T: Config> Storage<T> {
|
|
||||||
/// Reads a storage kv pair of a contract.
|
/// Reads a storage kv pair of a contract.
|
||||||
///
|
///
|
||||||
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
|
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
|
||||||
/// contract doesn't store under the given `key` `None` is returned.
|
/// contract doesn't store under the given `key` `None` is returned.
|
||||||
pub fn read<K: StorageKey<T>>(trie_id: &TrieId, key: &K) -> Option<Vec<u8>> {
|
pub fn read<K: StorageKey<T>>(&self, key: &K) -> Option<Vec<u8>> {
|
||||||
child::get_raw(&child_trie_info(trie_id), key.hash().as_slice())
|
child::get_raw(&self.child_trie_info(), key.hash().as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
|
||||||
/// was deleted.
|
/// was deleted.
|
||||||
pub fn size<K: StorageKey<T>>(trie_id: &TrieId, key: &K) -> Option<u32> {
|
pub fn size<K: StorageKey<T>>(&self, key: &K) -> Option<u32> {
|
||||||
child::len(&child_trie_info(trie_id), key.hash().as_slice())
|
child::len(&self.child_trie_info(), key.hash().as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a storage entry into a contract's kv storage.
|
/// Update a storage entry into a contract's kv storage.
|
||||||
@@ -159,13 +152,13 @@ impl<T: Config> Storage<T> {
|
|||||||
/// This function also records how much storage was created or removed if a `storage_meter`
|
/// This function also records how much storage was created or removed if a `storage_meter`
|
||||||
/// is supplied. It should only be absent for testing or benchmarking code.
|
/// is supplied. It should only be absent for testing or benchmarking code.
|
||||||
pub fn write<K: StorageKey<T>>(
|
pub fn write<K: StorageKey<T>>(
|
||||||
trie_id: &TrieId,
|
&self,
|
||||||
key: &K,
|
key: &K,
|
||||||
new_value: Option<Vec<u8>>,
|
new_value: Option<Vec<u8>>,
|
||||||
storage_meter: Option<&mut meter::NestedMeter<T>>,
|
storage_meter: Option<&mut meter::NestedMeter<T>>,
|
||||||
take: bool,
|
take: bool,
|
||||||
) -> Result<WriteOutcome, DispatchError> {
|
) -> Result<WriteOutcome, DispatchError> {
|
||||||
let child_trie_info = &child_trie_info(trie_id);
|
let child_trie_info = &self.child_trie_info();
|
||||||
let hashed_key = key.hash();
|
let hashed_key = key.hash();
|
||||||
let (old_len, old_value) = if take {
|
let (old_len, old_value) = if take {
|
||||||
let val = child::get_raw(child_trie_info, &hashed_key);
|
let val = child::get_raw(child_trie_info, &hashed_key);
|
||||||
@@ -208,37 +201,11 @@ impl<T: Config> Storage<T> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new contract descriptor in the storage with the given code hash at the given
|
|
||||||
/// address.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if there is already a contract at the given address.
|
|
||||||
pub fn new_contract(
|
|
||||||
account: &AccountIdOf<T>,
|
|
||||||
trie_id: TrieId,
|
|
||||||
code_hash: CodeHash<T>,
|
|
||||||
) -> Result<ContractInfo<T>, DispatchError> {
|
|
||||||
if <ContractInfoOf<T>>::contains_key(account) {
|
|
||||||
return Err(Error::<T>::DuplicateContract.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
let contract = ContractInfo::<T> {
|
|
||||||
code_hash,
|
|
||||||
trie_id,
|
|
||||||
storage_bytes: 0,
|
|
||||||
storage_items: 0,
|
|
||||||
storage_byte_deposit: Zero::zero(),
|
|
||||||
storage_item_deposit: Zero::zero(),
|
|
||||||
storage_base_deposit: Zero::zero(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push a contract's trie to the deletion queue for lazy removal.
|
/// Push a contract's trie to the deletion queue for lazy removal.
|
||||||
///
|
///
|
||||||
/// You must make sure that the contract is also removed when queuing the trie for deletion.
|
/// You must make sure that the contract is also removed when queuing the trie for deletion.
|
||||||
pub fn queue_trie_for_deletion(contract: &ContractInfo<T>) -> DispatchResult {
|
pub fn queue_trie_for_deletion(&self) -> DispatchResult {
|
||||||
<DeletionQueue<T>>::try_append(DeletedContract { trie_id: contract.trie_id.clone() })
|
<DeletionQueue<T>>::try_append(DeletedContract { trie_id: self.trie_id.clone() })
|
||||||
.map_err(|_| <Error<T>>::DeletionQueueFull.into())
|
.map_err(|_| <Error<T>>::DeletionQueueFull.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +256,10 @@ impl<T: Config> Storage<T> {
|
|||||||
// Cannot panic due to loop condition
|
// Cannot panic due to loop condition
|
||||||
let trie = &mut queue[0];
|
let trie = &mut queue[0];
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let outcome = child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget));
|
let outcome = child::kill_storage(
|
||||||
|
&ChildInfo::new_default(&trie.trie_id),
|
||||||
|
Some(remaining_key_budget),
|
||||||
|
);
|
||||||
let keys_removed = match outcome {
|
let keys_removed = match outcome {
|
||||||
// This happens when our budget wasn't large enough to remove all keys.
|
// This happens when our budget wasn't large enough to remove all keys.
|
||||||
KillStorageResult::SomeRemaining(c) => c,
|
KillStorageResult::SomeRemaining(c) => c,
|
||||||
@@ -307,17 +277,8 @@ impl<T: Config> Storage<T> {
|
|||||||
weight_limit.saturating_sub(weight_per_key.saturating_mul(u64::from(remaining_key_budget)))
|
weight_limit.saturating_sub(weight_per_key.saturating_mul(u64::from(remaining_key_budget)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a unique trie id by returning `hash(account_id ++ nonce)`.
|
|
||||||
pub fn generate_trie_id(account_id: &AccountIdOf<T>, nonce: u64) -> TrieId {
|
|
||||||
let buf = (account_id, nonce).using_encoded(T::Hashing::hash);
|
|
||||||
buf.as_ref()
|
|
||||||
.to_vec()
|
|
||||||
.try_into()
|
|
||||||
.expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the code hash of the contract specified by `account` ID.
|
/// Returns the code hash of the contract specified by `account` ID.
|
||||||
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
|
pub fn load_code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
|
||||||
<ContractInfoOf<T>>::get(account).map(|i| i.code_hash)
|
<ContractInfoOf<T>>::get(account).map(|i| i.code_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,3 +293,62 @@ impl<T: Config> Storage<T> {
|
|||||||
<DeletionQueue<T>>::put(bounded);
|
<DeletionQueue<T>>::put(bounded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||||
|
pub struct DeletedContract {
|
||||||
|
pub(crate) trie_id: TrieId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about what happended to the pre-existing value when calling [`ContractInfo::write`].
|
||||||
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
pub enum WriteOutcome {
|
||||||
|
/// No value existed at the specified key.
|
||||||
|
New,
|
||||||
|
/// A value of the returned length was overwritten.
|
||||||
|
Overwritten(u32),
|
||||||
|
/// The returned value was taken out of storage before being overwritten.
|
||||||
|
///
|
||||||
|
/// This is only returned when specifically requested because it causes additional work
|
||||||
|
/// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`]
|
||||||
|
/// is returned instead.
|
||||||
|
Taken(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WriteOutcome {
|
||||||
|
/// Extracts the size of the overwritten value or `0` if there
|
||||||
|
/// was no value in storage.
|
||||||
|
pub fn old_len(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::New => 0,
|
||||||
|
Self::Overwritten(len) => *len,
|
||||||
|
Self::Taken(value) => value.len() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the size of the overwritten value or `SENTINEL` if there
|
||||||
|
/// was no value in storage.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// We cannot use `0` as sentinel value because there could be a zero sized
|
||||||
|
/// storage entry which is different from a non existing one.
|
||||||
|
pub fn old_len_with_sentinel(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::New => SENTINEL,
|
||||||
|
Self::Overwritten(len) => *len,
|
||||||
|
Self::Taken(value) => value.len() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
|
||||||
|
#[scale_info(skip_type_params(T))]
|
||||||
|
pub struct DepositAccount<T: Config>(AccountIdOf<T>);
|
||||||
|
|
||||||
|
impl<T: Config> Deref for DepositAccount<T> {
|
||||||
|
type Target = AccountIdOf<T>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,22 +17,19 @@
|
|||||||
|
|
||||||
//! This module contains functions to meter the storage deposit.
|
//! This module contains functions to meter the storage deposit.
|
||||||
|
|
||||||
use crate::{storage::ContractInfo, BalanceOf, Config, Error, Inspect, Pallet};
|
use crate::{
|
||||||
|
storage::{ContractInfo, DepositAccount},
|
||||||
|
BalanceOf, Config, Error, Inspect, Pallet, System,
|
||||||
|
};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
dispatch::DispatchError,
|
dispatch::DispatchError,
|
||||||
ensure,
|
ensure,
|
||||||
traits::{
|
traits::{tokens::WithdrawConsequence, Currency, ExistenceRequirement, Get},
|
||||||
tokens::{BalanceStatus, WithdrawConsequence},
|
|
||||||
Currency, ExistenceRequirement, Get, ReservableCurrency,
|
|
||||||
},
|
|
||||||
DefaultNoBound, RuntimeDebugNoBound,
|
DefaultNoBound, RuntimeDebugNoBound,
|
||||||
};
|
};
|
||||||
use pallet_contracts_primitives::StorageDeposit as Deposit;
|
use pallet_contracts_primitives::StorageDeposit as Deposit;
|
||||||
use sp_runtime::{
|
use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128};
|
||||||
traits::{Saturating, Zero},
|
|
||||||
FixedPointNumber, FixedU128,
|
|
||||||
};
|
|
||||||
use sp_std::{marker::PhantomData, vec::Vec};
|
use sp_std::{marker::PhantomData, vec::Vec};
|
||||||
|
|
||||||
/// Deposit that uses the native currency's balance type.
|
/// Deposit that uses the native currency's balance type.
|
||||||
@@ -72,14 +69,14 @@ pub trait Ext<T: Config> {
|
|||||||
/// This is called to inform the implementer that some balance should be charged due to
|
/// This is called to inform the implementer that some balance should be charged due to
|
||||||
/// some interaction of the `origin` with a `contract`.
|
/// some interaction of the `origin` with a `contract`.
|
||||||
///
|
///
|
||||||
/// The balance transfer can either flow from `origin` to `contract` or the other way
|
/// The balance transfer can either flow from `origin` to `deposit_account` or the other way
|
||||||
/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
|
/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
|
||||||
/// It is guaranteed that that this succeeds because no more balance than returned by
|
/// It is guaranteed that this succeeds because no more balance than returned by
|
||||||
/// `check_limit` is ever charged. This is why this function is infallible.
|
/// `check_limit` is ever charged. This is why this function is infallible.
|
||||||
/// `terminated` designates whether the `contract` was terminated.
|
/// `terminated` designates whether the `contract` was terminated.
|
||||||
fn charge(
|
fn charge(
|
||||||
origin: &T::AccountId,
|
origin: &T::AccountId,
|
||||||
contract: &T::AccountId,
|
deposit_account: &DepositAccount<T>,
|
||||||
amount: &DepositOf<T>,
|
amount: &DepositOf<T>,
|
||||||
terminated: bool,
|
terminated: bool,
|
||||||
);
|
);
|
||||||
@@ -216,7 +213,7 @@ impl Diff {
|
|||||||
/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
|
/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
|
||||||
#[derive(RuntimeDebugNoBound, Clone)]
|
#[derive(RuntimeDebugNoBound, Clone)]
|
||||||
struct Charge<T: Config> {
|
struct Charge<T: Config> {
|
||||||
contract: T::AccountId,
|
deposit_account: DepositAccount<T>,
|
||||||
amount: DepositOf<T>,
|
amount: DepositOf<T>,
|
||||||
terminated: bool,
|
terminated: bool,
|
||||||
}
|
}
|
||||||
@@ -270,8 +267,8 @@ where
|
|||||||
/// Absorb a child that was spawned to handle a sub call.
|
/// Absorb a child that was spawned to handle a sub call.
|
||||||
///
|
///
|
||||||
/// This should be called whenever a sub call comes to its end and it is **not** reverted.
|
/// This should be called whenever a sub call comes to its end and it is **not** reverted.
|
||||||
/// This does the actual balance transfer from/to `origin` and `contract` based on the overall
|
/// This does the actual balance transfer from/to `origin` and `deposit_account` based on the
|
||||||
/// storage consumption of the call. It also updates the supplied contract info.
|
/// overall storage consumption of the call. It also updates the supplied contract info.
|
||||||
///
|
///
|
||||||
/// In case a contract reverted the child meter should just be dropped in order to revert
|
/// In case a contract reverted the child meter should just be dropped in order to revert
|
||||||
/// any changes it recorded.
|
/// any changes it recorded.
|
||||||
@@ -280,12 +277,12 @@ where
|
|||||||
///
|
///
|
||||||
/// - `absorbed`: The child storage meter that should be absorbed.
|
/// - `absorbed`: The child storage meter that should be absorbed.
|
||||||
/// - `origin`: The origin that spawned the original root meter.
|
/// - `origin`: The origin that spawned the original root meter.
|
||||||
/// - `contract`: The contract that this sub call belongs to.
|
/// - `deposit_account`: The contract's deposit account that this sub call belongs to.
|
||||||
/// - `info`: The info of the contract in question. `None` if the contract was terminated.
|
/// - `info`: The info of the contract in question. `None` if the contract was terminated.
|
||||||
pub fn absorb(
|
pub fn absorb(
|
||||||
&mut self,
|
&mut self,
|
||||||
absorbed: RawMeter<T, E, Nested>,
|
absorbed: RawMeter<T, E, Nested>,
|
||||||
contract: &T::AccountId,
|
deposit_account: DepositAccount<T>,
|
||||||
info: Option<&mut ContractInfo<T>>,
|
info: Option<&mut ContractInfo<T>>,
|
||||||
) {
|
) {
|
||||||
let own_deposit = absorbed.own_contribution.update_contract(info);
|
let own_deposit = absorbed.own_contribution.update_contract(info);
|
||||||
@@ -296,7 +293,7 @@ where
|
|||||||
if !own_deposit.is_zero() {
|
if !own_deposit.is_zero() {
|
||||||
self.charges.extend_from_slice(&absorbed.charges);
|
self.charges.extend_from_slice(&absorbed.charges);
|
||||||
self.charges.push(Charge {
|
self.charges.push(Charge {
|
||||||
contract: contract.clone(),
|
deposit_account,
|
||||||
amount: own_deposit,
|
amount: own_deposit,
|
||||||
terminated: absorbed.is_terminated(),
|
terminated: absorbed.is_terminated(),
|
||||||
});
|
});
|
||||||
@@ -345,10 +342,10 @@ where
|
|||||||
/// execution did finish.
|
/// execution did finish.
|
||||||
pub fn into_deposit(self, origin: &T::AccountId) -> DepositOf<T> {
|
pub fn into_deposit(self, origin: &T::AccountId) -> DepositOf<T> {
|
||||||
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
|
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
|
||||||
E::charge(origin, &charge.contract, &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(_))) {
|
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
|
||||||
E::charge(origin, &charge.contract, &charge.amount, charge.terminated);
|
E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated);
|
||||||
}
|
}
|
||||||
self.total_deposit
|
self.total_deposit
|
||||||
}
|
}
|
||||||
@@ -360,9 +357,8 @@ where
|
|||||||
T: Config,
|
T: Config,
|
||||||
E: Ext<T>,
|
E: Ext<T>,
|
||||||
{
|
{
|
||||||
/// Try to charge the `diff` from the meter. Fails if this would exceed the original limit.
|
/// Charge `diff` from the meter.
|
||||||
pub fn charge(&mut self, diff: &Diff) {
|
pub fn charge(&mut self, diff: &Diff) {
|
||||||
debug_assert!(self.is_alive());
|
|
||||||
match &mut self.own_contribution {
|
match &mut self.own_contribution {
|
||||||
Contribution::Alive(own) => *own = own.saturating_add(diff),
|
Contribution::Alive(own) => *own = own.saturating_add(diff),
|
||||||
_ => panic!("Charge is never called after termination; qed"),
|
_ => panic!("Charge is never called after termination; qed"),
|
||||||
@@ -379,13 +375,16 @@ where
|
|||||||
info: &mut ContractInfo<T>,
|
info: &mut ContractInfo<T>,
|
||||||
) -> Result<DepositOf<T>, DispatchError> {
|
) -> Result<DepositOf<T>, DispatchError> {
|
||||||
debug_assert!(self.is_alive());
|
debug_assert!(self.is_alive());
|
||||||
|
|
||||||
|
let ed = Pallet::<T>::min_balance();
|
||||||
let mut deposit =
|
let mut deposit =
|
||||||
Diff { bytes_added: info.encoded_size() as u32, items_added: 1, ..Default::default() }
|
Diff { bytes_added: info.encoded_size() as u32, items_added: 1, ..Default::default() }
|
||||||
.update_contract::<T>(None);
|
.update_contract::<T>(None);
|
||||||
|
|
||||||
// Instantiate needs to transfer the minimum balance at least in order to pull the
|
// Instantiate needs to transfer at least the minimum balance in order to pull the
|
||||||
// contract's account into existence.
|
// deposit account into existence.
|
||||||
deposit = deposit.max(Deposit::Charge(Pallet::<T>::min_balance()));
|
// We also add another `ed` here which goes to the contract's own account into existence.
|
||||||
|
deposit = deposit.max(Deposit::Charge(ed)).saturating_add(&Deposit::Charge(ed));
|
||||||
if deposit.charge_or_zero() > self.limit {
|
if deposit.charge_or_zero() > self.limit {
|
||||||
return Err(<Error<T>>::StorageDepositLimitExhausted.into())
|
return Err(<Error<T>>::StorageDepositLimitExhausted.into())
|
||||||
}
|
}
|
||||||
@@ -394,11 +393,22 @@ where
|
|||||||
// contract execution does conclude and hence would lead to a double charge.
|
// contract execution does conclude and hence would lead to a double charge.
|
||||||
self.total_deposit = deposit.clone();
|
self.total_deposit = deposit.clone();
|
||||||
info.storage_base_deposit = deposit.charge_or_zero();
|
info.storage_base_deposit = deposit.charge_or_zero();
|
||||||
if !deposit.is_zero() {
|
|
||||||
// We need to charge immediately so that the account is created before the `value`
|
// Usually, deposit charges are deferred to be able to coalesce them with refunds.
|
||||||
// is transferred from the caller to the contract.
|
// However, we need to charge immediately so that the account is created before
|
||||||
E::charge(origin, contract, &deposit, false);
|
// charges possibly below the ed are collected and fail.
|
||||||
}
|
E::charge(
|
||||||
|
origin,
|
||||||
|
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.
|
||||||
|
T::Currency::transfer(origin, contract, ed, ExistenceRequirement::KeepAlive)?;
|
||||||
|
System::<T>::inc_consumers(contract)?;
|
||||||
|
|
||||||
Ok(deposit)
|
Ok(deposit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,7 +453,12 @@ impl<T: Config> Ext<T> for ReservingExt {
|
|||||||
limit: Option<BalanceOf<T>>,
|
limit: Option<BalanceOf<T>>,
|
||||||
min_leftover: BalanceOf<T>,
|
min_leftover: BalanceOf<T>,
|
||||||
) -> Result<BalanceOf<T>, DispatchError> {
|
) -> Result<BalanceOf<T>, DispatchError> {
|
||||||
let max = T::Currency::reducible_balance(origin, true).saturating_sub(min_leftover);
|
// We are sending the `min_leftover` and the `min_balance` from the origin
|
||||||
|
// account as part of a contract call. Hence origin needs to have those left over
|
||||||
|
// as free balance after accounting for all deposits.
|
||||||
|
let max = T::Currency::reducible_balance(origin, true)
|
||||||
|
.saturating_sub(min_leftover)
|
||||||
|
.saturating_sub(Pallet::<T>::min_balance());
|
||||||
let limit = limit.unwrap_or(max);
|
let limit = limit.unwrap_or(max);
|
||||||
ensure!(
|
ensure!(
|
||||||
limit <= max &&
|
limit <= max &&
|
||||||
@@ -455,67 +470,67 @@ impl<T: Config> Ext<T> for ReservingExt {
|
|||||||
|
|
||||||
fn charge(
|
fn charge(
|
||||||
origin: &T::AccountId,
|
origin: &T::AccountId,
|
||||||
contract: &T::AccountId,
|
deposit_account: &DepositAccount<T>,
|
||||||
amount: &DepositOf<T>,
|
amount: &DepositOf<T>,
|
||||||
terminated: bool,
|
terminated: bool,
|
||||||
) {
|
) {
|
||||||
// There is nothing we can do when this fails as this constitutes a bug in the runtime:
|
// There is nothing we can do when this fails as this constitutes a bug in the runtime.
|
||||||
// Either the runtime does not hold up the invariant of never deleting a contract's account
|
// We need to settle for emitting an error log in this case.
|
||||||
// or it does not honor reserved balances. 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.
|
||||||
match amount {
|
match amount {
|
||||||
Deposit::Charge(amount) => {
|
Deposit::Charge(amount) => {
|
||||||
// This will never fail because a contract's account is required to exist
|
// This will never fail because a deposit account is required to exist
|
||||||
// at all times. The pallet enforces this invariant by depositing at least the
|
// at all times. The pallet enforces this invariant by holding a consumer reference
|
||||||
// existential deposit when instantiating and never refunds it unless the contract
|
// on the deposit account as long as the contract exists.
|
||||||
// is removed. This means the receiver always exists except when instantiating a
|
//
|
||||||
// contract. In this case we made sure that at least the existential deposit is
|
// The sender always has enough balance because we checked that it had enough
|
||||||
// sent. 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
|
||||||
// balance when instantiating the storage meter.
|
// which is a plain account to send away this balance in the meantime.
|
||||||
let result = T::Currency::transfer(
|
let result = T::Currency::transfer(
|
||||||
origin,
|
origin,
|
||||||
contract,
|
deposit_account,
|
||||||
*amount,
|
*amount,
|
||||||
ExistenceRequirement::KeepAlive,
|
ExistenceRequirement::KeepAlive,
|
||||||
)
|
);
|
||||||
.and_then(|_| T::Currency::reserve(contract, *amount));
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
log::error!(
|
log::error!(
|
||||||
target: "runtime::contracts",
|
target: "runtime::contracts",
|
||||||
"Failed to transfer storage deposit {:?} from origin {:?} to contract {:?}: {:?}",
|
"Failed to transfer storage deposit {:?} from origin {:?} to deposit account {:?}: {:?}",
|
||||||
amount, origin, contract, err,
|
amount, origin, deposit_account, err,
|
||||||
);
|
);
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
panic!("Unable to collect storage deposit. This is a bug.");
|
panic!("Unable to collect storage deposit. This is a bug.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// For `Refund(_)` no error happen because the initial value transfer from the
|
// The receiver always exists because the initial value transfer from the
|
||||||
// origin to the contract has a keep alive existence requirement and when reserving we
|
// origin to the contract has a keep alive existence requirement. When taking a deposit
|
||||||
// make sure to leave at least the ed in the free balance. Therefore the receiver always
|
// we make sure to leave at least the ed in the free balance.
|
||||||
// exists because there is no way for it to be removed in between. The sender always has
|
//
|
||||||
// enough reserved balance because we track it in the `ContractInfo` and never send more
|
// The sender always has enough balance because we track it in the `ContractInfo` and
|
||||||
// back than we have.
|
// never send more back than we have. Noone has access to the deposit account. Hence no
|
||||||
|
// other interaction with this account takes place.
|
||||||
Deposit::Refund(amount) => {
|
Deposit::Refund(amount) => {
|
||||||
let amount = if terminated {
|
if terminated {
|
||||||
*amount
|
System::<T>::dec_consumers(&deposit_account);
|
||||||
} else {
|
}
|
||||||
// This is necessary when the `storage_deposit` tracked inside the account
|
let result = T::Currency::transfer(
|
||||||
// info is out of sync with the actual balance. That can only happen due to
|
deposit_account,
|
||||||
// slashing. We make sure to never dust the contract's account through a
|
origin,
|
||||||
// refund because we consider this unexpected behaviour.
|
*amount,
|
||||||
*amount.min(
|
// We can safely use `AllowDeath` because our own consumer prevents an removal.
|
||||||
&T::Currency::reserved_balance(contract)
|
ExistenceRequirement::AllowDeath,
|
||||||
.saturating_sub(Pallet::<T>::min_balance()),
|
);
|
||||||
)
|
if matches!(result, Err(_)) {
|
||||||
};
|
|
||||||
let result =
|
|
||||||
T::Currency::repatriate_reserved(contract, origin, amount, BalanceStatus::Free);
|
|
||||||
if matches!(result, Ok(val) if !val.is_zero()) || matches!(result, Err(_)) {
|
|
||||||
log::error!(
|
log::error!(
|
||||||
target: "runtime::contracts",
|
target: "runtime::contracts",
|
||||||
"Failed to repatriate storage deposit {:?} from contract {:?} to origin {:?}: {:?}",
|
"Failed to refund storage deposit {:?} from deposit account {:?} to origin {:?}: {:?}",
|
||||||
amount, contract, origin, result,
|
amount, deposit_account, origin, result,
|
||||||
);
|
);
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
panic!("Unable to refund storage deposit. This is a bug.");
|
panic!("Unable to refund storage deposit. This is a bug.");
|
||||||
@@ -558,7 +573,7 @@ mod tests {
|
|||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
struct Charge {
|
struct Charge {
|
||||||
origin: AccountIdOf<Test>,
|
origin: AccountIdOf<Test>,
|
||||||
contract: AccountIdOf<Test>,
|
contract: DepositAccount<Test>,
|
||||||
amount: DepositOf<Test>,
|
amount: DepositOf<Test>,
|
||||||
terminated: bool,
|
terminated: bool,
|
||||||
}
|
}
|
||||||
@@ -592,7 +607,7 @@ mod tests {
|
|||||||
|
|
||||||
fn charge(
|
fn charge(
|
||||||
origin: &AccountIdOf<Test>,
|
origin: &AccountIdOf<Test>,
|
||||||
contract: &AccountIdOf<Test>,
|
contract: &DepositAccount<Test>,
|
||||||
amount: &DepositOf<Test>,
|
amount: &DepositOf<Test>,
|
||||||
terminated: bool,
|
terminated: bool,
|
||||||
) {
|
) {
|
||||||
@@ -620,12 +635,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_info(info: StorageInfo) -> ContractInfo<Test> {
|
fn new_info(info: StorageInfo) -> ContractInfo<Test> {
|
||||||
use crate::storage::Storage;
|
|
||||||
use sp_runtime::traits::Hash;
|
|
||||||
|
|
||||||
ContractInfo::<Test> {
|
ContractInfo::<Test> {
|
||||||
trie_id: <Storage<Test>>::generate_trie_id(&ALICE, 42),
|
trie_id: Default::default(),
|
||||||
code_hash: <Test as frame_system::Config>::Hashing::hash(b"42"),
|
deposit_account: DepositAccount([0u8; 32].into()),
|
||||||
|
code_hash: Default::default(),
|
||||||
storage_bytes: info.bytes,
|
storage_bytes: info.bytes,
|
||||||
storage_items: info.items,
|
storage_items: info.items,
|
||||||
storage_byte_deposit: info.bytes_deposit,
|
storage_byte_deposit: info.bytes_deposit,
|
||||||
@@ -659,7 +672,7 @@ mod tests {
|
|||||||
// an empty charge does not create a `Charge` entry
|
// an empty charge does not create a `Charge` entry
|
||||||
let mut nested0 = meter.nested();
|
let mut nested0 = meter.nested();
|
||||||
nested0.charge(&Default::default());
|
nested0.charge(&Default::default());
|
||||||
meter.absorb(nested0, &BOB, None);
|
meter.absorb(nested0, DepositAccount(BOB), None);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
TestExtTestValue::get(),
|
TestExtTestValue::get(),
|
||||||
@@ -692,16 +705,16 @@ mod tests {
|
|||||||
new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 });
|
new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 });
|
||||||
let mut nested1 = nested0.nested();
|
let mut nested1 = nested0.nested();
|
||||||
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
|
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
|
||||||
nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info));
|
nested0.absorb(nested1, DepositAccount(CHARLIE), Some(&mut nested1_info));
|
||||||
|
|
||||||
let mut nested2_info =
|
let mut nested2_info =
|
||||||
new_info(StorageInfo { bytes: 100, items: 7, bytes_deposit: 100, items_deposit: 20 });
|
new_info(StorageInfo { bytes: 100, items: 7, bytes_deposit: 100, items_deposit: 20 });
|
||||||
let mut nested2 = nested0.nested();
|
let mut nested2 = nested0.nested();
|
||||||
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
|
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
|
||||||
nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info));
|
nested0.absorb(nested2, DepositAccount(CHARLIE), Some(&mut nested2_info));
|
||||||
|
|
||||||
nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
|
nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
|
||||||
meter.absorb(nested0, &BOB, Some(&mut nested0_info));
|
meter.absorb(nested0, DepositAccount(BOB), Some(&mut nested0_info));
|
||||||
|
|
||||||
meter.into_deposit(&ALICE);
|
meter.into_deposit(&ALICE);
|
||||||
|
|
||||||
@@ -716,19 +729,19 @@ mod tests {
|
|||||||
charges: vec![
|
charges: vec![
|
||||||
Charge {
|
Charge {
|
||||||
origin: ALICE,
|
origin: ALICE,
|
||||||
contract: CHARLIE,
|
contract: DepositAccount(CHARLIE),
|
||||||
amount: Deposit::Refund(10),
|
amount: Deposit::Refund(10),
|
||||||
terminated: false
|
terminated: false
|
||||||
},
|
},
|
||||||
Charge {
|
Charge {
|
||||||
origin: ALICE,
|
origin: ALICE,
|
||||||
contract: CHARLIE,
|
contract: DepositAccount(CHARLIE),
|
||||||
amount: Deposit::Refund(20),
|
amount: Deposit::Refund(20),
|
||||||
terminated: false
|
terminated: false
|
||||||
},
|
},
|
||||||
Charge {
|
Charge {
|
||||||
origin: ALICE,
|
origin: ALICE,
|
||||||
contract: BOB,
|
contract: DepositAccount(BOB),
|
||||||
amount: Deposit::Charge(2),
|
amount: Deposit::Charge(2),
|
||||||
terminated: false
|
terminated: false
|
||||||
}
|
}
|
||||||
@@ -760,9 +773,9 @@ mod tests {
|
|||||||
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
|
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
|
||||||
nested1.terminate(&nested1_info);
|
nested1.terminate(&nested1_info);
|
||||||
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
|
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
|
||||||
nested0.absorb(nested1, &CHARLIE, None);
|
nested0.absorb(nested1, DepositAccount(CHARLIE), None);
|
||||||
|
|
||||||
meter.absorb(nested0, &BOB, None);
|
meter.absorb(nested0, DepositAccount(BOB), None);
|
||||||
meter.into_deposit(&ALICE);
|
meter.into_deposit(&ALICE);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -772,13 +785,13 @@ mod tests {
|
|||||||
charges: vec![
|
charges: vec![
|
||||||
Charge {
|
Charge {
|
||||||
origin: ALICE,
|
origin: ALICE,
|
||||||
contract: CHARLIE,
|
contract: DepositAccount(CHARLIE),
|
||||||
amount: Deposit::Refund(120),
|
amount: Deposit::Refund(119),
|
||||||
terminated: true
|
terminated: true
|
||||||
},
|
},
|
||||||
Charge {
|
Charge {
|
||||||
origin: ALICE,
|
origin: ALICE,
|
||||||
contract: BOB,
|
contract: DepositAccount(BOB),
|
||||||
amount: Deposit::Charge(12),
|
amount: Deposit::Charge(12),
|
||||||
terminated: false
|
terminated: false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,11 @@ use crate::{
|
|||||||
Result as ExtensionResult, RetVal, ReturnFlags, SysConfig,
|
Result as ExtensionResult, RetVal, ReturnFlags, SysConfig,
|
||||||
},
|
},
|
||||||
exec::{FixSizedKey, Frame},
|
exec::{FixSizedKey, Frame},
|
||||||
storage::Storage,
|
|
||||||
tests::test_utils::{get_contract, get_contract_checked},
|
tests::test_utils::{get_contract, get_contract_checked},
|
||||||
wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode},
|
wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode},
|
||||||
weights::WeightInfo,
|
weights::WeightInfo,
|
||||||
BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, DeletionQueue,
|
BalanceOf, Code, CodeStorage, Config, ContractInfo, ContractInfoOf, DefaultAddressGenerator,
|
||||||
Error, Pallet, Schedule,
|
DeletionQueue, Error, Pallet, Schedule,
|
||||||
};
|
};
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
@@ -37,8 +36,8 @@ use frame_support::{
|
|||||||
parameter_types,
|
parameter_types,
|
||||||
storage::child,
|
storage::child,
|
||||||
traits::{
|
traits::{
|
||||||
BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle,
|
ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, Get, LockableCurrency,
|
||||||
OnInitialize, ReservableCurrency, WithdrawReasons,
|
OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons,
|
||||||
},
|
},
|
||||||
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
|
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
|
||||||
};
|
};
|
||||||
@@ -51,7 +50,7 @@ use sp_runtime::{
|
|||||||
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
|
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
|
||||||
AccountId32,
|
AccountId32,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use crate as pallet_contracts;
|
use crate as pallet_contracts;
|
||||||
|
|
||||||
@@ -76,9 +75,7 @@ frame_support::construct_runtime!(
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use super::{Balances, Hash, SysConfig, Test};
|
use super::{Balances, Hash, SysConfig, Test};
|
||||||
use crate::{
|
use crate::{exec::AccountIdOf, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce};
|
||||||
exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce,
|
|
||||||
};
|
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_support::traits::Currency;
|
use frame_support::traits::Currency;
|
||||||
|
|
||||||
@@ -87,9 +84,8 @@ pub mod test_utils {
|
|||||||
*counter += 1;
|
*counter += 1;
|
||||||
*counter
|
*counter
|
||||||
});
|
});
|
||||||
let trie_id = Storage::<Test>::generate_trie_id(address, nonce);
|
|
||||||
set_balance(address, <Test as Config>::Currency::minimum_balance() * 10);
|
set_balance(address, <Test as Config>::Currency::minimum_balance() * 10);
|
||||||
let contract = Storage::<Test>::new_contract(&address, trie_id, code_hash).unwrap();
|
let contract = <ContractInfo<Test>>::new(&address, nonce, code_hash).unwrap();
|
||||||
<ContractInfoOf<Test>>::insert(address, contract);
|
<ContractInfoOf<Test>>::insert(address, contract);
|
||||||
}
|
}
|
||||||
pub fn set_balance(who: &AccountIdOf<Test>, amount: u64) {
|
pub fn set_balance(who: &AccountIdOf<Test>, amount: u64) {
|
||||||
@@ -518,7 +514,7 @@ fn calling_plain_account_fails() {
|
|||||||
fn instantiate_and_call_and_deposit_event() {
|
fn instantiate_and_call_and_deposit_event() {
|
||||||
let (wasm, code_hash) = compile_module::<Test>("event_and_return_on_deploy").unwrap();
|
let (wasm, code_hash) = compile_module::<Test>("event_and_return_on_deploy").unwrap();
|
||||||
|
|
||||||
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
|
ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
|
||||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||||
let value = 100;
|
let value = 100;
|
||||||
@@ -551,9 +547,36 @@ fn instantiate_and_call_and_deposit_event() {
|
|||||||
.account_id;
|
.account_id;
|
||||||
assert!(ContractInfoOf::<Test>::contains_key(&addr));
|
assert!(ContractInfoOf::<Test>::contains_key(&addr));
|
||||||
|
|
||||||
|
let contract = get_contract(&addr);
|
||||||
|
let deposit_account = contract.deposit_account().deref();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
free_balance: 131,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
|
from: ALICE,
|
||||||
|
to: deposit_account.clone(),
|
||||||
|
amount: 131,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
@@ -578,14 +601,6 @@ fn instantiate_and_call_and_deposit_event() {
|
|||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: min_balance,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
@@ -823,7 +838,7 @@ fn deploy_and_call_other_contract() {
|
|||||||
let (caller_wasm, _caller_code_hash) = compile_module::<Test>("caller_contract").unwrap();
|
let (caller_wasm, _caller_code_hash) = compile_module::<Test>("caller_contract").unwrap();
|
||||||
let (callee_wasm, callee_code_hash) = compile_module::<Test>("return_with_data").unwrap();
|
let (callee_wasm, callee_code_hash) = compile_module::<Test>("return_with_data").unwrap();
|
||||||
|
|
||||||
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
|
ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
|
||||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
@@ -841,18 +856,7 @@ fn deploy_and_call_other_contract() {
|
|||||||
.result
|
.result
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.account_id;
|
.account_id;
|
||||||
Contracts::bare_instantiate(
|
Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Deterministic).unwrap();
|
||||||
ALICE,
|
|
||||||
100_000,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
Code::Upload(callee_wasm),
|
|
||||||
0u32.to_le_bytes().encode(),
|
|
||||||
vec![42],
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.result
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let callee_addr = Contracts::contract_address(
|
let callee_addr = Contracts::contract_address(
|
||||||
&caller_addr,
|
&caller_addr,
|
||||||
@@ -875,9 +879,36 @@ fn deploy_and_call_other_contract() {
|
|||||||
callee_code_hash.as_ref().to_vec(),
|
callee_code_hash.as_ref().to_vec(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let callee = get_contract(&callee_addr);
|
||||||
|
let deposit_account = callee.deposit_account().deref();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
free_balance: 131,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
|
from: ALICE,
|
||||||
|
to: deposit_account.clone(),
|
||||||
|
amount: 131,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
@@ -902,20 +933,12 @@ fn deploy_and_call_other_contract() {
|
|||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: callee_addr.clone(),
|
|
||||||
amount: min_balance,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
from: caller_addr.clone(),
|
from: caller_addr.clone(),
|
||||||
to: callee_addr.clone(),
|
to: callee_addr.clone(),
|
||||||
amount: 32768, // hard coded in wasm
|
amount: 32768 // hardcoded in wasm
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
@@ -998,6 +1021,46 @@ fn delegate_call() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transfer_allow_death_cannot_kill_account() {
|
||||||
|
let (wasm, _code_hash) = compile_module::<Test>("dummy").unwrap();
|
||||||
|
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
||||||
|
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
|
||||||
|
// Instantiate the BOB contract.
|
||||||
|
let addr = Contracts::bare_instantiate(
|
||||||
|
ALICE,
|
||||||
|
1_000,
|
||||||
|
GAS_LIMIT,
|
||||||
|
None,
|
||||||
|
Code::Upload(wasm),
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.result
|
||||||
|
.unwrap()
|
||||||
|
.account_id;
|
||||||
|
|
||||||
|
// Check that the BOB contract has been instantiated.
|
||||||
|
get_contract(&addr);
|
||||||
|
|
||||||
|
let total_balance = <Test as Config>::Currency::total_balance(&addr);
|
||||||
|
|
||||||
|
assert_err!(
|
||||||
|
<<Test as Config>::Currency as Currency<AccountId32>>::transfer(
|
||||||
|
&addr,
|
||||||
|
&ALICE,
|
||||||
|
total_balance,
|
||||||
|
ExistenceRequirement::AllowDeath,
|
||||||
|
),
|
||||||
|
pallet_balances::Error::<Test>::KeepAlive,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(<Test as Config>::Currency::total_balance(&addr), total_balance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cannot_self_destruct_through_draning() {
|
fn cannot_self_destruct_through_draning() {
|
||||||
let (wasm, _code_hash) = compile_module::<Test>("drain").unwrap();
|
let (wasm, _code_hash) = compile_module::<Test>("drain").unwrap();
|
||||||
@@ -1023,7 +1086,7 @@ fn cannot_self_destruct_through_draning() {
|
|||||||
get_contract(&addr);
|
get_contract(&addr);
|
||||||
|
|
||||||
// Call BOB which makes it send all funds to the zero address
|
// Call BOB which makes it send all funds to the zero address
|
||||||
// The contract code asserts that the transfer was successful
|
// The contract code asserts that the transfer fails with the correct error code
|
||||||
assert_ok!(Contracts::call(
|
assert_ok!(Contracts::call(
|
||||||
RuntimeOrigin::signed(ALICE),
|
RuntimeOrigin::signed(ALICE),
|
||||||
addr.clone(),
|
addr.clone(),
|
||||||
@@ -1036,7 +1099,7 @@ fn cannot_self_destruct_through_draning() {
|
|||||||
// Make sure the account wasn't remove by sending all free balance away.
|
// Make sure the account wasn't remove by sending all free balance away.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<Test as Config>::Currency::total_balance(&addr),
|
<Test as Config>::Currency::total_balance(&addr),
|
||||||
<Test as Config>::Currency::minimum_balance(),
|
1_000 + <Test as Config>::Currency::minimum_balance(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1093,96 +1156,10 @@ fn cannot_self_destruct_through_storage_refund_after_price_change() {
|
|||||||
|
|
||||||
// Make sure the account wasn't removed by the refund
|
// Make sure the account wasn't removed by the refund
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<Test as Config>::Currency::total_balance(&addr),
|
<Test as Config>::Currency::total_balance(get_contract(&addr).deposit_account()),
|
||||||
get_contract(&addr).total_deposit(),
|
get_contract(&addr).total_deposit(),
|
||||||
);
|
);
|
||||||
assert_eq!(get_contract(&addr).extra_deposit(), 2,);
|
assert_eq!(get_contract(&addr).extra_deposit(), 2);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cannot_self_destruct_by_refund_after_slash() {
|
|
||||||
let (wasm, _code_hash) = compile_module::<Test>("store").unwrap();
|
|
||||||
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
|
|
||||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
|
||||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
|
||||||
|
|
||||||
let addr = Contracts::bare_instantiate(
|
|
||||||
ALICE,
|
|
||||||
0,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
Code::Upload(wasm),
|
|
||||||
vec![],
|
|
||||||
vec![],
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.result
|
|
||||||
.unwrap()
|
|
||||||
.account_id;
|
|
||||||
|
|
||||||
// create 100 more reserved balance
|
|
||||||
assert_ok!(Contracts::call(
|
|
||||||
RuntimeOrigin::signed(ALICE),
|
|
||||||
addr.clone(),
|
|
||||||
0,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
98u32.encode(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Drop previous events
|
|
||||||
initialize_block(2);
|
|
||||||
|
|
||||||
// slash parts of the 100 so that the next refund ould remove the account
|
|
||||||
// because it the value it stored for `storage_deposit` becomes out of sync
|
|
||||||
let _ = <Test as Config>::Currency::slash(&addr, 90);
|
|
||||||
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance + 10);
|
|
||||||
|
|
||||||
// trigger a refund of 50 which would bring the contract below min when actually refunded
|
|
||||||
assert_ok!(Contracts::call(
|
|
||||||
RuntimeOrigin::signed(ALICE),
|
|
||||||
addr.clone(),
|
|
||||||
0,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
48u32.encode(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Make sure the account kept the minimum balance and was not destroyed
|
|
||||||
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
System::events(),
|
|
||||||
vec![
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Slashed {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: 90,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Contracts(crate::Event::Called {
|
|
||||||
caller: ALICE,
|
|
||||||
contract: addr.clone(),
|
|
||||||
}),
|
|
||||||
topics: vec![hash(&ALICE), hash(&addr)],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated {
|
|
||||||
from: addr.clone(),
|
|
||||||
to: ALICE,
|
|
||||||
amount: 10,
|
|
||||||
destination_status: BalanceStatus::Free,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1252,7 +1229,7 @@ fn self_destruct_works() {
|
|||||||
.account_id;
|
.account_id;
|
||||||
|
|
||||||
// Check that the BOB contract has been instantiated.
|
// Check that the BOB contract has been instantiated.
|
||||||
get_contract(&addr);
|
let contract = get_contract(&addr);
|
||||||
|
|
||||||
// Drop all previous events
|
// Drop all previous events
|
||||||
initialize_block(2);
|
initialize_block(2);
|
||||||
@@ -1271,17 +1248,25 @@ fn self_destruct_works() {
|
|||||||
assert_eq!(Balances::total_balance(&addr), 0);
|
assert_eq!(Balances::total_balance(&addr), 0);
|
||||||
|
|
||||||
// check that the beneficiary (django) got remaining balance
|
// check that the beneficiary (django) got remaining balance
|
||||||
assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000);
|
let ed = <Test as Config>::Currency::minimum_balance();
|
||||||
|
assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000 + ed);
|
||||||
|
|
||||||
pretty_assertions::assert_eq!(
|
pretty_assertions::assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
|
||||||
|
account: addr.clone()
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
from: addr.clone(),
|
from: addr.clone(),
|
||||||
to: DJANGO,
|
to: DJANGO,
|
||||||
amount: 100_000,
|
amount: 100_000 + ed,
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
@@ -1304,17 +1289,16 @@ fn self_destruct_works() {
|
|||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
|
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
|
||||||
account: addr.clone()
|
account: contract.deposit_account().deref().clone(),
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
from: addr.clone(),
|
from: contract.deposit_account().deref().clone(),
|
||||||
to: ALICE,
|
to: ALICE,
|
||||||
amount: 1_000,
|
amount: 1_000,
|
||||||
destination_status: BalanceStatus::Free,
|
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
@@ -2055,7 +2039,7 @@ fn lazy_removal_on_full_queue_works_on_initialize() {
|
|||||||
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
||||||
// Fill the deletion queue with dummy values, so that on_initialize attempts
|
// Fill the deletion queue with dummy values, so that on_initialize attempts
|
||||||
// to clear the queue
|
// to clear the queue
|
||||||
Storage::<Test>::fill_queue_with_dummies();
|
ContractInfo::<Test>::fill_queue_with_dummies();
|
||||||
|
|
||||||
let queue_len_initial = <DeletionQueue<Test>>::decode_len().unwrap_or(0);
|
let queue_len_initial = <DeletionQueue<Test>>::decode_len().unwrap_or(0);
|
||||||
|
|
||||||
@@ -2132,7 +2116,7 @@ fn lazy_removal_partial_remove_works() {
|
|||||||
// We create a contract with some extra keys above the weight limit
|
// We create a contract with some extra keys above the weight limit
|
||||||
let extra_keys = 7u32;
|
let extra_keys = 7u32;
|
||||||
let weight_limit = Weight::from_ref_time(5_000_000_000);
|
let weight_limit = Weight::from_ref_time(5_000_000_000);
|
||||||
let (_, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
|
let (_, max_keys) = ContractInfo::<Test>::deletion_budget(1, weight_limit);
|
||||||
let vals: Vec<_> = (0..max_keys + extra_keys)
|
let vals: Vec<_> = (0..max_keys + extra_keys)
|
||||||
.map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode()))
|
.map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode()))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -2161,14 +2145,7 @@ fn lazy_removal_partial_remove_works() {
|
|||||||
|
|
||||||
// Put value into the contracts child trie
|
// Put value into the contracts child trie
|
||||||
for val in &vals {
|
for val in &vals {
|
||||||
Storage::<Test>::write(
|
info.write(&val.0 as &FixSizedKey, Some(val.2.clone()), None, false).unwrap();
|
||||||
&info.trie_id,
|
|
||||||
&val.0 as &FixSizedKey,
|
|
||||||
Some(val.2.clone()),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
||||||
|
|
||||||
@@ -2201,7 +2178,7 @@ fn lazy_removal_partial_remove_works() {
|
|||||||
|
|
||||||
ext.execute_with(|| {
|
ext.execute_with(|| {
|
||||||
// Run the lazy removal
|
// Run the lazy removal
|
||||||
let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
|
let weight_used = ContractInfo::<Test>::process_deletion_queue_batch(weight_limit);
|
||||||
|
|
||||||
// Weight should be exhausted because we could not even delete all keys
|
// Weight should be exhausted because we could not even delete all keys
|
||||||
assert_eq!(weight_used, weight_limit);
|
assert_eq!(weight_used, weight_limit);
|
||||||
@@ -2235,7 +2212,7 @@ fn lazy_removal_does_no_run_on_full_queue_and_full_block() {
|
|||||||
|
|
||||||
// Fill the deletion queue with dummy values, so that on_initialize attempts
|
// Fill the deletion queue with dummy values, so that on_initialize attempts
|
||||||
// to clear the queue
|
// to clear the queue
|
||||||
Storage::<Test>::fill_queue_with_dummies();
|
ContractInfo::<Test>::fill_queue_with_dummies();
|
||||||
|
|
||||||
// Check that on_initialize() tries to perform lazy removal but removes nothing
|
// Check that on_initialize() tries to perform lazy removal but removes nothing
|
||||||
// as no more weight is left for that.
|
// as no more weight is left for that.
|
||||||
@@ -2344,7 +2321,7 @@ fn lazy_removal_does_not_use_all_weight() {
|
|||||||
.account_id;
|
.account_id;
|
||||||
|
|
||||||
let info = get_contract(&addr);
|
let info = get_contract(&addr);
|
||||||
let (weight_per_key, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
|
let (weight_per_key, max_keys) = ContractInfo::<Test>::deletion_budget(1, weight_limit);
|
||||||
|
|
||||||
// We create a contract with one less storage item than we can remove within the limit
|
// We create a contract with one less storage item than we can remove within the limit
|
||||||
let vals: Vec<_> = (0..max_keys - 1)
|
let vals: Vec<_> = (0..max_keys - 1)
|
||||||
@@ -2353,14 +2330,7 @@ fn lazy_removal_does_not_use_all_weight() {
|
|||||||
|
|
||||||
// Put value into the contracts child trie
|
// Put value into the contracts child trie
|
||||||
for val in &vals {
|
for val in &vals {
|
||||||
Storage::<Test>::write(
|
info.write(&val.0 as &FixSizedKey, Some(val.2.clone()), None, false).unwrap();
|
||||||
&info.trie_id,
|
|
||||||
&val.0 as &FixSizedKey,
|
|
||||||
Some(val.2.clone()),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
<ContractInfoOf<Test>>::insert(&addr, info.clone());
|
||||||
|
|
||||||
@@ -2393,7 +2363,7 @@ fn lazy_removal_does_not_use_all_weight() {
|
|||||||
|
|
||||||
ext.execute_with(|| {
|
ext.execute_with(|| {
|
||||||
// Run the lazy removal
|
// Run the lazy removal
|
||||||
let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
|
let weight_used = ContractInfo::<Test>::process_deletion_queue_batch(weight_limit);
|
||||||
|
|
||||||
// We have one less key in our trie than our weight limit suffices for
|
// We have one less key in our trie than our weight limit suffices for
|
||||||
assert_eq!(weight_used, weight_limit - weight_per_key);
|
assert_eq!(weight_used, weight_limit - weight_per_key);
|
||||||
@@ -2427,7 +2397,7 @@ fn deletion_queue_full() {
|
|||||||
.account_id;
|
.account_id;
|
||||||
|
|
||||||
// fill the deletion queue up until its limit
|
// fill the deletion queue up until its limit
|
||||||
Storage::<Test>::fill_queue_with_dummies();
|
ContractInfo::<Test>::fill_queue_with_dummies();
|
||||||
|
|
||||||
// Terminate the contract should fail
|
// Terminate the contract should fail
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
@@ -3196,14 +3166,12 @@ fn instantiate_with_zero_balance_works() {
|
|||||||
.account_id;
|
.account_id;
|
||||||
|
|
||||||
// Check that the BOB contract has been instantiated.
|
// Check that the BOB contract has been instantiated.
|
||||||
get_contract(&addr);
|
let contract = get_contract(&addr);
|
||||||
|
let deposit_account = contract.deposit_account().deref();
|
||||||
|
|
||||||
// Make sure the account exists even though no free balance was send
|
// Make sure the account exists even though no free balance was send
|
||||||
assert_eq!(<Test as Config>::Currency::free_balance(&addr), 0,);
|
assert_eq!(<Test as Config>::Currency::free_balance(&addr), min_balance);
|
||||||
assert_eq!(
|
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance,);
|
||||||
<Test as Config>::Currency::total_balance(&addr),
|
|
||||||
<Test as Config>::Currency::minimum_balance(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
@@ -3211,7 +3179,31 @@ fn instantiate_with_zero_balance_works() {
|
|||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
account: addr.clone()
|
account: deposit_account.clone(),
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
free_balance: min_balance,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
|
from: ALICE,
|
||||||
|
to: deposit_account.clone(),
|
||||||
|
amount: min_balance,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
|
account: addr.clone(),
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
@@ -3232,14 +3224,6 @@ fn instantiate_with_zero_balance_works() {
|
|||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: min_balance,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||||
@@ -3292,18 +3276,40 @@ fn instantiate_with_below_existential_deposit_works() {
|
|||||||
.account_id;
|
.account_id;
|
||||||
|
|
||||||
// Check that the BOB contract has been instantiated.
|
// Check that the BOB contract has been instantiated.
|
||||||
get_contract(&addr);
|
let contract = get_contract(&addr);
|
||||||
|
let deposit_account = contract.deposit_account().deref();
|
||||||
|
|
||||||
// Make sure the account exists even though no free balance was send
|
// Make sure the account exists even though not enough free balance was send
|
||||||
assert_eq!(<Test as Config>::Currency::free_balance(&addr), 50,);
|
assert_eq!(<Test as Config>::Currency::free_balance(&addr), min_balance + 50);
|
||||||
assert_eq!(
|
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance + 50);
|
||||||
<Test as Config>::Currency::total_balance(&addr),
|
|
||||||
<Test as Config>::Currency::minimum_balance() + 50,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
||||||
|
account: deposit_account.clone(),
|
||||||
|
free_balance: min_balance,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
|
from: ALICE,
|
||||||
|
to: deposit_account.clone(),
|
||||||
|
amount: min_balance,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
@@ -3328,14 +3334,6 @@ fn instantiate_with_below_existential_deposit_works() {
|
|||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: min_balance,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
@@ -3436,6 +3434,9 @@ fn storage_deposit_works() {
|
|||||||
deposit -= refunded0;
|
deposit -= refunded0;
|
||||||
assert_eq!(get_contract(&addr).total_deposit(), deposit);
|
assert_eq!(get_contract(&addr).total_deposit(), deposit);
|
||||||
|
|
||||||
|
let contract = get_contract(&addr);
|
||||||
|
let deposit_account = contract.deposit_account().deref();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
@@ -3460,15 +3461,7 @@ fn storage_deposit_works() {
|
|||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
from: ALICE,
|
from: ALICE,
|
||||||
to: addr.clone(),
|
to: deposit_account.clone(),
|
||||||
amount: charged0,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: charged0,
|
amount: charged0,
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
@@ -3485,15 +3478,7 @@ fn storage_deposit_works() {
|
|||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
from: ALICE,
|
from: ALICE,
|
||||||
to: addr.clone(),
|
to: deposit_account.clone(),
|
||||||
amount: charged1,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: charged1,
|
amount: charged1,
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
@@ -3508,11 +3493,10 @@ fn storage_deposit_works() {
|
|||||||
},
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated {
|
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
||||||
from: addr.clone(),
|
from: deposit_account.clone(),
|
||||||
to: ALICE,
|
to: ALICE,
|
||||||
amount: refunded0,
|
amount: refunded0,
|
||||||
destination_status: BalanceStatus::Free,
|
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
@@ -3610,11 +3594,10 @@ fn set_code_extrinsic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn call_after_killed_account_needs_funding() {
|
fn slash_cannot_kill_account() {
|
||||||
let (wasm, _code_hash) = compile_module::<Test>("dummy").unwrap();
|
let (wasm, _code_hash) = compile_module::<Test>("dummy").unwrap();
|
||||||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
||||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
|
||||||
|
|
||||||
let addr = Contracts::bare_instantiate(
|
let addr = Contracts::bare_instantiate(
|
||||||
ALICE,
|
ALICE,
|
||||||
@@ -3633,107 +3616,24 @@ fn call_after_killed_account_needs_funding() {
|
|||||||
// Drop previous events
|
// Drop previous events
|
||||||
initialize_block(2);
|
initialize_block(2);
|
||||||
|
|
||||||
// Destroy the account of the contract by slashing.
|
// Try to destroy the account of the contract by slashing.
|
||||||
// Slashing can actually happen if the contract takes part in staking.
|
// The account does not get destroyed because of the consumer reference.
|
||||||
// It is a corner case and we accept the destruction of the account.
|
// Slashing can for example happen if the contract takes part in staking.
|
||||||
let _ = <Test as Config>::Currency::slash(
|
let _ = <Test as Config>::Currency::slash(
|
||||||
&addr,
|
&addr,
|
||||||
<Test as Config>::Currency::total_balance(&addr),
|
<Test as Config>::Currency::total_balance(&addr),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sending below the minimum balance will fail the call because it needs to create the
|
|
||||||
// account in order to send balance there.
|
|
||||||
assert_err_ignore_postinfo!(
|
|
||||||
Contracts::call(
|
|
||||||
RuntimeOrigin::signed(ALICE),
|
|
||||||
addr.clone(),
|
|
||||||
min_balance - 1,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
<Error<Test>>::TransferFailed
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sending zero should work as it does not do a transfer
|
|
||||||
assert_ok!(Contracts::call(
|
|
||||||
RuntimeOrigin::signed(ALICE),
|
|
||||||
addr.clone(),
|
|
||||||
0,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
|
|
||||||
// Sending minimum balance should work
|
|
||||||
assert_ok!(Contracts::call(
|
|
||||||
RuntimeOrigin::signed(ALICE),
|
|
||||||
addr.clone(),
|
|
||||||
min_balance,
|
|
||||||
GAS_LIMIT,
|
|
||||||
None,
|
|
||||||
vec![],
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![EventRecord {
|
||||||
EventRecord {
|
phase: Phase::Initialization,
|
||||||
phase: Phase::Initialization,
|
event: RuntimeEvent::Balances(pallet_balances::Event::Slashed {
|
||||||
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
|
who: addr.clone(),
|
||||||
account: addr.clone()
|
amount: 700, // slash didn't remove the minimum balance
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},]
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Slashed {
|
|
||||||
who: addr.clone(),
|
|
||||||
amount: min_balance + 700
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Contracts(crate::Event::Called {
|
|
||||||
caller: ALICE,
|
|
||||||
contract: addr.clone(),
|
|
||||||
}),
|
|
||||||
topics: vec![hash(&ALICE), hash(&addr)],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
|
||||||
account: addr.clone()
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
|
|
||||||
account: addr.clone(),
|
|
||||||
free_balance: min_balance
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
|
|
||||||
from: ALICE,
|
|
||||||
to: addr.clone(),
|
|
||||||
amount: min_balance
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Contracts(crate::Event::Called {
|
|
||||||
caller: ALICE,
|
|
||||||
contract: addr.clone(),
|
|
||||||
}),
|
|
||||||
topics: vec![hash(&ALICE), hash(&addr)],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1467
-1463
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user