diff --git a/Cargo.lock b/Cargo.lock index 29a52b1a5b..5751fd4715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1286,8 +1286,7 @@ dependencies = [ [[package]] name = "evm" version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49a4e11987c51220aa89dbe1a5cc877f5079fa6864c0a5b4533331db44e9365" +source = "git+https://github.com/oasisprotocol/evm?branch=oasis#13a30bfc1a10074f3f1676988aeb6ada76c65553" dependencies = [ "auto_impl", "environmental", @@ -1307,8 +1306,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1f13264b044cb66f0602180f0bc781c29accb41ff560669a3ec15858d5b606" +source = "git+https://github.com/oasisprotocol/evm?branch=oasis#13a30bfc1a10074f3f1676988aeb6ada76c65553" dependencies = [ "parity-scale-codec", "primitive-types", @@ -1319,8 +1317,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d43eadc395bd1a52990787ca1495c26b0248165444912be075c28909a853b8c" +source = "git+https://github.com/oasisprotocol/evm?branch=oasis#13a30bfc1a10074f3f1676988aeb6ada76c65553" dependencies = [ "environmental", "evm-core", @@ -1331,8 +1328,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa5b32f59ec582a5651978004e5c784920291263b7dcb6de418047438e37f4f" +source = "git+https://github.com/oasisprotocol/evm?branch=oasis#13a30bfc1a10074f3f1676988aeb6ada76c65553" dependencies = [ "auto_impl", "environmental", diff --git a/runtime-sdk/modules/evm/Cargo.toml b/runtime-sdk/modules/evm/Cargo.toml index e47a36d016..311c058f07 100644 --- a/runtime-sdk/modules/evm/Cargo.toml +++ b/runtime-sdk/modules/evm/Cargo.toml @@ -30,7 +30,8 @@ rand_core = { version = "0.6.4", default-features = false } # Ethereum. ethabi = { version = "18.0.0", default-features = false, features = ["std"] } ethereum = "0.14" -evm = "0.39.1" +# evm = "0.39.1" +evm = { git = "https://github.com/oasisprotocol/evm", branch = "oasis" } fixed-hash = "0.8.0" primitive-types = { version = "0.12", default-features = false, features = ["rlp", "num-traits"] } rlp = "0.5.2" @@ -47,7 +48,7 @@ oasis-runtime-sdk = { path = "../..", features = ["test"] } rand = "0.7.3" serde = { version = "1.0.144", features = ["derive"] } serde_json = { version = "1.0.87", features = ["raw_value"] } -ethabi = { version = "18.0.0", default-features = false, features = ["std", "full-serde"]} +ethabi = { version = "18.0.0", default-features = false, features = ["std", "full-serde"] } [features] default = [] @@ -60,6 +61,7 @@ harness = false [[bin]] name = "fuzz-precompile" path = "fuzz/precompile.rs" +required-features = ["test"] [[bin]] name = "fuzz-precompile-corpus" diff --git a/runtime-sdk/modules/evm/src/precompile/gas.rs b/runtime-sdk/modules/evm/src/precompile/gas.rs new file mode 100644 index 0000000000..ef37b33456 --- /dev/null +++ b/runtime-sdk/modules/evm/src/precompile/gas.rs @@ -0,0 +1,220 @@ +use ethabi::{ParamType, Token}; +use evm::{ + executor::stack::{PrecompileFailure, PrecompileHandle, PrecompileOutput}, + ExitError, ExitSucceed, +}; + +use super::PrecompileResult; + +// TODO: set these. +const GAS_USED_COST: u64 = 10; +const MIN_PAD_GAS_AMOUNT: u64 = 10; + +pub(super) fn call_gas_used(handle: &mut impl PrecompileHandle) -> PrecompileResult { + // Record the cost calling the precompile. + handle.record_cost(GAS_USED_COST)?; + + let used_gas = handle.used_gas(); + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: ethabi::encode(&[Token::Uint(used_gas.into())]), + }) +} + +pub(super) fn call_pad_gas(handle: &mut impl PrecompileHandle) -> PrecompileResult { + // Record the cost of the precompile itself. + handle.record_cost(MIN_PAD_GAS_AMOUNT)?; + + // Decode args. + let mut call_args = ethabi::decode(&[ParamType::Uint(128)], handle.input()).map_err(|e| { + PrecompileFailure::Error { + exit_status: ExitError::Other(e.to_string().into()), + } + })?; + let gas_amount_big = call_args.pop().unwrap().into_uint().unwrap(); + let gas_amount = gas_amount_big.try_into().unwrap_or(u64::max_value()); + + // Obtain total used gas so far. + let used_gas = handle.used_gas(); + + // Fail if more gas that the desired padding was already used. + if gas_amount < used_gas { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + "gas pad amount less than already used gas" + .to_string() + .into(), + ), + }); + } + + // Record the remainder so that the gas use is padded to the desired amount. + handle.record_cost(gas_amount - used_gas)?; + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: Vec::new(), + }) +} + +#[cfg(test)] +mod test { + use super::super::testing::*; + use crate::{ + mock::EvmSigner, + precompile::testing::{init_and_deploy_contract, TestRuntime}, + }; + use ethabi::{ParamType, Token}; + use oasis_runtime_sdk::{ + context, + modules::core::Event, + testing::{keys, mock::Mock}, + }; + + /// Test contract code. + static TEST_CONTRACT_CODE_HEX: &str = + include_str!("../../../../../tests/e2e/contracts/use_gas/evm_use_gas.hex"); + + #[test] + fn test_call_gas_used() { + // Test basic. + let ret = call_contract( + H160([ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x09, + ]), + ðabi::encode(&[Token::Bytes(Vec::new())]), + 10_560, + ) + .unwrap(); + + let gas_usage = ethabi::decode(&[ParamType::Uint(128)], &ret.unwrap().output) + .expect("call should return gas usage") + .pop() + .unwrap() + .into_uint() + .expect("call should return uint") + .try_into() + .unwrap_or(u64::max_value()); + assert_eq!(gas_usage, 10, "call should return gas usage"); + + // Test use gas in contract. + let mut mock = Mock::default(); + let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let mut signer = EvmSigner::new(0, keys::dave::sigspec()); + + // Create contract. + let contract_address = + init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + + let expected_gas_used = 22_659; + + // Call into the test contract. + let dispatch_result = + signer.call_evm(&mut ctx, contract_address.into(), "test_gas_used", &[], &[]); + assert!( + dispatch_result.result.is_success(), + "test gas used should succeed" + ); + assert_eq!(dispatch_result.tags.len(), 2, "2 emitted tags expected"); + + // Check actual gas usage. + let expected = cbor::to_vec(vec![Event::GasUsed { + amount: expected_gas_used, + }]); + assert_eq!( + dispatch_result.tags[0].value, expected, + "expected events emitted" + ); + } + + #[test] + fn test_pad_gas() { + // Test basic. + call_contract( + H160([ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa, + ]), + ðabi::encode(&[Token::Uint(1.into())]), + 10_560, + ) + .expect("call should return something") + .expect_err("call should fail as the input gas amount is to small"); + + let ret = call_contract( + H160([ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa, + ]), + ðabi::encode(&[Token::Uint(20.into())]), + 10_560, + ) + .unwrap(); + assert_eq!(ret.unwrap().output.len(), 0); + + // Test out of gas. + call_contract( + H160([ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa, + ]), + ðabi::encode(&[Token::Uint(20_000.into())]), + 10_560, + ) + .expect("call should return something") + .expect_err("call should fail as the gas limit is reached"); + + // Test gas padding. + let mut mock = Mock::default(); + let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let mut signer = EvmSigner::new(0, keys::dave::sigspec()); + + // Create contract. + let contract_address = + init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + + let expected_gas = 41_359; + + // Call into the test contract path for `if param > 10`. + let dispatch_result = signer.call_evm( + &mut ctx, + contract_address.into(), + "test_pad_gas", + &[ParamType::Uint(128)], + &[Token::Uint(100.into())], + ); + assert!( + dispatch_result.result.is_success(), + "pad gas should succeed" + ); + assert_eq!(dispatch_result.tags.len(), 1, "1 emitted tags expected"); + + let expected = cbor::to_vec(vec![Event::GasUsed { + amount: expected_gas, + }]); + assert_eq!( + dispatch_result.tags[0].value, expected, + "expected gas usage" + ); + + // Call into the test contract path `if param < 10`. + let dispatch_result = signer.call_evm( + &mut ctx, + contract_address.into(), + "test_pad_gas", + &[ParamType::Uint(128)], + &[Token::Uint(1.into())], + ); + assert!( + dispatch_result.result.is_success(), + "pad gas should succeed" + ); + assert_eq!(dispatch_result.tags.len(), 1, "1 emitted tags expected"); + + let expected = cbor::to_vec(vec![Event::GasUsed { + amount: expected_gas, // Gas usage should match for both code paths in the contract. + }]); + assert_eq!( + dispatch_result.tags[0].value, expected, + "expected gas usage" + ); + } +} diff --git a/runtime-sdk/modules/evm/src/precompile/mod.rs b/runtime-sdk/modules/evm/src/precompile/mod.rs index c5b28ee10a..180a9ff4a8 100644 --- a/runtime-sdk/modules/evm/src/precompile/mod.rs +++ b/runtime-sdk/modules/evm/src/precompile/mod.rs @@ -13,10 +13,12 @@ use primitive_types::H160; use crate::{backend::EVMBackendExt, Config}; mod confidential; +mod gas; mod sha512; mod standard; mod subcall; +#[cfg(any(test, feature = "test"))] pub mod testing; // Some types matching evm::executor::stack. @@ -125,6 +127,8 @@ impl PrecompileSet for Precompiles<'_, Cfg, B> { (1, 0, 6) => confidential::call_sign(handle), (1, 0, 7) => confidential::call_verify(handle), (1, 0, 8) => confidential::call_curve25519_compute_public(handle), + (1, 0, 9) => gas::call_gas_used(handle), + (1, 0, 10) => gas::call_pad_gas(handle), // Oasis-specific, general. (1, 1, 1) => sha512::call_sha512_256(handle), (1, 1, 2) => sha512::call_sha512(handle), @@ -143,7 +147,7 @@ impl PrecompileSet for Precompiles<'_, Cfg, B> { // Ethereum-compatible. (0, 0, 1..=8, _) | // Oasis-specific, confidential. - (1, 0, 1..=8, true) | + (1, 0, 1..=10, true) | // Oasis-specific, general. (1, 1, 1..=3, _) ) diff --git a/runtime-sdk/modules/evm/src/precompile/subcall.rs b/runtime-sdk/modules/evm/src/precompile/subcall.rs index 89e331d364..39b34e1be5 100644 --- a/runtime-sdk/modules/evm/src/precompile/subcall.rs +++ b/runtime-sdk/modules/evm/src/precompile/subcall.rs @@ -117,14 +117,12 @@ pub(super) fn call_subcall( #[cfg(test)] mod test { - use std::collections::BTreeMap; - use ethabi::{ParamType, Token}; use oasis_runtime_sdk::{ context, module::{self, Module as _}, - modules::{accounts, core}, + modules::accounts, testing::{ keys, mock::{CallOptions, Mock}, @@ -134,68 +132,15 @@ mod test { token::{self, BaseUnits, Denomination}, transaction::Fee, }, - BatchContext, Runtime, Version, }; - use crate as evm; use crate::{ - mock::{decode_reverted, load_contract_bytecode, EvmSigner}, - types::{self, H160}, + self as evm, + mock::{decode_reverted, EvmSigner}, + precompile::testing::{init_and_deploy_contract, TestConfig, TestRuntime}, Config as _, }; - struct TestConfig; - - type Core = core::Module; - type Accounts = accounts::Module; - type Evm = evm::Module; - - impl core::Config for TestConfig {} - - impl evm::Config for TestConfig { - type Accounts = Accounts; - - type AdditionalPrecompileSet = (); - - const CHAIN_ID: u64 = 0x42; - - const TOKEN_DENOMINATION: Denomination = Denomination::NATIVE; - } - - struct TestRuntime; - - impl Runtime for TestRuntime { - const VERSION: Version = Version::new(0, 0, 0); - type Core = Core; - type Modules = (Core, Accounts, Evm); - - fn genesis_state() -> ::Genesis { - ( - core::Genesis { - parameters: core::Parameters { - max_batch_gas: u64::MAX, - max_tx_size: 32 * 1024, - max_tx_signers: 1, - max_multisig_signers: 8, - gas_costs: Default::default(), - min_gas_price: BTreeMap::from([(token::Denomination::NATIVE, 0)]), - }, - }, - accounts::Genesis { - balances: BTreeMap::from([( - keys::dave::address(), - BTreeMap::from([(Denomination::NATIVE, 1_000_000)]), - )]), - total_supplies: BTreeMap::from([(Denomination::NATIVE, 1_000_000)]), - ..Default::default() - }, - evm::Genesis { - ..Default::default() - }, - ) - } - } - /// Test contract code. static TEST_CONTRACT_CODE_HEX: &str = include_str!("../../../../../tests/e2e/contracts/subcall/evm_subcall.hex"); @@ -203,27 +148,6 @@ mod test { static TEST_CONTRACT_ABI_JSON: &str = include_str!("../../../../../tests/e2e/contracts/subcall/evm_subcall.abi"); - fn init_and_deploy_contract(ctx: &mut C, signer: &mut EvmSigner) -> H160 { - TestRuntime::migrate(ctx); - - let test_contract = load_contract_bytecode(TEST_CONTRACT_CODE_HEX); - - // Create contract. - let dispatch_result = signer.call( - ctx, - "evm.Create", - types::Create { - value: 0.into(), - init_code: test_contract, - }, - ); - let result = dispatch_result.result.unwrap(); - let result: Vec = cbor::from_value(result).unwrap(); - let contract_address = H160::from_slice(&result); - - contract_address - } - #[test] fn test_subcall_dispatch() { let mut mock = Mock::default(); @@ -231,12 +155,13 @@ mod test { let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = init_and_deploy_contract(&mut ctx, &mut signer); + let contract_address = + init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Call into the test contract. let dispatch_result = signer.call_evm( &mut ctx, - contract_address, + contract_address.into(), "test", &[ ParamType::Bytes, // method @@ -272,7 +197,7 @@ mod test { // Call into test contract again. let dispatch_result = signer.call_evm_opts( &mut ctx, - contract_address, + contract_address.into(), "test", &[ ParamType::Bytes, // method @@ -337,12 +262,13 @@ mod test { let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = init_and_deploy_contract(&mut ctx, &mut signer); + let contract_address = + init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Call into the test contract. let dispatch_result = signer.call_evm( &mut ctx, - contract_address, + contract_address.into(), "test_delegatecall", &[ ParamType::Bytes, // method @@ -377,12 +303,13 @@ mod test { let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = init_and_deploy_contract(&mut ctx, &mut signer); + let contract_address = + init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Call into the test contract. let dispatch_result = signer.call_evm( &mut ctx, - contract_address, + contract_address.into(), "test", &[ ParamType::Bytes, // method @@ -391,7 +318,7 @@ mod test { &[ Token::Bytes("evm.Call".into()), Token::Bytes(cbor::to_vec(evm::types::Call { - address: contract_address, + address: contract_address.into(), value: 0.into(), data: [ ethabi::short_signature("test", &[ParamType::Bytes, ParamType::Bytes]) @@ -429,10 +356,11 @@ mod test { let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = init_and_deploy_contract(&mut ctx, &mut signer); + let contract_address = + init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Make transfers more expensive so we can test an out-of-gas condition. - Accounts::set_params(accounts::Parameters { + accounts::Module::set_params(accounts::Parameters { gas_costs: accounts::GasCosts { tx_transfer: 100_000, }, @@ -442,7 +370,7 @@ mod test { // First try a call with enough gas. let dispatch_result = signer.call_evm_opts( &mut ctx, - contract_address, + contract_address.into(), "test", &[ ParamType::Bytes, // method @@ -471,7 +399,7 @@ mod test { // can still continue (e.g. to trigger the revert). let dispatch_result = signer.call_evm_opts( &mut ctx, - contract_address, + contract_address.into(), "test", &[ ParamType::Bytes, // method @@ -522,7 +450,7 @@ mod test { // execution would fail. let dispatch_result = signer.call_evm_opts( &mut ctx, - contract_address, + contract_address.into(), "test_spin", // Version that spins, wasting gas, after the subcall. &[ ParamType::Bytes, // method diff --git a/runtime-sdk/modules/evm/src/precompile/testing.rs b/runtime-sdk/modules/evm/src/precompile/testing.rs index 799a32a480..642ae9ba08 100644 --- a/runtime-sdk/modules/evm/src/precompile/testing.rs +++ b/runtime-sdk/modules/evm/src/precompile/testing.rs @@ -5,14 +5,23 @@ use evm::{ pub use primitive_types::{H160, H256}; use oasis_runtime_sdk::{ - modules::{accounts::Module, core::Error}, + module::{self}, + modules::{accounts, accounts::Module, core, core::Error}, subcall, - types::token::Denomination, + testing::keys, + types::token::{self, Denomination}, + BatchContext, Runtime, Version, +}; + +use crate::{ + mock::{load_contract_bytecode, EvmSigner}, + types::{self}, }; use super::{PrecompileResult, Precompiles}; +use std::collections::BTreeMap; -struct TestConfig; +pub(crate) struct TestConfig; impl crate::Config for TestConfig { type Accounts = Module; @@ -50,6 +59,7 @@ struct MockPrecompileHandle<'a> { context: &'a Context, gas_limit: u64, gas_cost: u64, + gas_used: u64, } impl<'a> PrecompileHandle for MockPrecompileHandle<'a> { @@ -70,6 +80,7 @@ impl<'a> PrecompileHandle for MockPrecompileHandle<'a> { return Err(ExitError::OutOfGas); } self.gas_cost = self.gas_cost.saturating_add(cost); + self.gas_used = self.gas_used.saturating_add(cost); Ok(()) } @@ -101,6 +112,23 @@ impl<'a> PrecompileHandle for MockPrecompileHandle<'a> { fn gas_limit(&self) -> Option { Some(self.gas_limit) } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + _storage_growth: Option, + ) -> Result<(), ExitError> { + unimplemented!() + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) { + unimplemented!() + } + + fn used_gas(&self) -> u64 { + self.gas_used + } } #[doc(hidden)] @@ -126,6 +154,7 @@ pub fn call_contract_with_gas_report( context: &context, gas_limit, gas_cost: 0, + gas_used: 0, }; precompiles .execute(&mut handle) @@ -168,3 +197,67 @@ pub fn read_test_cases(name: &str) -> Vec { serde_json::from_str(&contents).expect("json decoding should succeed") } + +type Core = core::Module; +type Accounts = accounts::Module; +type Evm = crate::Module; + +impl core::Config for TestConfig {} + +pub(crate) struct TestRuntime; + +impl Runtime for TestRuntime { + const VERSION: Version = Version::new(0, 0, 0); + type Core = Core; + type Modules = (Core, Accounts, Evm); + + fn genesis_state() -> ::Genesis { + ( + core::Genesis { + parameters: core::Parameters { + max_batch_gas: u64::MAX, + max_tx_size: 32 * 1024, + max_tx_signers: 1, + max_multisig_signers: 8, + gas_costs: Default::default(), + min_gas_price: BTreeMap::from([(token::Denomination::NATIVE, 0)]), + }, + }, + accounts::Genesis { + balances: BTreeMap::from([( + keys::dave::address(), + BTreeMap::from([(Denomination::NATIVE, 1_000_000)]), + )]), + total_supplies: BTreeMap::from([(Denomination::NATIVE, 1_000_000)]), + ..Default::default() + }, + crate::Genesis { + ..Default::default() + }, + ) + } +} + +#[cfg(any(test, feature = "test"))] +pub(crate) fn init_and_deploy_contract( + ctx: &mut C, + signer: &mut EvmSigner, + bytecode: &str, +) -> H160 { + TestRuntime::migrate(ctx); + + let test_contract = load_contract_bytecode(bytecode); + + // Create contract. + let dispatch_result = signer.call( + ctx, + "evm.Create", + types::Create { + value: 0.into(), + init_code: test_contract, + }, + ); + let result = dispatch_result.result.unwrap(); + let result: Vec = cbor::from_value(result).unwrap(); + H160::from_slice(&result) +} diff --git a/tests/e2e/contracts/use_gas/Makefile b/tests/e2e/contracts/use_gas/Makefile new file mode 100644 index 0000000000..25bb7b20be --- /dev/null +++ b/tests/e2e/contracts/use_gas/Makefile @@ -0,0 +1,6 @@ +contract = evm_use_gas.sol +abi = evm_use_gas.abi +hex = evm_use_gas.hex + +include ../contracts.mk + diff --git a/tests/e2e/contracts/use_gas/evm_use_gas.abi b/tests/e2e/contracts/use_gas/evm_use_gas.abi new file mode 100644 index 0000000000..6e3be14857 --- /dev/null +++ b/tests/e2e/contracts/use_gas/evm_use_gas.abi @@ -0,0 +1 @@ +[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"UsedGas","type":"event"},{"inputs":[],"name":"test_gas_used","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"input","type":"uint128"}],"name":"test_pad_gas","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/tests/e2e/contracts/use_gas/evm_use_gas.hex b/tests/e2e/contracts/use_gas/evm_use_gas.hex new file mode 100644 index 0000000000..a8a80cc837 --- /dev/null +++ b/tests/e2e/contracts/use_gas/evm_use_gas.hex @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b5061035b8061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80634f7be61114610038578063ce5cf2b314610042575b5f80fd5b610040610071565b005b610055610050366004610251565b610153565b6040516001600160801b03909116815260200160405180910390f35b6040515f9081906009600160981b01908281818181865af19150503d805f81146100b6576040519150601f19603f3d011682016040523d82523d5f602084013e6100bb565b606091505b5091509150816101095760405162461bcd60e51b815260206004820152601460248201527319d85cd7dd5cd9590818d85b1b0819985a5b195960621b60448201526064015b60405180910390fd5b7fa7f4919b703b170b4423761fe4fa09db0a7caff6f793544ae33a65f6c3ae81e88180602001905181019061013e919061027e565b60405190815260200160405180910390a15050565b5f614e20600a836001600160801b0316111561018a576005610176600285610295565b61018091906102c6565b925082915061018e565b8291505b604080516001600160801b03831660208201525f918291600a600160981b01910160408051601f19818403018152908290526101c9916102f9565b5f604051808303815f865af19150503d805f8114610202576040519150601f19603f3d011682016040523d82523d5f602084013e610207565b606091505b5091509150816102495760405162461bcd60e51b815260206004820152600d60248201526c1c185919d85cc819985a5b1959609a1b6044820152606401610100565b505050919050565b5f60208284031215610261575f80fd5b81356001600160801b0381168114610277575f80fd5b9392505050565b5f6020828403121561028e575f80fd5b5051919050565b5f6001600160801b03808416806102ba57634e487b7160e01b5f52601260045260245ffd5b92169190910492915050565b6001600160801b038281168282160390808211156102f257634e487b7160e01b5f52601160045260245ffd5b5092915050565b5f82515f5b8181101561031857602081860181015185830152016102fe565b505f92019182525091905056fea26469706673582212202370b2a1e5c223fe3f045279a7652e709f284fecad8cb87fc78a06a799ebf0b964736f6c63430008150033 \ No newline at end of file diff --git a/tests/e2e/contracts/use_gas/evm_use_gas.sol b/tests/e2e/contracts/use_gas/evm_use_gas.sol new file mode 100644 index 0000000000..e25154759b --- /dev/null +++ b/tests/e2e/contracts/use_gas/evm_use_gas.sol @@ -0,0 +1,33 @@ +pragma solidity ^0.8.0; + +contract Test { + address private constant GAS_USED = + 0x0100000000000000000000000000000000000009; + address private constant PADGAS = + 0x010000000000000000000000000000000000000a; + + event UsedGas(uint256 value); + + function test_gas_used() public { + (bool success, bytes memory gas_used) = GAS_USED.call(""); + require(success, "gas_used call failed"); + emit UsedGas(abi.decode(gas_used, (uint256))); + } + + modifier padGas(uint128 amount) { + _; + (bool success, bytes memory data) = PADGAS.call(abi.encode(amount)); + require(success, "padgas failed"); + } + + function test_pad_gas( + uint128 input + ) public padGas(20_000) returns (uint128) { + if (input > 10) { + input = (input / 2) - 5; + return input; + } else { + return input; + } + } +}