mirror of
https://github.com/serai-dex/serai.git
synced 2025-04-13 09:41:57 +00:00
Test createAddress
Benchmarks gas usage Note the estimator needs to be updated as this is now variable-gas to the state.
This commit is contained in:
parent
75c6427d7c
commit
a9625364df
5 changed files with 123 additions and 4 deletions
processor/ethereum/router
|
@ -45,5 +45,10 @@ fn main() {
|
|||
);
|
||||
|
||||
// Build the test contracts
|
||||
build_solidity_contracts::build(&[], "contracts/tests", &(artifacts_path + "/tests")).unwrap();
|
||||
build_solidity_contracts::build(
|
||||
&["../../../networks/ethereum/schnorr/contracts", "../erc20/contracts", "contracts"],
|
||||
"contracts/tests",
|
||||
&(artifacts_path + "/tests"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -414,7 +414,7 @@ contract Router is IRouterWithoutCollisions {
|
|||
* detrimental to other `OutInstruction`s within the same batch) is sufficiently concerning to
|
||||
* justify this.
|
||||
*/
|
||||
function createAddress(uint256 nonce) private view returns (address) {
|
||||
function createAddress(uint256 nonce) internal view returns (address) {
|
||||
unchecked {
|
||||
/*
|
||||
The hashed RLP-encoding is:
|
||||
|
@ -438,9 +438,10 @@ contract Router is IRouterWithoutCollisions {
|
|||
bitsNeeded += 8;
|
||||
}
|
||||
uint256 bytesNeeded = bitsNeeded / 8;
|
||||
rlpEncodingLen = 22 + bytesNeeded;
|
||||
// 22 + 1 + the amount of bytes needed
|
||||
rlpEncodingLen = 23 + bytesNeeded;
|
||||
// Shift from byte 31 to byte 22
|
||||
rlpEncoding |= 0x80 + (bytesNeeded << 72);
|
||||
rlpEncoding |= (0x80 + bytesNeeded) << 72;
|
||||
// Shift past the unnecessary bytes
|
||||
rlpEncoding |= nonce << (72 - bitsNeeded);
|
||||
}
|
||||
|
|
13
processor/ethereum/router/contracts/tests/CreateAddress.sol
Normal file
13
processor/ethereum/router/contracts/tests/CreateAddress.sol
Normal file
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "Router.sol";
|
||||
|
||||
// Wrap the Router with a contract which exposes the address
|
||||
contract CreateAddress is Router {
|
||||
constructor() Router(bytes32(uint256(1))) {}
|
||||
|
||||
function createAddressForSelf(uint256 nonce) external returns (address) {
|
||||
return Router.createAddress(nonce);
|
||||
}
|
||||
}
|
97
processor/ethereum/router/src/tests/create_address.rs
Normal file
97
processor/ethereum/router/src/tests/create_address.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use alloy_core::primitives::{hex, U256, Bytes, TxKind};
|
||||
use alloy_sol_types::SolCall;
|
||||
|
||||
use alloy_consensus::TxLegacy;
|
||||
|
||||
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
|
||||
use alloy_provider::Provider;
|
||||
|
||||
use revm::{primitives::SpecId, interpreter::gas::calculate_initial_tx_gas};
|
||||
|
||||
use crate::tests::Test;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[expect(warnings)]
|
||||
#[expect(needless_pass_by_value)]
|
||||
#[expect(clippy::all)]
|
||||
#[expect(clippy::ignored_unit_patterns)]
|
||||
#[expect(clippy::redundant_closure_for_method_calls)]
|
||||
mod abi {
|
||||
alloy_sol_macro::sol!("contracts/tests/CreateAddress.sol");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_address() {
|
||||
let test = Test::new().await;
|
||||
|
||||
let address = {
|
||||
const BYTECODE: &[u8] = {
|
||||
const BYTECODE_HEX: &[u8] = include_bytes!(concat!(
|
||||
env!("OUT_DIR"),
|
||||
"/serai-processor-ethereum-router/tests/CreateAddress.bin"
|
||||
));
|
||||
const BYTECODE: [u8; BYTECODE_HEX.len() / 2] =
|
||||
match hex::const_decode_to_array::<{ BYTECODE_HEX.len() / 2 }>(BYTECODE_HEX) {
|
||||
Ok(bytecode) => bytecode,
|
||||
Err(_) => panic!("CreateAddress.bin did not contain valid hex"),
|
||||
};
|
||||
&BYTECODE
|
||||
};
|
||||
|
||||
let tx = TxLegacy {
|
||||
chain_id: None,
|
||||
nonce: 0,
|
||||
gas_price: 100_000_000_000u128,
|
||||
gas_limit: 1_100_000,
|
||||
to: TxKind::Create,
|
||||
value: U256::ZERO,
|
||||
input: Bytes::from_static(BYTECODE),
|
||||
};
|
||||
let tx = ethereum_primitives::deterministically_sign(tx);
|
||||
let receipt = ethereum_test_primitives::publish_tx(&test.provider, tx).await;
|
||||
receipt.contract_address.unwrap()
|
||||
};
|
||||
|
||||
// Check `createAddress` correctly encodes the nonce for every single meaningful bit pattern
|
||||
// The only meaningful patterns are < 0x80, == 0x80, and then each length greater > 0x80
|
||||
// The following covers all three
|
||||
let mut nonce = 1u64;
|
||||
while nonce.checked_add(nonce).is_some() {
|
||||
assert_eq!(
|
||||
&test
|
||||
.provider
|
||||
.call(
|
||||
&TransactionRequest::default().to(address).input(TransactionInput::new(
|
||||
(abi::CreateAddress::createAddressForSelfCall { nonce: U256::from(nonce) })
|
||||
.abi_encode()
|
||||
.into()
|
||||
))
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.as_ref()[12 ..],
|
||||
address.create(nonce).as_slice(),
|
||||
);
|
||||
nonce <<= 1;
|
||||
}
|
||||
|
||||
let input =
|
||||
(abi::CreateAddress::createAddressForSelfCall { nonce: U256::from(u64::MAX) }).abi_encode();
|
||||
let gas = test
|
||||
.provider
|
||||
.estimate_gas(
|
||||
&TransactionRequest::default().to(address).input(TransactionInput::new(input.clone().into())),
|
||||
)
|
||||
.await
|
||||
.unwrap() -
|
||||
calculate_initial_tx_gas(SpecId::CANCUN, &input, false, &[], 0).initial_gas;
|
||||
|
||||
let keccak256_gas_estimate = |len: u64| 30 + (6 * len.div_ceil(32));
|
||||
let mut bytecode_len = 0;
|
||||
while (keccak256_gas_estimate(bytecode_len) + keccak256_gas_estimate(85)) < gas {
|
||||
bytecode_len += 32;
|
||||
}
|
||||
println!(
|
||||
"Worst-case createAddress gas: {gas}, CREATE2 break-even is bytecode of length {bytecode_len}",
|
||||
);
|
||||
}
|
|
@ -41,6 +41,9 @@ use crate::{
|
|||
};
|
||||
|
||||
mod constants;
|
||||
|
||||
mod create_address;
|
||||
|
||||
mod erc20;
|
||||
use erc20::Erc20;
|
||||
|
||||
|
|
Loading…
Reference in a new issue