runtime.rs 62 KiB
Newer Older
		ctx.charge_gas(RuntimeCosts::Return(data_len))?;
			data: ctx.read_sandbox_memory(data_ptr, data_len)?,
	// Stores the address of the caller 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.
	// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the
	// extrinsic will be returned. Otherwise, if this call is initiated by another contract then the
	// address of the contract will be returned. The value is encoded as T::AccountId.
	[seal0] seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::Caller)?;
			out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged
	// Stores the address of the current contract 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.
	[seal0] seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::Address)?;
			out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged
	// Stores the price for the specified amount of gas 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 T::Balance.
	//
	// # Note
	// It is recommended to avoid specifying very small values for `gas` as the prices for a single
	// gas can be smaller than one.
	[seal0] seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::WeightToFee)?;
			out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, already_charged
	// Stores the amount of gas left 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 Gas.
	[seal0] seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::GasLeft)?;
		let gas_left = &ctx.ext.gas_meter().gas_left().encode();
			out_ptr, out_len_ptr, &gas_left, false, already_charged,
	// Stores the balance of the current account 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 T::Balance.
	[seal0] seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::Balance)?;
			out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged
	// Stores the value transferred along with this call or as endowment 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 T::Balance.
	[seal0] seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::ValueTransferred)?;
			out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, already_charged
	// Stores a random number for the current block and the given subject 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 T::Hash.
	//
	// # Deprecation
	//
	// This function is deprecated. Users should migrate to the version in the "seal1" module.
	[seal0] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::Random)?;
		if subject_len > ctx.ext.schedule().limits.subject_len {
			Err(Error::<E::T>::RandomSubjectTooLong)?;
		}
		let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?;
		Ok(ctx.write_sandbox_output(
			out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).0.encode(), false, already_charged
		)?)
	},

	// Stores a random number for the current block and the given subject 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 (T::Hash, T::BlockNumber).
	//
	// # Changes from v0
	//
	// In addition to the seed it returns the block number since which it was determinable
	// by chain observers.
	//
	// # Note
	//
	// The returned seed should only be used to distinguish commitments made before
	// the returned block number. If the block number is too early (i.e. commitments were
	// made afterwards), then ensure no further commitments may be made and repeatedly
	// call this on later blocks until the block number returned is later than the latest
	// commitment.
	[seal1] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::Random)?;
		if subject_len > ctx.ext.schedule().limits.subject_len {
		let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?;
			out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, already_charged
	// Load the latest block timestamp 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.
	[seal0] seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::Now)?;
			out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged
	// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer.
	// The data is encoded as T::Balance.
	[seal0] seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::MinimumBalance)?;
			out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged
	// Stores the tombstone deposit 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 T::Balance.
	//
	// # Note
	//
	// The tombstone deposit is on top of the existential deposit. So in order for
	// a contract to leave a tombstone the balance of the contract must not go
	// below the sum of existential deposit and the tombstone deposit. The sum
	// is commonly referred as subsistence threshold in code.
	[seal0] seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::TombstoneDeposit)?;
			out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, already_charged
	// Try to restore the given destination contract sacrificing the caller.
	// This function will compute a tombstone hash from the caller's storage and the given code hash
	// and if the hash matches the hash found in the tombstone at the specified address - kill
	// the caller contract and restore the destination contract and set the specified `rent_allowance`.
	// All caller's funds are transferred to the destination.
	// The tombstone hash is derived as `hash(code_hash, storage_root_hash)`. In order to match
	// this hash to its own hash the restorer must make its storage equal to the one of the
	// evicted destination contract. In order to allow for additional storage items in the
	// restoring contract a delta can be specified to this function. All keys specified as
	// delta are disregarded when calculating the storage root hash.
	// On success, the destination contract is restored. This function is diverging and
	// stops execution even on success.
	// - `dest_ptr`, `dest_len` - the pointer and the length of a buffer that encodes `T::AccountId`
	//    with the address of the to be restored contract.
	// - `code_hash_ptr`, `code_hash_len` - the pointer and the length of a buffer that encodes
	//    a code hash of the to be restored contract.
	// - `rent_allowance_ptr`, `rent_allowance_len` - the pointer and the length of a buffer that
	//    encodes the rent allowance that must be set in the case of successful restoration.
	// - `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys
	//    laid out sequentially.
	// - There is no tombstone at the destination address.
	// - Tombstone hashes do not match.
	// - The calling contract is already present on the call stack.
	// - The supplied code_hash does not exist on-chain.
	[seal0] seal_restore_to(
		ctx,
		dest_ptr: u32,
		dest_len: u32,
		code_hash_ptr: u32,
		code_hash_len: u32,
		rent_allowance_ptr: u32,
		rent_allowance_len: u32,
		delta_ptr: u32,
		delta_count: u32
	) => {
		ctx.charge_gas(RuntimeCosts::RestoreTo(delta_count))?;
		let dest: <<E as Ext>::T as frame_system::Config>::AccountId =
			ctx.read_sandbox_memory_as(dest_ptr, dest_len)?;
		let code_hash: CodeHash<<E as Ext>::T> =
			ctx.read_sandbox_memory_as(code_hash_ptr, code_hash_len)?;
		let rent_allowance: BalanceOf<<E as Ext>::T> =
			ctx.read_sandbox_memory_as(rent_allowance_ptr, rent_allowance_len)?;
			// We can eagerly allocate because we charged for the complete delta count already
			// We still need to make sure that the allocation isn't larger than the memory
			// allocator can handle.
			ensure!(
				delta_count
					.saturating_mul(KEY_SIZE as u32) <= ctx.ext.schedule().limits.max_memory_size(),
				Error::<E::T>::OutOfBounds,
			);
			let mut delta = vec![[0; KEY_SIZE]; delta_count as usize];
			let mut key_ptr = delta_ptr;

			for i in 0..delta_count {
				// Read the delta into the provided buffer
				// This cannot panic because of the loop condition
				ctx.read_sandbox_memory_into_buf(key_ptr, &mut delta[i as usize])?;

				// Offset key_ptr to the next element.
				key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or(Error::<E::T>::OutOfBounds)?;
		let max_len = <E::T as Config>::Schedule::get().limits.code_len;
		let charged = ctx.charge_gas(RuntimeCosts::RestoreToSurchargeCodeSize {
			caller_code: max_len,
			tombstone_code: max_len,
		})?;
		let (result, caller_code, tombstone_code) = match ctx.ext.restore_to(
			dest, code_hash, rent_allowance, delta
		) {
			Ok((code, tomb)) => (Ok(()), code, tomb),
			Err((err, code, tomb)) => (Err(err), code, tomb),
		};
		ctx.adjust_gas(charged, RuntimeCosts::RestoreToSurchargeCodeSize {
			caller_code,
			tombstone_code,
		});
		result?;
	// Deposit a contract event with the data buffer and optional list of topics. There is a limit
	// on the maximum number of topics specified by `event_topics`.
	//
	// - topics_ptr - a pointer to the buffer of topics encoded as `Vec<T::Hash>`. The value of this
	//   is ignored if `topics_len` is set to 0. The topics list can't contain duplicates.
	// - topics_len - the length of the topics buffer. Pass 0 if you want to pass an empty vector.
	// - data_ptr - a pointer to a raw data buffer which will saved along the event.
	// - data_len - the length of the data buffer.
	[seal0] seal_deposit_event(
		ctx,
		topics_ptr: u32,
		topics_len: u32,
		data_ptr: u32,
		data_len: u32
	) => {
		fn has_duplicates<T: Ord>(items: &mut Vec<T>) -> bool {
			// # Warning
			//
			// Unstable sorts are non-deterministic across architectures. The usage here is OK
			// because we are rejecting duplicates which removes the non determinism.
			items.sort_unstable();
			// Find any two consecutive equal elements.
			items.windows(2).any(|w| {
				match &w {
					&[a, b] => a == b,
					_ => false,
				}
			})
		}

		let num_topic = topics_len
			.checked_div(sp_std::mem::size_of::<TopicOf<E::T>>() as u32)
		ctx.charge_gas(RuntimeCosts::DepositEvent {
			num_topic,
			len: data_len,
		})?;
		if data_len > ctx.ext.max_value_size() {
		let mut topics: Vec::<TopicOf<<E as Ext>::T>> = match topics_len {
			0 => Vec::new(),
			_ => ctx.read_sandbox_memory_as(topics_ptr, topics_len)?,
		// If there are more than `event_topics`, then trap.
		if topics.len() > ctx.ext.schedule().limits.event_topics as usize {
		}

		// Check for duplicate topics. If there are any, then trap.
		// Complexity O(n * log(n)) and no additional allocations.
		// This also sorts the topics.
		if has_duplicates(&mut topics) {
		let event_data = ctx.read_sandbox_memory(data_ptr, data_len)?;

		ctx.ext.deposit_event(topics, event_data);
	// Set rent allowance of the contract
	//
	// - value_ptr: a pointer to the buffer with value, how much to allow for rent
	//   Should be decodable as a `T::Balance`. Traps otherwise.
	// - value_len: length of the value buffer.
	[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
		ctx.charge_gas(RuntimeCosts::SetRentAllowance)?;
		let value: BalanceOf<<E as Ext>::T> =
			ctx.read_sandbox_memory_as(value_ptr, value_len)?;
		ctx.ext.set_rent_allowance(value);

		Ok(())
	},

	// Stores the rent allowance 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 T::Balance.
	[seal0] seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::RentAllowance)?;
		let rent_allowance = ctx.ext.rent_allowance().encode();
			out_ptr, out_len_ptr, &rent_allowance, false, already_charged
	// Stores the current block number of the current contract 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.
	[seal0] seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::BlockNumber)?;
			out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged
	// Computes the SHA2 256-bit hash on the given input buffer.
	//
	// Returns the result directly into the given output buffer.
	//
	// # Note
	//
	// - The `input` and `output` buffer may overlap.
	// - The output buffer is expected to hold at least 32 bytes (256 bits).
	// - It is the callers responsibility to provide an output buffer that
	//   is large enough to hold the expected amount of bytes returned by the
	//   chosen hash function.
	//
	// # Parameters
	//
	// - `input_ptr`: the pointer into the linear memory where the input
	//                data is placed.
	// - `input_len`: the length of the input data in bytes.
	// - `output_ptr`: the pointer into the linear memory where the output
	//                 data is placed. The function will write the result
	//                 directly into this buffer.
	[seal0] seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::HashSha256(input_len))?;
		Ok(ctx.compute_hash_on_intermediate_buffer(sha2_256, input_ptr, input_len, output_ptr)?)
	},

	// Computes the KECCAK 256-bit hash on the given input buffer.
	//
	// Returns the result directly into the given output buffer.
	//
	// # Note
	//
	// - The `input` and `output` buffer may overlap.
	// - The output buffer is expected to hold at least 32 bytes (256 bits).
	// - It is the callers responsibility to provide an output buffer that
	//   is large enough to hold the expected amount of bytes returned by the
	//   chosen hash function.
	//
	// # Parameters
	//
	// - `input_ptr`: the pointer into the linear memory where the input
	//                data is placed.
	// - `input_len`: the length of the input data in bytes.
	// - `output_ptr`: the pointer into the linear memory where the output
	//                 data is placed. The function will write the result
	//                 directly into this buffer.
	[seal0] seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::HashKeccak256(input_len))?;
		Ok(ctx.compute_hash_on_intermediate_buffer(keccak_256, input_ptr, input_len, output_ptr)?)
	},

	// Computes the BLAKE2 256-bit hash on the given input buffer.
	//
	// Returns the result directly into the given output buffer.
	//
	// # Note
	//
	// - The `input` and `output` buffer may overlap.
	// - The output buffer is expected to hold at least 32 bytes (256 bits).
	// - It is the callers responsibility to provide an output buffer that
	//   is large enough to hold the expected amount of bytes returned by the
	//   chosen hash function.
	//
	// # Parameters
	//
	// - `input_ptr`: the pointer into the linear memory where the input
	//                data is placed.
	// - `input_len`: the length of the input data in bytes.
	// - `output_ptr`: the pointer into the linear memory where the output
	//                 data is placed. The function will write the result
	//                 directly into this buffer.
	[seal0] seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::HashBlake256(input_len))?;
		Ok(ctx.compute_hash_on_intermediate_buffer(blake2_256, input_ptr, input_len, output_ptr)?)
	},

	// Computes the BLAKE2 128-bit hash on the given input buffer.
	//
	// Returns the result directly into the given output buffer.
	//
	// # Note
	//
	// - The `input` and `output` buffer may overlap.
	// - The output buffer is expected to hold at least 16 bytes (128 bits).
	// - It is the callers responsibility to provide an output buffer that
	//   is large enough to hold the expected amount of bytes returned by the
	//   chosen hash function.
	//
	// # Parameters
	//
	// - `input_ptr`: the pointer into the linear memory where the input
	//                data is placed.
	// - `input_len`: the length of the input data in bytes.
	// - `output_ptr`: the pointer into the linear memory where the output
	//                 data is placed. The function will write the result
	//                 directly into this buffer.
	[seal0] seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
		ctx.charge_gas(RuntimeCosts::HashBlake128(input_len))?;
		Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?)

	// Call into the chain extension provided by the chain if any.
	//
	// Handling of the input values is up to the specific chain extension and so is the
	// return value. The extension can decide to use the inputs as primitive inputs or as
	// in/out arguments by interpreting them as pointers. Any caller of this function
	// must therefore coordinate with the chain that it targets.
	//
	// # Note
	//
	// If no chain extension exists the contract will trap with the `NoChainExtension`
	// module error.
	[seal0] seal_call_chain_extension(
		ctx,
		func_id: u32,
		input_ptr: u32,
		input_len: u32,
		output_ptr: u32,
		output_len_ptr: u32
	) -> u32 => {
		use crate::chain_extension::{ChainExtension, Environment, RetVal};
		if <E::T as Config>::ChainExtension::enabled() == false {
			Err(Error::<E::T>::NoChainExtension)?;
		}
		let env = Environment::new(ctx, input_ptr, input_len, output_ptr, output_len_ptr);
		match <E::T as Config>::ChainExtension::call(func_id, env)? {
			RetVal::Converging(val) => Ok(val),
			RetVal::Diverging{flags, data} => Err(TrapReason::Return(ReturnData {
				flags: flags.bits(),
				data,
			})),
		}
	},
	// Emit a custom debug message.
	//
	// No newlines are added to the supplied message.
	// Specifying invalid UTF-8 triggers a trap.
	//
	// This is a no-op if debug message recording is disabled which is always the case
	// when the code is executing on-chain. The message is interpreted as UTF-8 and
	// appended to the debug buffer which is then supplied to the calling RPC client.
	//
	// # Note
	//
	// Even though no action is taken when debug message recording is disabled there is still
	// a non trivial overhead (and weight cost) associated with calling this function. Contract
	// languages should remove calls to this function (either at runtime or compile time) when
	// not being executed as an RPC. For example, they could allow users to disable logging
	// through compile time flags (cargo features) for on-chain deployment. Additionally, the
	// return value of this function can be cached in order to prevent further calls at runtime.
	[__unstable__] seal_debug_message(ctx, str_ptr: u32, str_len: u32) -> ReturnCode => {
		ctx.charge_gas(RuntimeCosts::DebugMessage)?;
		if ctx.ext.append_debug_buffer("") {
			let data = ctx.read_sandbox_memory(str_ptr, str_len)?;
			let msg = core::str::from_utf8(&data)
				.map_err(|_| <Error<E::T>>::DebugMessageInvalidUTF8)?;
			ctx.ext.append_debug_buffer(msg);
			return Ok(ReturnCode::Success);
		}
		Ok(ReturnCode::LoggingDisabled)
	},

	// Stores the rent params 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::exec::RentParams`].
	//
	// # Note
	//
	// The returned information was collected and cached when the current contract call
	// started execution. Any change to those values that happens due to actions of the
	// current call or contracts that are called by this contract are not considered.
	//
	// # Unstable
	//
	// This function is unstable and subject to change (or removal) in the future. Do not
	// deploy a contract using it to a production chain.
	[__unstable__] seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
		Ok(ctx.write_sandbox_output(
			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
	//
	// # Unstable
	//
	// This function is unstable and subject to change (or removal) in the future. Do not
	// deploy a contract using it to a production chain.
	[__unstable__] seal_rent_status(ctx, at_refcount: u32, out_ptr: u32, out_len_ptr: u32) => {
		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
		)?)
	},