Skip to content

Commit

Permalink
Merge pull request #1496 from oasisprotocol/ptrus/feature/precompile-…
Browse files Browse the repository at this point in the history
…gas-pad

evm: add gas_padding precompile
  • Loading branch information
ptrus authored Sep 18, 2023
2 parents 998adda + 817041a commit b9bea98
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 108 deletions.
12 changes: 4 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions runtime-sdk/modules/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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 = { git = "https://github.com/oasisprotocol/evm", tag = "v0.39.1-oasis" }
fixed-hash = "0.8.0"
primitive-types = { version = "0.12", default-features = false, features = ["rlp", "num-traits"] }
rlp = "0.5.2"
Expand All @@ -47,7 +47,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 = []
Expand All @@ -60,6 +60,7 @@ harness = false
[[bin]]
name = "fuzz-precompile"
path = "fuzz/precompile.rs"
required-features = ["test"]

[[bin]]
name = "fuzz-precompile-corpus"
Expand Down
2 changes: 1 addition & 1 deletion runtime-sdk/modules/evm/fuzz/precompile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn main() {
let mut address = [0u8; 20];
address[0] = data[0] % 2;
address[18] = data[1] % 2;
address[19] = data[2] % 9;
address[19] = data[2] % 11;
let data = &data[3..];

call_contract(address.into(), data, 1_000_000);
Expand Down
217 changes: 217 additions & 0 deletions runtime-sdk/modules/evm/src/precompile/gas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
use ethabi::{ParamType, Token};
use evm::{
executor::stack::{PrecompileFailure, PrecompileHandle, PrecompileOutput},
ExitError, ExitSucceed,
};

use super::PrecompileResult;

const GAS_USED_COST: u64 = 10;
const PAD_GAS_COST: u64 = 10;

pub(super) fn call_gas_used(handle: &mut impl PrecompileHandle) -> PrecompileResult {
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 {
handle.record_cost(PAD_GAS_COST)?;

// 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,
]),
&ethabi::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::<TestRuntime>(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,
]),
&ethabi::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,
]),
&ethabi::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,
]),
&ethabi::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::<TestRuntime>(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"
);
}
}
6 changes: 5 additions & 1 deletion runtime-sdk/modules/evm/src/precompile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -125,6 +127,8 @@ impl<Cfg: Config, B: EVMBackendExt> 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),
Expand All @@ -143,7 +147,7 @@ impl<Cfg: Config, B: EVMBackendExt> 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, _)
)
Expand Down
Loading

0 comments on commit b9bea98

Please sign in to comment.