Skip to content

Commit

Permalink
Feat: EVM gas cost optimizations (#934)
Browse files Browse the repository at this point in the history
## Description

Based on `SputnikVM` EVM optimizations: aurora-is-near/sputnikvm/pull/48

### Gas cost

➡️  A lot of tests show gas decreased to: `1-2%`
➡️ For some tests gas cost increased, it'is related to:
aurora-is-near/sputnikvm/pull/46 as it's requires additional NEAR
storage read operation.
  • Loading branch information
mrLSD authored Aug 14, 2024
1 parent 885b732 commit 342d171
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 24 deletions.
16 changes: 8 additions & 8 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ byte-slice-cast = { version = "1", default-features = false }
criterion = "0.5"
digest = "0.10"
ethabi = { version = "18", default-features = false }
evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.42.0-aurora", default-features = false }
evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.42.0-aurora", default-features = false, features = ["std"] }
evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.42.0-aurora", default-features = false, features = ["std", "tracing"] }
evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.42.0-aurora", default-features = false, features = ["std", "tracing"] }
evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.43.1-aurora", default-features = false }
evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.43.1-aurora", default-features = false, features = ["std"] }
evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.43.1-aurora", default-features = false, features = ["std", "tracing"] }
evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.43.1-aurora", default-features = false, features = ["std", "tracing"] }
fixed-hash = { version = "0.8", default-features = false }
function_name = "0.3"
git2 = "0.19"
Expand Down
2 changes: 1 addition & 1 deletion engine-precompiles/src/xcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub mod costs {
pub const CROSS_CONTRACT_CALL_BASE: EthGas = EthGas::new(343_650);
/// Additional EVM gas cost per bytes of input given.
/// See `CROSS_CONTRACT_CALL_BASE` for estimation methodology.
pub const CROSS_CONTRACT_CALL_BYTE: EthGas = EthGas::new(3);
pub const CROSS_CONTRACT_CALL_BYTE: EthGas = EthGas::new(4);
/// EVM gas cost per NEAR gas attached to the created promise.
/// This value is derived from the gas report `https://hackmd.io/@birchmd/Sy4piXQ29`
/// The units on this quantity are `NEAR Gas / EVM Gas`.
Expand Down
4 changes: 4 additions & 0 deletions engine-sdk/src/caching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ impl<K: Ord, V> FullCache<K, V> {
pub fn get_or_insert_with<F: FnOnce() -> V>(&mut self, k: K, f: F) -> &mut V {
self.inner.entry(k).or_insert_with(f)
}

pub fn contains_key(&self, k: &K) -> bool {
self.inner.contains_key(k)
}
}
6 changes: 3 additions & 3 deletions engine-tests/src/tests/one_inch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn test_1inch_liquidity_protocol() {

let (result, profile, pool_factory) = helper.create_pool_factory(&deployer_address);
assert!(result.gas_used >= 2_800_000); // more than 2.8M EVM gas used
assert_gas_bound(profile.all_gas(), 9); // less than 9 NEAR Tgas used
assert_gas_bound(profile.all_gas(), 10); // less than 9 NEAR Tgas used

// create some ERC-20 tokens to have a liquidity pool for
let signer_address = utils::address_from_secret_key(&helper.signer.secret_key);
Expand All @@ -38,7 +38,7 @@ fn test_1inch_liquidity_protocol() {
let (result, profile, pool) =
helper.create_pool(&pool_factory, token_a.0.address, token_b.0.address);
assert!(result.gas_used >= 4_500_000); // more than 4.5M EVM gas used
assert_gas_bound(profile.all_gas(), 18);
assert_gas_bound(profile.all_gas(), 20);

// Approve giving ERC-20 tokens to the pool
helper.approve_erc20_tokens(&token_a, pool.address());
Expand Down Expand Up @@ -100,7 +100,7 @@ fn test_1_inch_limit_order_deploy() {
// more than 3.5 million Ethereum gas used
assert!(result.gas_used > 3_500_000);
// less than 11 NEAR TGas used
assert_gas_bound(profile.all_gas(), 11);
assert_gas_bound(profile.all_gas(), 12);
// at least 45% of which is from wasm execution
let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas();
assert!(
Expand Down
4 changes: 2 additions & 2 deletions engine-tests/src/tests/promise_results_precompile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ fn test_promise_result_gas_cost() {
let total_gas2 = y2 + baseline.all_gas();

assert!(
utils::within_x_percent(17, evm1, total_gas1 / NEAR_GAS_PER_EVM),
utils::within_x_percent(21, evm1, total_gas1 / NEAR_GAS_PER_EVM),
"Incorrect EVM gas used. Expected: {} Actual: {}",
evm1,
total_gas1 / NEAR_GAS_PER_EVM
);
assert!(
utils::within_x_percent(17, evm2, total_gas2 / NEAR_GAS_PER_EVM),
utils::within_x_percent(21, evm2, total_gas2 / NEAR_GAS_PER_EVM),
"Incorrect EVM gas used. Expected: {} Actual: {}",
evm2,
total_gas2 / NEAR_GAS_PER_EVM
Expand Down
8 changes: 4 additions & 4 deletions engine-tests/src/tests/repro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn repro_GdASJ3KESs() {
block_timestamp: 1_645_717_564_644_206_730,
input_path: "src/tests/res/input_GdASJ3KESs.hex",
evm_gas_used: 706_713,
near_gas_used: 114,
near_gas_used: 113,
});
}

Expand Down Expand Up @@ -71,7 +71,7 @@ fn repro_FRcorNv() {
block_timestamp: 1_650_960_438_774_745_116,
input_path: "src/tests/res/input_FRcorNv.hex",
evm_gas_used: 1_239_721,
near_gas_used: 166,
near_gas_used: 165,
});
}

Expand All @@ -88,7 +88,7 @@ fn repro_5bEgfRQ() {
block_timestamp: 1_651_073_772_931_594_646,
input_path: "src/tests/res/input_5bEgfRQ.hex",
evm_gas_used: 6_414_105,
near_gas_used: 646,
near_gas_used: 645,
});
}

Expand Down Expand Up @@ -125,7 +125,7 @@ fn repro_Emufid2() {
block_timestamp: 1_662_118_048_636_713_538,
input_path: "src/tests/res/input_Emufid2.hex",
evm_gas_used: 1_156_364,
near_gas_used: 292,
near_gas_used: 291,
});
}

Expand Down
3 changes: 1 addition & 2 deletions engine-tests/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1027,8 +1027,7 @@ pub fn assert_gas_bound(total_gas: u64, tgas_bound: u64) {
/// this simpler formula to avoid floating point arithmetic.
pub const fn within_x_percent(x: u64, a: u64, b: u64) -> bool {
let (larger, smaller) = if a < b { (b, a) } else { (a, b) };

(100 / x) * (larger - smaller) <= larger
100 * (larger - smaller) <= larger * x
}

fn into_engine_error(gas_used: u64, aborted: &FunctionCallError) -> EngineError {
Expand Down
29 changes: 29 additions & 0 deletions engine/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1543,10 +1543,16 @@ pub fn get_storage<I: IO>(io: &I, address: &Address, key: &H256, generation: u32
.unwrap_or_default()
}

pub fn storage_has_key<I: IO>(io: &I, address: &Address, key: &H256, generation: u32) -> bool {
io.storage_has_key(storage_to_key(address, key, generation).as_ref())
}

/// EIP-7610: balance, nonce, code, storage should be empty
pub fn is_account_empty<I: IO>(io: &I, address: &Address) -> bool {
get_balance(io, address).is_zero()
&& get_nonce(io, address).is_zero()
&& get_code_size(io, address) == 0
&& !storage_has_key(io, address, &H256::zero(), get_generation(io, address))
}

/// Increments storage generation for a given address.
Expand Down Expand Up @@ -1914,6 +1920,29 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Backend for Engine<'env, I,
result
}

/// Check is storage empty for the address
/// EIP-7610: non-empty storage
fn is_empty_storage(&self, address: H160) -> bool {
let address = Address::new(address);
// As we can't read all storage data for account we assuming that if storage exists
// `index = 0` always true
let index = H256::zero();
let generation = *self
.generation_cache
.borrow_mut()
.entry(address)
.or_insert_with(|| get_generation(&self.io, &address));
// First we're checking cache to not produce read-storage operation
if self
.contract_storage_cache
.borrow()
.contains_key(&(address, index))
{
return false;
}
!storage_has_key(&self.io, &address, &index, generation)
}

/// Get original storage value of address at index, if available.
///
/// Since `SputnikVM` collects storage changes in memory until the transaction is over,
Expand Down

0 comments on commit 342d171

Please sign in to comment.