diff --git a/CHANGELOG.md b/CHANGELOG.md index ca27953caa..5c969c2f3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Version 6.0.0-beta ### Added +- Implements the API for the `pallet-revive` host functions `gas_price`, `call_data_size`, `return_data_size`, `gas_left` - [#2694](https://github.com/use-ink/ink/pull/2694) - Implements the API for the `pallet-revive` host function `gas_limit` - [#2691](https://github.com/use-ink/ink/pull/2691) - Implements the API for the `pallet-revive` host function `to_account_id` - [#2578](https://github.com/use-ink/ink/pull/2578) - Add `#[ink::contract_ref]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648) @@ -2635,4 +2636,4 @@ impl Contract { This is useful if the `impl` block itself does not contain any ink! constructors or messages, but you still need to access some of the "magic" provided by ink!. In the example above, you would not have -access to `emit_event` without `#[ink(impl)]`. +access to `emit_event` without `#[ink(impl)]`. \ No newline at end of file diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 3f0764951e..a64c58cdd4 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -76,12 +76,41 @@ pub fn caller() -> Address { } /// Returns the block's `ref_time` limit. +/// [GASLIMIT](https://www.evm.codes/?fork=cancun#45) opcode. /// /// See for more information. pub fn gas_limit() -> u64 { ::on_instance(TypedEnvBackend::gas_limit) } +/// Returns the price per `ref_time`, akin to the EVM +/// [GASPRICE](https://www.evm.codes/?fork=cancun#3a) opcode. +/// +/// See for more information. +pub fn gas_price() -> u64 { + ::on_instance(TypedEnvBackend::gas_price) +} + +/// Returns the amount of gas left. +/// This is the `ref_time` left. +/// +/// See for more information. +pub fn gas_left() -> u64 { + ::on_instance(TypedEnvBackend::gas_left) +} + +/// Returns the total size of the contract call input data, akin to the EVM +/// [CALLDATASIZE](https://www.evm.codes/?fork=cancun#36) opcode. +pub fn call_data_size() -> u64 { + ::on_instance(TypedEnvBackend::call_data_size) +} + +/// Returns the size of the returned data of the last contract call or instantiation, +/// akin to the EVM [RETURNDATASIZE](https://www.evm.codes/?fork=cancun#3d) opcode. +pub fn return_data_size() -> u64 { + ::on_instance(TypedEnvBackend::return_data_size) +} + /// Returns the transferred value for the contract execution. /// /// # Errors diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index e6ac84e87c..a9b695abb5 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -231,12 +231,49 @@ pub trait TypedEnvBackend: EnvBackend { fn caller(&mut self) -> Address; /// Returns the block's `ref_time` limit. + /// This is akin to the EVM [GASLIMIT](https://www.evm.codes/?fork=cancun#45) opcode. /// /// # Note /// /// For more details visit: [`gas_limit`][`crate::gas_limit`] fn gas_limit(&mut self) -> u64; + /// Returns the price per `ref_time`, akin to the EVM + /// [GASPRICE](https://www.evm.codes/?fork=cancun#3a) opcode. + /// + /// See for more information. + /// + /// # Note + /// + /// For more details visit: [`gas_price`][`crate::gas_price`] + fn gas_price(&mut self) -> u64; + + /// Returns the amount of gas left. + /// This is the `ref_time` left. + /// + /// See for more information. + /// + /// # Note + /// + /// For more details visit: [`gas_left`][`crate::gas_left`] + fn gas_left(&mut self) -> u64; + + /// Returns the total size of the contract call input data. + /// This is akin to the EVM [CALLDATASIZE](https://www.evm.codes/?fork=cancun#36) opcode. + /// + /// # Note + /// + /// For more details visit: [`call_data_size`][`crate::call_data_size] + fn call_data_size(&mut self) -> u64; + + /// Returns the size of the returned data of the last contract call or instantiation. + /// This is akin to the EVM [RETURNDATASIZE](https://www.evm.codes/?fork=cancun#3d) opcode. + /// + /// # Note + /// + /// For more details visit: [`return_data_size`][`crate::return_data_size] + fn return_data_size(&mut self) -> u64; + /// Returns the transferred value for the contract execution. /// /// # Note diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index ac92e776a9..2a2b018e1d 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -556,6 +556,22 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("not implemented, the off-chain environment will be removed"); } + fn gas_price(&mut self) -> u64 { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn gas_left(&mut self) -> u64 { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn call_data_size(&mut self) -> u64 { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn return_data_size(&mut self) -> u64 { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + fn transferred_value(&mut self) -> U256 { self.get_property(Engine::value_transferred) .unwrap_or_else(|error| { diff --git a/crates/env/src/engine/on_chain/pallet_revive.rs b/crates/env/src/engine/on_chain/pallet_revive.rs index b67ffa3765..853c2f3178 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -937,6 +937,24 @@ impl TypedEnvBackend for EnvInstance { ext::gas_limit() } + fn gas_price(&mut self) -> u64 { + ext::gas_price() + } + + fn gas_left(&mut self) -> u64 { + // TODO: Change to `ext::gas_left()` when `pallet-revive-uapi` is updated. + // Ref: https://github.com/paritytech/polkadot-sdk/pull/9968 + ext::ref_time_left() + } + + fn call_data_size(&mut self) -> u64 { + ext::call_data_size() + } + + fn return_data_size(&mut self) -> u64 { + ext::return_data_size() + } + fn transferred_value(&mut self) -> U256 { let mut scope = self.scoped_buffer(); let u256: &mut [u8; 32] = scope.take(32).try_into().unwrap(); diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 0a11ccc067..81866edf60 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -110,7 +110,8 @@ where ink_env::caller() } - /// Returns the block ref_time limit. + /// Returns the block's `ref_time` limit, akin to the EVM + /// [GASLIMIT](https://www.evm.codes/?fork=cancun#45) opcode. /// /// # Example /// @@ -141,6 +142,138 @@ where ink_env::gas_limit() } + /// Returns the price per `ref_time`, akin to the EVM + /// [GASPRICE](https://www.evm.codes/?fork=cancun#3a) opcode. + /// + /// See for more information. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn get_gas_price(&self) -> u64 { + /// self.env().gas_price() + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::gas_price`] + pub fn gas_price(self) -> u64 { + ink_env::gas_price() + } + + /// Returns the amount of gas left. + /// This is the `ref_time` left. + /// + /// See for more information. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn get_gas_left(&self) -> u64 { + /// self.env().gas_left() + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::gas_left`] + pub fn gas_left(self) -> u64 { + ink_env::gas_left() + } + + /// Returns the total size of the contract call input data. + /// This is akin to the EVM [CALLDATASIZE](https://www.evm.codes/?fork=cancun#36) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn get_call_data_size(&self) -> u64 { + /// self.env().call_data_size() + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::call_data_size`] + pub fn call_data_size(self) -> u64 { + ink_env::call_data_size() + } + + /// Returns the size of the returned data of the last contract call or instantiation. + /// This is akin to the EVM [RETURNDATASIZE](https://www.evm.codes/?fork=cancun#3d) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn get_return_data_size(&self) -> u64 { + /// self.env().return_data_size() + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::return_data_size`] + pub fn return_data_size(self) -> u64 { + ink_env::return_data_size() + } + /// Returns the transferred value for the contract execution. /// /// # Example diff --git a/crates/ink/tests/ui/contract/pass/env-access.rs b/crates/ink/tests/ui/contract/pass/env-access.rs index e2afe0b8b4..0d515966a6 100644 --- a/crates/ink/tests/ui/contract/pass/env-access.rs +++ b/crates/ink/tests/ui/contract/pass/env-access.rs @@ -15,6 +15,10 @@ mod contract { let _ = Self::env().caller(); let _ = Self::env().minimum_balance(); let _ = Self::env().gas_limit(); + let _ = Self::env().gas_price(); + let _ = Self::env().gas_left(); + let _ = Self::env().call_data_size(); + let _ = Self::env().return_data_size(); let _ = Self::env().transferred_value(); let _ = Self::env().weight_to_fee(0); Self {} @@ -29,6 +33,10 @@ mod contract { let _ = self.env().caller(); let _ = self.env().minimum_balance(); let _ = self.env().gas_limit(); + let _ = self.env().gas_price(); + let _ = self.env().gas_left(); + let _ = self.env().call_data_size(); + let _ = self.env().return_data_size(); let _ = self.env().transferred_value(); let _ = self.env().weight_to_fee(0); } diff --git a/integration-tests/internal/data-hostfns/Cargo.toml b/integration-tests/internal/data-hostfns/Cargo.toml new file mode 100755 index 0000000000..7897a057cf --- /dev/null +++ b/integration-tests/internal/data-hostfns/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "data_hostfns" +description = "E2E tests for data related host functions" +version = "6.0.0-alpha.4" +authors = ["Use Ink "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false, features = ["unstable-hostfn"] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" diff --git a/integration-tests/internal/data-hostfns/lib.rs b/integration-tests/internal/data-hostfns/lib.rs new file mode 100644 index 0000000000..a28b7921b0 --- /dev/null +++ b/integration-tests/internal/data-hostfns/lib.rs @@ -0,0 +1,95 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +mod data_hostfns { + #[ink(storage)] + pub struct DataHostfns {} + + impl DataHostfns { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Checks that the host function `call_data_size` works + #[ink(message)] + pub fn call_data_size(&self) -> u64 { + self.env().call_data_size() + } + + /// Checks that the host function `return_data_size` works + #[ink(message)] + pub fn return_data_size(&self) -> u64 { + self.env().return_data_size() + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_call_data_size_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let contract = client + .instantiate( + "data_hostfns", + &ink_e2e::alice(), + &mut DataHostfnsRef::new(), + ) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let call_res = client + .call(&ink_e2e::alice(), &call_builder.call_data_size()) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + assert!(call_res.return_value() > 0); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_return_data_size_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let contract = client + .instantiate( + "data_hostfns", + &ink_e2e::alice(), + &mut DataHostfnsRef::new(), + ) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let call_res = client + .call(&ink_e2e::alice(), &call_builder.return_data_size()) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + assert!(call_res.return_value() == 0); // no calls were made, thus is 0 + + Ok(()) + } + } +} diff --git a/integration-tests/internal/gas-hostfns/lib.rs b/integration-tests/internal/gas-hostfns/lib.rs index 8c5046541a..ea9ad1416f 100644 --- a/integration-tests/internal/gas-hostfns/lib.rs +++ b/integration-tests/internal/gas-hostfns/lib.rs @@ -17,6 +17,18 @@ mod gas_hostfns { pub fn gas_limit(&self) -> u64 { self.env().gas_limit() } + + /// Checks that the host function `gas_price` works + #[ink(message)] + pub fn gas_price(&self) -> u64 { + self.env().gas_price() + } + + /// Checks that the host function `gas_left` works + #[ink(message)] + pub fn gas_left(&self) -> u64 { + self.env().gas_left() + } } #[cfg(all(test, feature = "e2e-tests"))] @@ -51,5 +63,57 @@ mod gas_hostfns { Ok(()) } + + #[ink_e2e::test] + async fn e2e_gas_price_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let contract = client + .instantiate("gas_hostfns", &ink_e2e::alice(), &mut GasHostfnsRef::new()) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let call_res = client + .call(&ink_e2e::alice(), &call_builder.gas_price()) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + assert!(call_res.return_value() > 0); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_gas_left_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let contract = client + .instantiate("gas_hostfns", &ink_e2e::alice(), &mut GasHostfnsRef::new()) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let call_res = client + .call(&ink_e2e::alice(), &call_builder.gas_left()) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + assert!(call_res.return_value() > 0); + + Ok(()) + } } }