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

Commit

Permalink
Added RentStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
athei committed May 11, 2021
1 parent fbec1ec commit 360b467
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 10 deletions.
26 changes: 26 additions & 0 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,32 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])

seal_rent_status {
let r in 0 .. API_BENCHMARK_BATCHES;
let pages = code::max_pages::<T>();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
name: "seal_rent_status",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
return_type: None,
}],
data_segments: vec![DataSegment {
offset: 0,
value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
}],
call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[
Instruction::I32Const(1),
Instruction::I32Const(4),
Instruction::I32Const(0),
Instruction::Call(0),
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])

seal_weight_to_fee {
let r in 0 .. API_BENCHMARK_BATCHES;
let pages = code::max_pages::<T>();
Expand Down
61 changes: 60 additions & 1 deletion frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

use crate::{
CodeHash, Event, Config, Pallet as Contracts,
BalanceOf, ContractInfo, gas::GasMeter, rent::Rent, storage::Storage,
BalanceOf, ContractInfo, gas::GasMeter, rent::{Rent, RentStatus}, storage::Storage,
Error, ContractInfoOf, Schedule, AliveContractInfo, AccountCounter,
};
use sp_core::crypto::UncheckedFrom;
Expand Down Expand Up @@ -313,6 +313,9 @@ pub trait Ext: sealing::Sealed {
/// Information needed for rent calculations.
fn rent_params(&self) -> &RentParams<Self::T>;

/// Information about the required deposit and resulting rent.
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T>;

/// Get a mutable reference to the nested gas meter.
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;

Expand Down Expand Up @@ -1232,6 +1235,20 @@ where
&self.top_frame().rent_params
}

fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T> {
let frame = self.top_frame_mut();
let balance = T::Currency::free_balance(&frame.account_id);
let code_size = frame.rent_params.code_size;
let refcount = frame.rent_params.code_refcount;
<Rent<T, E>>::rent_status(
&balance,
&frame.contract_info(),
code_size,
refcount,
at_refcount,
)
}

fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_meter
}
Expand Down Expand Up @@ -2186,6 +2203,48 @@ mod tests {
});
}

#[test]
fn rent_status_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(ctx.ext.rent_status(0), RentStatus {
max_deposit: 80000,
current_deposit: 80000,
custom_refcount_deposit: None,
max_rent: 32,
current_rent: 32,
custom_refcount_rent: None,
_reserved: None,
});
assert_eq!(ctx.ext.rent_status(1), RentStatus {
max_deposit: 80000,
current_deposit: 80000,
custom_refcount_deposit: Some(80000),
max_rent: 32,
current_rent: 32,
custom_refcount_rent: Some(32),
_reserved: None,
});
exec_success()
});

ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&schedule,
0,
vec![],
None,
).unwrap();
});
}

#[test]
fn in_memory_changes_not_discarded() {
// Call stack: BOB -> CHARLIE (trap) -> BOB' (success)
Expand Down
104 changes: 95 additions & 9 deletions frame/contracts/src/rent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,49 @@ use sp_runtime::{
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
};

/// Information about the required deposit and resulting rent.
///
/// The easiest way to guarantee that a contract stays alive is to assert that
/// `max_rent == 0` at the **end** of a contract's execution.
///
/// # Note
///
/// The `current_*` fields do **not** consider changes to the code's refcount made during
/// the currently running call.
#[derive(codec::Encode)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RentStatus<T: Config> {
/// Required deposit assuming that this contract is the only user of its code.
pub max_deposit: BalanceOf<T>,
/// Required deposit assuming the code's current refcount.
pub current_deposit: BalanceOf<T>,
/// Required deposit assuming the specified refcount (None if 0 is supplied).
pub custom_refcount_deposit: Option<BalanceOf<T>>,
/// Rent that is payed assuming that the contract is the only user of its code.
pub max_rent: BalanceOf<T>,
/// Rent that is payed given the code's current refcount.
pub current_rent: BalanceOf<T>,
/// Rent that is payed assuming the specified refcount (None is 0 is supplied).
pub custom_refcount_rent: Option<BalanceOf<T>>,
/// Reserved for backwards compatible changes to this data structure.
pub _reserved: Option<()>,
}

/// We cannot derive `Default` because `T` does not necessarily implement `Default`.
impl<T: Config> Default for RentStatus<T> {
fn default() -> Self {
Self {
max_deposit: Default::default(),
current_deposit: Default::default(),
custom_refcount_deposit: Default::default(),
max_rent: Default::default(),
current_rent: Default::default(),
custom_refcount_rent: Default::default(),
_reserved: Default::default(),
}
}
}

pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);

impl<T, E> Rent<T, E>
Expand Down Expand Up @@ -160,7 +203,7 @@ where
// Compute how much would the fee per block be with the *updated* balance.
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = Self::compute_fee_per_block(
let fee_per_block = Self::fee_per_block(
&free_balance, &alive_contract_info, code_size,
);
if fee_per_block.is_zero() {
Expand Down Expand Up @@ -281,24 +324,67 @@ where
Ok((caller_code_len, tombstone_code_len))
}

/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
/// then the fee can drop to zero.
fn compute_fee_per_block(
/// Create a new `RentStatus` struct for pass through to a requesting contract.
pub fn rent_status(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
aggregated_code_size: u32,
current_refcount: u32,
at_refcount: u32,
) -> RentStatus<T> {
let calc_share = |refcount: u32| {
aggregated_code_size.checked_div(refcount).unwrap_or(0)
};
let max_share = calc_share(1);
let current_share = calc_share(current_refcount);
let custom_share = calc_share(at_refcount);
RentStatus {
max_deposit: Self::required_deposit(contract, max_share),
current_deposit: Self::required_deposit(contract, current_share),
custom_refcount_deposit:
if at_refcount > 0 {
Some(Self::required_deposit(contract, custom_share))
} else {
None
},
max_rent: Self::fee_per_block(free_balance, contract, max_share),
current_rent: Self::fee_per_block(free_balance, contract, current_share),
custom_refcount_rent:
if at_refcount > 0 {
Some(Self::fee_per_block(free_balance, contract, custom_share))
} else {
None
},
_reserved: None,
}
}

/// Returns how much deposit is required to not pay rent.
fn required_deposit(
contract: &AliveContractInfo<T>,
code_size_share: u32,
) -> BalanceOf<T> {
let uncovered_by_balance = T::DepositPerStorageByte::get()
T::DepositPerStorageByte::get()
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
.saturating_add(
T::DepositPerStorageItem::get()
.saturating_mul(contract.pair_count.into())
)
.saturating_add(T::DepositPerContract::get())
}

/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract
/// possesses enough funds then the fee can drop to zero.
fn fee_per_block(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
code_size_share: u32,
) -> BalanceOf<T> {
let missing_deposit = Self::required_deposit(contract, code_size_share)
.saturating_sub(*free_balance);
T::RentFraction::get().mul_ceil(uncovered_by_balance)
T::RentFraction::get().mul_ceil(missing_deposit)
}

/// Returns amount of funds available to consume by rent mechanism.
Expand Down Expand Up @@ -354,7 +440,7 @@ where
let free_balance = T::Currency::free_balance(account);

// An amount of funds to charge per block for storage taken up by the contract.
let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);
let fee_per_block = Self::fee_per_block(&free_balance, contract, code_size);
if fee_per_block.is_zero() {
// The rent deposit offset reduced the fee to 0. This means that the contract
// gets the rent for free.
Expand Down
4 changes: 4 additions & 0 deletions frame/contracts/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_rent_params`.
pub rent_params: Weight,

/// Weight of calling `seal_rent_status`.
pub rent_status: Weight,

/// The type parameter is used in the default implementation.
#[codec(skip)]
pub _phantom: PhantomData<T>
Expand Down Expand Up @@ -620,6 +623,7 @@ impl<T: Config> Default for HostFnWeights<T> {
hash_blake2_128: cost_batched!(seal_hash_blake2_128),
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
rent_params: cost_batched!(seal_rent_params),
rent_status: cost_batched!(seal_rent_status),
_phantom: PhantomData,
}
}
Expand Down
46 changes: 46 additions & 0 deletions frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ mod tests {
RentParams, ExecError, ErrorOrigin,
},
gas::GasMeter,
rent::RentStatus,
tests::{Test, Call, ALICE, BOB},
};
use std::collections::HashMap;
Expand Down Expand Up @@ -452,6 +453,9 @@ mod tests {
fn rent_params(&self) -> &RentParams<Self::T> {
&self.rent_params
}
fn rent_status(&mut self, _at_refcount: u32) -> RentStatus<Self::T> {
Default::default()
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.gas_meter
}
Expand Down Expand Up @@ -1858,6 +1862,48 @@ mod tests {
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
}

const CODE_RENT_STATUS: &str = r#"
(module
(import "seal0" "seal_rent_status" (func $seal_rent_status (param i32 i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 4) buffer size = 128 bytes
(data (i32.const 0) "\80")
;; [4; inf) buffer where the result is copied
(func (export "call")
;; Load the rent params into memory
(call $seal_rent_status
(i32.const 1) ;; at_refcount
(i32.const 4) ;; Pointer to the output buffer
(i32.const 0) ;; Pointer to the size of the buffer
)
;; Return the contents of the buffer
(call $seal_return
(i32.const 0) ;; return flags
(i32.const 4) ;; buffer pointer
(i32.load (i32.const 0)) ;; buffer size
)
)
(func (export "deploy"))
)
"#;

#[test]
fn rent_status_work() {
let output = execute(
CODE_RENT_STATUS,
vec![],
MockExt::default(),
).unwrap();
let rent_status = Bytes(<RentStatus<Test>>::default().encode());
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_status });
}

const CODE_DEBUG_MESSAGE: &str = r#"
(module
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
Expand Down
23 changes: 23 additions & 0 deletions frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ pub enum RuntimeCosts {
CopyIn(u32),
/// Weight of calling `seal_rent_params`.
RentParams,
/// Weight of calling `seal_rent_status`.
RentStatus,
}

impl RuntimeCosts {
Expand Down Expand Up @@ -291,6 +293,7 @@ impl RuntimeCosts {
ChainExtension(amount) => amount,
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
RentParams => s.rent_params,
RentStatus => s.rent_status,
};
RuntimeToken {
#[cfg(test)]
Expand Down Expand Up @@ -1585,4 +1588,24 @@ define_env!(Env, <E: Ext>,
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
)?)
},

// Stores the rent status into the supplied buffer.
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
// `out_len_ptr` must point to a u32 value that describes the available space at
// `out_ptr`. This call overwrites it with the size of the value. If the available
// space at `out_ptr` is less than the size of the value a trap is triggered.
//
// The data is encoded as [`crate::rent::RentStatus`].
//
// # Parameters
//
// - `at_refcount`: The refcount assumed for the returned `custom_refcount_*` fields
[seal0] seal_rent_status(ctx, at_refcount: u32, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeCosts::RentStatus)?;
let rent_status = ctx.ext.rent_status(at_refcount).encode();
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &rent_status, false, already_charged
)?)
},
);
Loading

0 comments on commit 360b467

Please sign in to comment.