Unverified Commit cbf08a02 authored by Michael Müller's avatar Michael Müller Committed by GitHub
Browse files

Implement `seal_rent_params` (#755)

* Fix typo

* Add `seal_rent_params`

* Make `rent_params` only available on `ink-unstable`

* wip

* wip

* Wrap `seal_rent_params` in `__unstable__`

* Add `rent_params` to experimental off-chain env

* Fix return type

* Remove comments

* Remove debugging code

* Remove `ink-unstable` feature
parent f60c64cf
Pipeline #139406 passed with stages
in 29 minutes and 52 seconds
......@@ -28,6 +28,7 @@ cfg-if = "1.0"
paste = "1.0"
arrayref = "0.3"
static_assertions = "1.1"
sp-arithmetic = { version = "3.0", default-features = false }
# Hashes for the off-chain environment.
sha2 = { version = "0.9", optional = true }
......
......@@ -34,6 +34,7 @@ use crate::{
HashOutput,
},
topics::Topics,
types::RentParams,
Environment,
Result,
};
......@@ -155,6 +156,20 @@ where
})
}
/// Returns information needed for rent calculations.
///
/// # Errors
///
/// If the returned value cannot be properly decoded.
pub fn rent_params<T>() -> Result<RentParams<T>>
where
T: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::rent_params::<T>(instance)
})
}
/// Returns the current block number.
///
/// # Errors
......@@ -401,6 +416,8 @@ pub fn restore_contract<T>(
/// This removes the calling account and transfers all remaining balance
/// to the given beneficiary.
///
/// No tombstone will be created, this function kills a contract completely!
///
/// # Note
///
/// This function never returns. Either the termination was successful and the
......
......@@ -23,6 +23,7 @@ use crate::{
HashOutput,
},
topics::Topics,
types::RentParams,
Environment,
Result,
};
......@@ -222,6 +223,13 @@ pub trait TypedEnvBackend: EnvBackend {
/// For more details visit: [`rent_allowance`][`crate::rent_allowance`]
fn rent_allowance<T: Environment>(&mut self) -> Result<T::Balance>;
/// Returns information needed for rent calculations.
///
/// # Note
///
/// For more details visit: [`RentParams`][`crate::RentParams`]
fn rent_params<T: Environment>(&mut self) -> Result<RentParams<T>>;
/// Returns the current block number.
///
/// # Note
......
......@@ -34,6 +34,7 @@ use crate::{
Clear,
EnvBackend,
Environment,
RentParams,
Result,
ReturnFlags,
TypedEnvBackend,
......@@ -301,6 +302,13 @@ impl TypedEnvBackend for EnvInstance {
self.get_property::<T::Balance>(Engine::rent_allowance)
}
fn rent_params<T>(&mut self) -> Result<RentParams<T>>
where
T: Environment,
{
unimplemented!("off-chain environment does not support rent params")
}
fn block_number<T: Environment>(&mut self) -> Result<T::BlockNumber> {
self.get_property::<T::BlockNumber>(Engine::block_number)
}
......
......@@ -17,7 +17,11 @@ use super::{
OffBalance,
OffTimestamp,
};
use crate::Environment;
use crate::{
engine::off_chain::types::OffRentFraction,
Environment,
};
use sp_arithmetic::PerThing;
/// The chain specification.
pub struct ChainSpec {
......@@ -29,6 +33,14 @@ pub struct ChainSpec {
tombstone_deposit: OffBalance,
/// The targeted block time.
block_time: OffTimestamp,
/// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
deposit_per_storage_byte: OffBalance,
/// The balance every contract needs to deposit to stay alive indefinitely.
deposit_per_contract: OffBalance,
/// The balance a contract needs to deposit per storage item to stay alive indefinitely.
deposit_per_storage_item: OffBalance,
/// The fraction of the deposit costs that should be used as rent per block.
rent_fraction: OffRentFraction,
}
impl ChainSpec {
......@@ -39,6 +51,10 @@ impl ChainSpec {
minimum_balance: OffBalance::uninitialized(),
tombstone_deposit: OffBalance::uninitialized(),
block_time: OffTimestamp::uninitialized(),
deposit_per_storage_byte: OffBalance::uninitialized(),
deposit_per_contract: OffBalance::uninitialized(),
deposit_per_storage_item: OffBalance::uninitialized(),
rent_fraction: OffRentFraction::uninitialized(),
}
}
......@@ -48,6 +64,10 @@ impl ChainSpec {
self.minimum_balance = OffBalance::uninitialized();
self.tombstone_deposit = OffBalance::uninitialized();
self.block_time = OffTimestamp::uninitialized();
self.deposit_per_storage_byte = OffBalance::uninitialized();
self.deposit_per_contract = OffBalance::uninitialized();
self.deposit_per_storage_item = OffBalance::uninitialized();
self.rent_fraction = OffRentFraction::uninitialized();
}
/// Default initialization for the off-chain specification.
......@@ -64,6 +84,20 @@ impl ChainSpec {
.try_initialize::<T::Balance>(&T::Balance::from(16u32))?;
self.block_time
.try_initialize::<T::Timestamp>(&T::Timestamp::from(5u32))?;
let deposit_per_storage_byte = 10_000u32;
self.deposit_per_storage_byte
.try_initialize::<T::Balance>(&T::Balance::from(deposit_per_storage_byte))?;
self.deposit_per_contract
.try_initialize::<T::Balance>(&T::Balance::from(
8 * deposit_per_storage_byte,
))?;
self.deposit_per_storage_item
.try_initialize::<T::Balance>(&T::Balance::from(10_000u32))?;
self.rent_fraction.try_initialize::<T::RentFraction>(
&T::RentFraction::from_rational_approximation(4, 10_000),
)?;
Ok(())
}
......@@ -106,4 +140,36 @@ impl ChainSpec {
{
self.block_time.decode().map_err(Into::into)
}
/// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
pub fn deposit_per_storage_byte<T>(&self) -> Result<T::Balance>
where
T: Environment,
{
self.deposit_per_storage_byte.decode().map_err(Into::into)
}
/// The balance every contract needs to deposit to stay alive indefinitely.
pub fn deposit_per_contract<T>(&self) -> Result<T::Balance>
where
T: Environment,
{
self.deposit_per_contract.decode().map_err(Into::into)
}
/// The balance a contract needs to deposit per storage item to stay alive indefinitely.
pub fn deposit_per_storage_item<T>(&self) -> Result<T::Balance>
where
T: Environment,
{
self.deposit_per_storage_item.decode().map_err(Into::into)
}
/// The fraction of the deposit costs that should be used as rent per block.
pub fn rent_fraction<T>(&self) -> Result<T::RentFraction>
where
T: Environment,
{
self.rent_fraction.decode().map_err(Into::into)
}
}
......@@ -32,6 +32,7 @@ use crate::{
Sha2x256,
},
topics::Topics,
types::RentParams,
EnvBackend,
Environment,
Error,
......@@ -43,7 +44,7 @@ use core::convert::TryInto;
use ink_primitives::Key;
use num_traits::Bounded;
const UNITIALIZED_EXEC_CONTEXT: &str = "unitialized execution context: \
const UNINITIALIZED_EXEC_CONTEXT: &str = "uninitialized execution context: \
a possible source of error could be that you are using `#[test]` instead of `#[ink::test]`.";
impl EnvInstance {
......@@ -51,7 +52,7 @@ impl EnvInstance {
fn callee_account(&self) -> &Account {
let callee = self
.exec_context()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.callee
.clone();
self.accounts
......@@ -63,7 +64,7 @@ impl EnvInstance {
fn callee_account_mut(&mut self) -> &mut Account {
let callee = self
.exec_context()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.callee
.clone();
self.accounts
......@@ -166,7 +167,7 @@ impl EnvBackend for EnvInstance {
where
R: scale::Encode,
{
let ctx = self.exec_context_mut().expect(UNITIALIZED_EXEC_CONTEXT);
let ctx = self.exec_context_mut().expect(UNINITIALIZED_EXEC_CONTEXT);
ctx.output = Some(return_value.encode());
std::process::exit(flags.into_u32() as i32)
}
......@@ -285,7 +286,7 @@ impl EnvInstance {
impl TypedEnvBackend for EnvInstance {
fn caller<T: Environment>(&mut self) -> Result<T::AccountId> {
self.exec_context()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.caller::<T>()
.map_err(|_| scale::Error::from("could not decode caller"))
.map_err(Into::into)
......@@ -293,7 +294,7 @@ impl TypedEnvBackend for EnvInstance {
fn transferred_balance<T: Environment>(&mut self) -> Result<T::Balance> {
self.exec_context()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.transferred_value::<T>()
.map_err(|_| scale::Error::from("could not decode transferred balance"))
.map_err(Into::into)
......@@ -314,7 +315,7 @@ impl TypedEnvBackend for EnvInstance {
fn gas_left<T: Environment>(&mut self) -> Result<T::Balance> {
self.exec_context()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.gas::<T>()
.map_err(|_| scale::Error::from("could not decode gas left"))
.map_err(Into::into)
......@@ -322,7 +323,7 @@ impl TypedEnvBackend for EnvInstance {
fn block_timestamp<T: Environment>(&mut self) -> Result<T::Timestamp> {
self.current_block()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.timestamp::<T>()
.map_err(|_| scale::Error::from("could not decode block time"))
.map_err(Into::into)
......@@ -330,7 +331,7 @@ impl TypedEnvBackend for EnvInstance {
fn account_id<T: Environment>(&mut self) -> Result<T::AccountId> {
self.exec_context()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.callee::<T>()
.map_err(|_| scale::Error::from("could not decode callee"))
.map_err(Into::into)
......@@ -350,9 +351,49 @@ impl TypedEnvBackend for EnvInstance {
.map_err(Into::into)
}
fn rent_params<T>(&mut self) -> Result<RentParams<T>>
where
T: Environment,
{
use crate::arithmetic::Saturating as _;
let total_balance = self.balance::<T>()?;
// the off-chain environment does currently not support reserved balance,
// hence we just use the total balance here.
let free_balance = self.balance::<T>()?;
let deposit_per_contract = self.chain_spec.deposit_per_contract::<T>()?;
let deposit_per_storage_byte = self.chain_spec.deposit_per_storage_byte::<T>()?;
let deposit_per_storage_item = self.chain_spec.deposit_per_storage_item::<T>()?;
let rent_fraction = self.chain_spec.rent_fraction::<T>()?;
let minimum_balance: T::Balance = self.minimum_balance::<T>()?;
let tombstone_deposit = self.tombstone_deposit::<T>()?;
let subsistence_threshold = minimum_balance.saturating_add(tombstone_deposit);
let rent_allowance = self.rent_allowance::<T>()?;
Ok(RentParams {
deposit_per_contract,
deposit_per_storage_byte,
deposit_per_storage_item,
rent_fraction,
subsistence_threshold,
rent_allowance,
total_balance,
free_balance,
storage_size: 0,
code_size: 0,
code_refcount: 0,
_reserved: None,
})
}
fn block_number<T: Environment>(&mut self) -> Result<T::BlockNumber> {
self.current_block()
.expect(UNITIALIZED_EXEC_CONTEXT)
.expect(UNINITIALIZED_EXEC_CONTEXT)
.number::<T>()
.map_err(|_| scale::Error::from("could not decode block number"))
.map_err(Into::into)
......@@ -453,7 +494,7 @@ impl TypedEnvBackend for EnvInstance {
where
T: Environment,
{
let block = self.current_block().expect(UNITIALIZED_EXEC_CONTEXT);
let block = self.current_block().expect(UNINITIALIZED_EXEC_CONTEXT);
Ok((block.random::<T>(subject)?, block.number::<T>()?))
}
}
......@@ -40,6 +40,8 @@ mod type_marker {
#[derive(Debug, Clone)] pub enum OffTimestamp {}
/// Type marker representing an environmental `BlockNumber`.
#[derive(Debug, Clone)] pub enum BlockNumber {}
/// Type marker representing an environmental `RentFraction`.
#[derive(Debug, Clone)] pub enum RentFraction {}
}
/// Off-chain environment account ID type.
......@@ -52,3 +54,5 @@ pub type OffHash = TypedEncoded<type_marker::Hash>;
pub type OffTimestamp = TypedEncoded<type_marker::OffTimestamp>;
/// Off-chain environment block number type.
pub type OffBlockNumber = TypedEncoded<type_marker::BlockNumber>;
/// Off-chain environment rent fraction type.
pub type OffRentFraction = TypedEncoded<type_marker::RentFraction>;
......@@ -341,6 +341,14 @@ mod sys {
output_len_ptr: Ptr32Mut<u32>,
);
}
#[link(wasm_import_module = "__unstable__")]
extern "C" {
pub fn seal_rent_params(
output_ptr: Ptr32Mut<[u8]>,
output_len_ptr: Ptr32Mut<u32>,
);
}
}
fn extract_from_slice(output: &mut &mut [u8], new_len: usize) {
......@@ -589,6 +597,19 @@ pub fn set_rent_allowance(value: &[u8]) {
unsafe { sys::seal_set_rent_allowance(Ptr32::from_slice(value), value.len() as u32) }
}
pub fn rent_params(output: &mut &mut [u8]) {
let mut output_len = output.len() as u32;
{
unsafe {
sys::seal_rent_params(
Ptr32Mut::from_slice(output),
Ptr32Mut::from_ref(&mut output_len),
)
};
}
extract_from_slice(output, output_len as usize);
}
pub fn random(subject: &[u8], output: &mut &mut [u8]) {
let mut output_len = output.len() as u32;
{
......
......@@ -36,6 +36,7 @@ use crate::{
Topics,
TopicsBuilderBackend,
},
types::RentParams,
Clear,
EnvBackend,
Environment,
......@@ -355,6 +356,13 @@ impl TypedEnvBackend for EnvInstance {
ext::set_rent_allowance(&buffer[..])
}
fn rent_params<T>(&mut self) -> Result<RentParams<T>>
where
T: Environment,
{
self.get_property::<RentParams<T>>(ext::rent_params)
}
fn invoke_contract<T, Args>(
&mut self,
call_params: &CallParams<T, Args, ()>,
......
......@@ -97,5 +97,7 @@ pub use self::{
Environment,
Hash,
NoChainExtension,
Perbill,
RentParams,
},
};
......@@ -43,6 +43,8 @@ use scale::{
};
#[cfg(feature = "std")]
use scale_info::TypeInfo;
use sp_arithmetic::PerThing;
pub use sp_arithmetic::Perbill;
/// The environmental types usable by contracts defined with ink!.
pub trait Environment {
......@@ -100,6 +102,9 @@ pub trait Environment {
///
/// [chain_extension]: https://paritytech.github.io/ink/ink_lang/attr.chain_extension.html
type ChainExtension;
/// The fraction of the deposit costs that should be used as rent per block.
type RentFraction: 'static + scale::Codec + Clone + PartialEq + Eq + Ord + PerThing;
}
/// Placeholder for chains that have no defined chain extension.
......@@ -119,6 +124,7 @@ impl Environment for DefaultEnvironment {
type Timestamp = Timestamp;
type BlockNumber = BlockNumber;
type ChainExtension = NoChainExtension;
type RentFraction = RentFraction;
}
/// The default balance type.
......@@ -130,6 +136,9 @@ pub type Timestamp = u64;
/// The default block number type.
pub type BlockNumber = u32;
/// The default rent fraction type.
pub type RentFraction = Perbill;
/// The default environment `AccountId` type.
///
/// # Note
......@@ -236,3 +245,72 @@ impl Clear for Hash {
Self(<[u8; 32] as Clear>::clear())
}
}
/// Information needed for rent calculations that can be requested by a contract.
#[derive(scale::Decode)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RentParams<T: Environment> {
/// The total balance of the contract. Includes the balance transferred from the caller.
pub total_balance: T::Balance,
/// The free balance of the contract, i.e. the portion of the contract's balance
/// that is not reserved. Includes the balance transferred from the caller.
pub free_balance: T::Balance,
/// Subsistence threshold is the extension of the minimum balance (aka existential deposit)
/// by the tombstone deposit, required for leaving a tombstone.
///
/// Rent or any contract initiated balance transfer mechanism cannot make the balance lower
/// than the subsistence threshold in order to guarantee that a tombstone is created.
///
/// The only way to completely kill a contract without a tombstone is calling `seal_terminate`.
pub subsistence_threshold: T::Balance,
/// The balance every contract needs to deposit to stay alive indefinitely.
///
/// This is different from the tombstone deposit because this only needs to be
/// deposited while the contract is alive. Costs for additional storage are added to
/// this base cost.
///
/// This is a simple way to ensure that contracts with empty storage eventually get deleted by
/// making them pay rent. This creates an incentive to remove them early in order to save rent.
pub deposit_per_contract: T::Balance,
/// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
///
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
/// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
/// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
/// then it would pay 500 BU/day.
pub deposit_per_storage_byte: T::Balance,
/// The balance a contract needs to deposit per storage item to stay alive indefinitely.
///
/// It works as [`Self::deposit_per_storage_byte`] but for storage items.
pub deposit_per_storage_item: T::Balance,
/// The contract's rent allowance, the rent mechanism cannot consume more than this.
pub rent_allowance: T::Balance,
/// The fraction of the deposit costs that should be used as rent per block.
///
/// When a contract doesn't have enough balance deposited to stay alive indefinitely
/// it needs to pay per block for the storage it consumes that is not covered by the
/// deposit. This determines how high this rent payment is per block as a fraction
/// of the deposit costs.
pub rent_fraction: T::RentFraction,
/// The total number of bytes used by this contract.
///
/// It is a sum of each key-value pair stored by this contract.
pub storage_size: u32,
/// Sum of instrumented and pristine code length.
pub code_size: u32,
/// The number of contracts using this executable.
pub code_refcount: u32,
/// Reserved for backwards compatible changes to this data structure.
pub _reserved: Option<()>,
}
......@@ -152,6 +152,7 @@ use proc_macro::TokenStream;
/// type Hash = [u8; 32];
/// type Timestamp = u64;
/// type BlockNumber = u32;
/// type RentFraction = ::ink_env::Perbill;
/// type ChainExtension = ::ink_env::NoChainExtension;
/// }
/// ```
......@@ -171,6 +172,7 @@ use proc_macro::TokenStream;
/// # type Timestamp = u64;
/// # type BlockNumber = u32;
/// # type ChainExtension = ::ink_env::NoChainExtension;
/// # type RentFraction = ::ink_env::Perbill;
/// # }
/// #
/// # #[ink(storage)]
......@@ -909,6 +911,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
/// type Hash = <DefaultEnvironment as Environment>::Hash;
/// type BlockNumber = <DefaultEnvironment as Environment>::BlockNumber;
/// type Timestamp = <DefaultEnvironment as Environment>::Timestamp;
/// type RentFraction = <DefaultEnvironment as Environment>::RentFraction;
///
/// type ChainExtension = RuntimeReadWrite;
/// }
......@@ -1052,6 +1055,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
/// # type Hash = <ink_env::DefaultEnvironment as ink_env::Environment>::Hash;
/// # type BlockNumber = <ink_env::DefaultEnvironment as ink_env::Environment>::BlockNumber;
/// # type Timestamp = <ink_env::DefaultEnvironment as ink_env::Environment>::Timestamp;
/// # type RentFraction = <ink_env::DefaultEnvironment as ink_env::Environment>::RentFraction;
/// #
/// # type ChainExtension = RuntimeReadWrite;
/// # }
......
......@@ -117,6 +117,7 @@ impl Environment for CustomEnvironment {
type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;
type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
type RentFraction = <ink_env::DefaultEnvironment as Environment>::RentFraction;
type ChainExtension = RuntimeReadWrite;
}
......
......@@ -24,6 +24,7 @@ use ink_env::{
HashOutput,
},
Environment,
RentParams,
Result,
};
use ink_primitives::Key;
......@@ -180,6 +181,15 @@ where
ink_env::rent_allowance::<T>().expect("couldn't decode contract rent allowance")
}
/// Returns information needed for rent calculations.
///
/// # Note
///
/// For more details visit: [`ink_env::RentParams`]
pub fn rent_params(self) -> RentParams<T> {
ink_env::rent_params::<T>().expect("couldn't decode contract rent params")
}
/// Returns the current block number.
///
/// # Note
......
......@@ -60,6 +60,7 @@ impl Environment for CustomEnvironment {
type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;
type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
type RentFraction = <ink_env::DefaultEnvironment as Environment>::RentFraction;
type ChainExtension = FetchRandom;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment