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

Commit 360b467

Browse files
committed
Added RentStatus
1 parent fbec1ec commit 360b467

File tree

7 files changed

+269
-10
lines changed

7 files changed

+269
-10
lines changed

frame/contracts/src/benchmarking/mod.rs

+26
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,32 @@ benchmarks! {
535535
let origin = RawOrigin::Signed(instance.caller.clone());
536536
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
537537

538+
seal_rent_status {
539+
let r in 0 .. API_BENCHMARK_BATCHES;
540+
let pages = code::max_pages::<T>();
541+
let code = WasmModule::<T>::from(ModuleDefinition {
542+
memory: Some(ImportedMemory::max::<T>()),
543+
imported_functions: vec![ImportedFunction {
544+
name: "seal_rent_status",
545+
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
546+
return_type: None,
547+
}],
548+
data_segments: vec![DataSegment {
549+
offset: 0,
550+
value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
551+
}],
552+
call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[
553+
Instruction::I32Const(1),
554+
Instruction::I32Const(4),
555+
Instruction::I32Const(0),
556+
Instruction::Call(0),
557+
])),
558+
.. Default::default()
559+
});
560+
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
561+
let origin = RawOrigin::Signed(instance.caller.clone());
562+
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
563+
538564
seal_weight_to_fee {
539565
let r in 0 .. API_BENCHMARK_BATCHES;
540566
let pages = code::max_pages::<T>();

frame/contracts/src/exec.rs

+60-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

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

316+
/// Information about the required deposit and resulting rent.
317+
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T>;
318+
316319
/// Get a mutable reference to the nested gas meter.
317320
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
318321

@@ -1232,6 +1235,20 @@ where
12321235
&self.top_frame().rent_params
12331236
}
12341237

1238+
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T> {
1239+
let frame = self.top_frame_mut();
1240+
let balance = T::Currency::free_balance(&frame.account_id);
1241+
let code_size = frame.rent_params.code_size;
1242+
let refcount = frame.rent_params.code_refcount;
1243+
<Rent<T, E>>::rent_status(
1244+
&balance,
1245+
&frame.contract_info(),
1246+
code_size,
1247+
refcount,
1248+
at_refcount,
1249+
)
1250+
}
1251+
12351252
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
12361253
&mut self.top_frame_mut().nested_meter
12371254
}
@@ -2186,6 +2203,48 @@ mod tests {
21862203
});
21872204
}
21882205

2206+
#[test]
2207+
fn rent_status_works() {
2208+
let code_hash = MockLoader::insert(Call, |ctx, _| {
2209+
assert_eq!(ctx.ext.rent_status(0), RentStatus {
2210+
max_deposit: 80000,
2211+
current_deposit: 80000,
2212+
custom_refcount_deposit: None,
2213+
max_rent: 32,
2214+
current_rent: 32,
2215+
custom_refcount_rent: None,
2216+
_reserved: None,
2217+
});
2218+
assert_eq!(ctx.ext.rent_status(1), RentStatus {
2219+
max_deposit: 80000,
2220+
current_deposit: 80000,
2221+
custom_refcount_deposit: Some(80000),
2222+
max_rent: 32,
2223+
current_rent: 32,
2224+
custom_refcount_rent: Some(32),
2225+
_reserved: None,
2226+
});
2227+
exec_success()
2228+
});
2229+
2230+
ExtBuilder::default().build().execute_with(|| {
2231+
let subsistence = Contracts::<Test>::subsistence_threshold();
2232+
let schedule = <Test as Config>::Schedule::get();
2233+
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
2234+
set_balance(&ALICE, subsistence * 10);
2235+
place_contract(&BOB, code_hash);
2236+
MockStack::run_call(
2237+
ALICE,
2238+
BOB,
2239+
&mut gas_meter,
2240+
&schedule,
2241+
0,
2242+
vec![],
2243+
None,
2244+
).unwrap();
2245+
});
2246+
}
2247+
21892248
#[test]
21902249
fn in_memory_changes_not_discarded() {
21912250
// Call stack: BOB -> CHARLIE (trap) -> BOB' (success)

frame/contracts/src/rent.rs

+95-9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,49 @@ use sp_runtime::{
3535
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
3636
};
3737

38+
/// Information about the required deposit and resulting rent.
39+
///
40+
/// The easiest way to guarantee that a contract stays alive is to assert that
41+
/// `max_rent == 0` at the **end** of a contract's execution.
42+
///
43+
/// # Note
44+
///
45+
/// The `current_*` fields do **not** consider changes to the code's refcount made during
46+
/// the currently running call.
47+
#[derive(codec::Encode)]
48+
#[cfg_attr(test, derive(Debug, PartialEq))]
49+
pub struct RentStatus<T: Config> {
50+
/// Required deposit assuming that this contract is the only user of its code.
51+
pub max_deposit: BalanceOf<T>,
52+
/// Required deposit assuming the code's current refcount.
53+
pub current_deposit: BalanceOf<T>,
54+
/// Required deposit assuming the specified refcount (None if 0 is supplied).
55+
pub custom_refcount_deposit: Option<BalanceOf<T>>,
56+
/// Rent that is payed assuming that the contract is the only user of its code.
57+
pub max_rent: BalanceOf<T>,
58+
/// Rent that is payed given the code's current refcount.
59+
pub current_rent: BalanceOf<T>,
60+
/// Rent that is payed assuming the specified refcount (None is 0 is supplied).
61+
pub custom_refcount_rent: Option<BalanceOf<T>>,
62+
/// Reserved for backwards compatible changes to this data structure.
63+
pub _reserved: Option<()>,
64+
}
65+
66+
/// We cannot derive `Default` because `T` does not necessarily implement `Default`.
67+
impl<T: Config> Default for RentStatus<T> {
68+
fn default() -> Self {
69+
Self {
70+
max_deposit: Default::default(),
71+
current_deposit: Default::default(),
72+
custom_refcount_deposit: Default::default(),
73+
max_rent: Default::default(),
74+
current_rent: Default::default(),
75+
custom_refcount_rent: Default::default(),
76+
_reserved: Default::default(),
77+
}
78+
}
79+
}
80+
3881
pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);
3982

4083
impl<T, E> Rent<T, E>
@@ -160,7 +203,7 @@ where
160203
// Compute how much would the fee per block be with the *updated* balance.
161204
let total_balance = T::Currency::total_balance(account);
162205
let free_balance = T::Currency::free_balance(account);
163-
let fee_per_block = Self::compute_fee_per_block(
206+
let fee_per_block = Self::fee_per_block(
164207
&free_balance, &alive_contract_info, code_size,
165208
);
166209
if fee_per_block.is_zero() {
@@ -281,24 +324,67 @@ where
281324
Ok((caller_code_len, tombstone_code_len))
282325
}
283326

284-
/// Returns a fee charged per block from the contract.
285-
///
286-
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
287-
/// then the fee can drop to zero.
288-
fn compute_fee_per_block(
327+
/// Create a new `RentStatus` struct for pass through to a requesting contract.
328+
pub fn rent_status(
289329
free_balance: &BalanceOf<T>,
330+
contract: &AliveContractInfo<T>,
331+
aggregated_code_size: u32,
332+
current_refcount: u32,
333+
at_refcount: u32,
334+
) -> RentStatus<T> {
335+
let calc_share = |refcount: u32| {
336+
aggregated_code_size.checked_div(refcount).unwrap_or(0)
337+
};
338+
let max_share = calc_share(1);
339+
let current_share = calc_share(current_refcount);
340+
let custom_share = calc_share(at_refcount);
341+
RentStatus {
342+
max_deposit: Self::required_deposit(contract, max_share),
343+
current_deposit: Self::required_deposit(contract, current_share),
344+
custom_refcount_deposit:
345+
if at_refcount > 0 {
346+
Some(Self::required_deposit(contract, custom_share))
347+
} else {
348+
None
349+
},
350+
max_rent: Self::fee_per_block(free_balance, contract, max_share),
351+
current_rent: Self::fee_per_block(free_balance, contract, current_share),
352+
custom_refcount_rent:
353+
if at_refcount > 0 {
354+
Some(Self::fee_per_block(free_balance, contract, custom_share))
355+
} else {
356+
None
357+
},
358+
_reserved: None,
359+
}
360+
}
361+
362+
/// Returns how much deposit is required to not pay rent.
363+
fn required_deposit(
290364
contract: &AliveContractInfo<T>,
291365
code_size_share: u32,
292366
) -> BalanceOf<T> {
293-
let uncovered_by_balance = T::DepositPerStorageByte::get()
367+
T::DepositPerStorageByte::get()
294368
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
295369
.saturating_add(
296370
T::DepositPerStorageItem::get()
297371
.saturating_mul(contract.pair_count.into())
298372
)
299373
.saturating_add(T::DepositPerContract::get())
374+
}
375+
376+
/// Returns a fee charged per block from the contract.
377+
///
378+
/// This function accounts for the storage rent deposit. I.e. if the contract
379+
/// possesses enough funds then the fee can drop to zero.
380+
fn fee_per_block(
381+
free_balance: &BalanceOf<T>,
382+
contract: &AliveContractInfo<T>,
383+
code_size_share: u32,
384+
) -> BalanceOf<T> {
385+
let missing_deposit = Self::required_deposit(contract, code_size_share)
300386
.saturating_sub(*free_balance);
301-
T::RentFraction::get().mul_ceil(uncovered_by_balance)
387+
T::RentFraction::get().mul_ceil(missing_deposit)
302388
}
303389

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

356442
// An amount of funds to charge per block for storage taken up by the contract.
357-
let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);
443+
let fee_per_block = Self::fee_per_block(&free_balance, contract, code_size);
358444
if fee_per_block.is_zero() {
359445
// The rent deposit offset reduced the fee to 0. This means that the contract
360446
// gets the rent for free.

frame/contracts/src/schedule.rs

+4
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ pub struct HostFnWeights<T: Config> {
391391
/// Weight of calling `seal_rent_params`.
392392
pub rent_params: Weight,
393393

394+
/// Weight of calling `seal_rent_status`.
395+
pub rent_status: Weight,
396+
394397
/// The type parameter is used in the default implementation.
395398
#[codec(skip)]
396399
pub _phantom: PhantomData<T>
@@ -620,6 +623,7 @@ impl<T: Config> Default for HostFnWeights<T> {
620623
hash_blake2_128: cost_batched!(seal_hash_blake2_128),
621624
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
622625
rent_params: cost_batched!(seal_rent_params),
626+
rent_status: cost_batched!(seal_rent_status),
623627
_phantom: PhantomData,
624628
}
625629
}

frame/contracts/src/wasm/mod.rs

+46
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ mod tests {
247247
RentParams, ExecError, ErrorOrigin,
248248
},
249249
gas::GasMeter,
250+
rent::RentStatus,
250251
tests::{Test, Call, ALICE, BOB},
251252
};
252253
use std::collections::HashMap;
@@ -452,6 +453,9 @@ mod tests {
452453
fn rent_params(&self) -> &RentParams<Self::T> {
453454
&self.rent_params
454455
}
456+
fn rent_status(&mut self, _at_refcount: u32) -> RentStatus<Self::T> {
457+
Default::default()
458+
}
455459
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
456460
&mut self.gas_meter
457461
}
@@ -1858,6 +1862,48 @@ mod tests {
18581862
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
18591863
}
18601864

1865+
const CODE_RENT_STATUS: &str = r#"
1866+
(module
1867+
(import "seal0" "seal_rent_status" (func $seal_rent_status (param i32 i32 i32)))
1868+
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
1869+
(import "env" "memory" (memory 1 1))
1870+
1871+
;; [0, 4) buffer size = 128 bytes
1872+
(data (i32.const 0) "\80")
1873+
1874+
;; [4; inf) buffer where the result is copied
1875+
1876+
(func (export "call")
1877+
;; Load the rent params into memory
1878+
(call $seal_rent_status
1879+
(i32.const 1) ;; at_refcount
1880+
(i32.const 4) ;; Pointer to the output buffer
1881+
(i32.const 0) ;; Pointer to the size of the buffer
1882+
)
1883+
1884+
;; Return the contents of the buffer
1885+
(call $seal_return
1886+
(i32.const 0) ;; return flags
1887+
(i32.const 4) ;; buffer pointer
1888+
(i32.load (i32.const 0)) ;; buffer size
1889+
)
1890+
)
1891+
1892+
(func (export "deploy"))
1893+
)
1894+
"#;
1895+
1896+
#[test]
1897+
fn rent_status_work() {
1898+
let output = execute(
1899+
CODE_RENT_STATUS,
1900+
vec![],
1901+
MockExt::default(),
1902+
).unwrap();
1903+
let rent_status = Bytes(<RentStatus<Test>>::default().encode());
1904+
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_status });
1905+
}
1906+
18611907
const CODE_DEBUG_MESSAGE: &str = r#"
18621908
(module
18631909
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))

frame/contracts/src/wasm/runtime.rs

+23
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ pub enum RuntimeCosts {
222222
CopyIn(u32),
223223
/// Weight of calling `seal_rent_params`.
224224
RentParams,
225+
/// Weight of calling `seal_rent_status`.
226+
RentStatus,
225227
}
226228

227229
impl RuntimeCosts {
@@ -291,6 +293,7 @@ impl RuntimeCosts {
291293
ChainExtension(amount) => amount,
292294
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
293295
RentParams => s.rent_params,
296+
RentStatus => s.rent_status,
294297
};
295298
RuntimeToken {
296299
#[cfg(test)]
@@ -1585,4 +1588,24 @@ define_env!(Env, <E: Ext>,
15851588
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
15861589
)?)
15871590
},
1591+
1592+
// Stores the rent status into the supplied buffer.
1593+
//
1594+
// The value is stored to linear memory at the address pointed to by `out_ptr`.
1595+
// `out_len_ptr` must point to a u32 value that describes the available space at
1596+
// `out_ptr`. This call overwrites it with the size of the value. If the available
1597+
// space at `out_ptr` is less than the size of the value a trap is triggered.
1598+
//
1599+
// The data is encoded as [`crate::rent::RentStatus`].
1600+
//
1601+
// # Parameters
1602+
//
1603+
// - `at_refcount`: The refcount assumed for the returned `custom_refcount_*` fields
1604+
[seal0] seal_rent_status(ctx, at_refcount: u32, out_ptr: u32, out_len_ptr: u32) => {
1605+
ctx.charge_gas(RuntimeCosts::RentStatus)?;
1606+
let rent_status = ctx.ext.rent_status(at_refcount).encode();
1607+
Ok(ctx.write_sandbox_output(
1608+
out_ptr, out_len_ptr, &rent_status, false, already_charged
1609+
)?)
1610+
},
15881611
);

0 commit comments

Comments
 (0)