Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aurora Engine Benchmarks #41

Merged
merged 17 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
977 changes: 920 additions & 57 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ homepage = "https://github.com/aurora-is-near/aurora-engine"
repository = "https://github.com/aurora-is-near/aurora-engine"
license = "CC0-1.0"
publish = false
autobenches = false

[lib]
crate-type = ["cdylib", "rlib"]
Expand Down Expand Up @@ -59,10 +60,23 @@ hex = { version = "0.4.3", default-features = false }
near-sdk = { git = "https://github.com/near/near-sdk-rs", rev = "9d99077c6acfde68c06845f2a1eb2b5ed7983401" }
near-sdk-sim = { git = "https://github.com/near/near-sdk-rs", rev = "9d99077c6acfde68c06845f2a1eb2b5ed7983401" }
near-crypto = "0.1.0"
near-vm-runner = { git = "https://github.com/near/nearcore", rev = "3744f07e13bf43a9522fb39fa8f6f128396d0e1f" }
near-primitives-core = { git = "https://github.com/near/nearcore", rev = "3744f07e13bf43a9522fb39fa8f6f128396d0e1f" }
near-vm-logic = { git = "https://github.com/near/nearcore", rev = "3744f07e13bf43a9522fb39fa8f6f128396d0e1f" }
libsecp256k1 = "0.3.5"
rand = "0.7.3"
criterion = "0.3.4"
git2 = "0.13"

[[bench]]
name = "benches"
path = "benches/main.rs"
harness = false

[features]
default = ["sha2", "std"]
std = ["borsh/std", "evm/std", "primitive-types/std", "rlp/std", "sha3/std", "ethabi/std", "lunarity-lexer/std", "bn/std"]
testnet = []
contract = []
evm_bully = []
profile_eth_gas = []
75 changes: 75 additions & 0 deletions benches/eth_deploy_code.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use criterion::{criterion_group, BatchSize, BenchmarkId, Criterion, Throughput};
use secp256k1::SecretKey;

use super::{address_from_secret_key, create_eth_transaction, deploy_evm, RAW_CALL};

const INITIAL_BALANCE: u64 = 1000;
const INITIAL_NONCE: u64 = 0;
const TRANSFER_AMOUNT: u64 = 0;

fn eth_deploy_code_benchmark(c: &mut Criterion) {
let mut runner = deploy_evm();
let mut rng = rand::thread_rng();
let source_account = SecretKey::random(&mut rng);
runner.create_address(
address_from_secret_key(&source_account),
INITIAL_BALANCE.into(),
INITIAL_NONCE.into(),
);
let inputs: Vec<_> = [1, 4, 8, 12, 16]
.iter()
.copied()
.map(|n| {
let code_size = 2usize.pow(n);
let code: Vec<u8> = vec![0; code_size];
let transaction = create_eth_transaction(
None,
TRANSFER_AMOUNT.into(),
code,
Some(runner.chain_id),
&source_account,
);
rlp::encode(&transaction).to_vec()
})
.collect();
let calling_account_id = "some-account.near".to_string();

// measure gas usage
for input in inputs.iter() {
let input_size = input.len();
let (output, maybe_err) =
runner
.one_shot()
.call(RAW_CALL, calling_account_id.clone(), input.clone());
assert!(maybe_err.is_none());
let output = output.unwrap();
let gas = output.burnt_gas;
// TODO(#45): capture this in a file
println!("ETH_DEPLOY_CODE_{:?} NEAR GAS: {:?}", input_size, gas);

#[cfg(feature = "profile_eth_gas")]
{
let eth_gas = super::parse_eth_gas(&output);
// TODO(#45): capture this in a file
println!("ETH_DEPLOY_CODE_{:?} ETH GAS: {:?}", input_size, eth_gas);
}
}

// measure wall-clock time
let mut group = c.benchmark_group("deploy_code");
for input in inputs {
let input_size = input.len() as u64;
let id = BenchmarkId::from_parameter(input_size);
group.throughput(Throughput::Bytes(input_size));
group.bench_function(id, |b| {
b.iter_batched(
|| (runner.one_shot(), calling_account_id.clone(), input.clone()),
|(r, c, i)| r.call(RAW_CALL, c, i),
BatchSize::SmallInput,
)
});
}
group.finish();
}

criterion_group!(benches, eth_deploy_code_benchmark);
264 changes: 264 additions & 0 deletions benches/eth_erc20.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
use aurora_engine::prelude::{Address, U256};
use aurora_engine::transaction::EthTransaction;
use criterion::{criterion_group, BatchSize, BenchmarkId, Criterion};
use near_vm_logic::VMOutcome;
use secp256k1::SecretKey;
use std::path::{Path, PathBuf};

use super::{address_from_secret_key, deploy_evm, sign_transaction, AuroraRunner, RAW_CALL};
use crate::solidity;

const INITIAL_BALANCE: u64 = 1000;
const INITIAL_NONCE: u64 = 0;
const TRANSFER_AMOUNT: u64 = 67;

fn eth_erc20_benchmark(c: &mut Criterion) {
let mut runner = deploy_evm();
let mut rng = rand::thread_rng();
let source_account = SecretKey::random(&mut rng);
runner.create_address(
address_from_secret_key(&source_account),
INITIAL_BALANCE.into(),
INITIAL_NONCE.into(),
);
let calling_account_id = "some-account.near".to_string();

// deploy the erc20 contract
let constructor = ERC20Constructor::load();
let output = exec_transaction(
&mut runner,
constructor.deploy("Benchmarker", "BENCH", INITIAL_NONCE.into()),
&source_account,
);
let erc20_address = output.return_data.as_value().unwrap();
let contract = ERC20 {
abi: constructor.abi,
address: Address::from_slice(&erc20_address),
};

// create the transaction for minting
let tx = contract.mint(
address_from_secret_key(&source_account),
INITIAL_BALANCE.into(),
U256::from(INITIAL_NONCE + 1),
);
let signed_tx = sign_transaction(tx, Some(runner.chain_id), &source_account);
let mint_tx_bytes = rlp::encode(&signed_tx).to_vec();

// create the transaction for transfer
let dest_address = address_from_secret_key(&SecretKey::random(&mut rng));
let tx = contract.transfer(
dest_address,
TRANSFER_AMOUNT.into(),
U256::from(INITIAL_NONCE + 2),
);
let signed_tx = sign_transaction(tx, Some(runner.chain_id), &source_account);
let transfer_tx_bytes = rlp::encode(&signed_tx).to_vec();

let mut group = c.benchmark_group("erc20");
let mint_id = BenchmarkId::from_parameter("mint");
let transfer_id = BenchmarkId::from_parameter("transfer");

// measure mint wall-clock time
group.bench_function(mint_id, |b| {
b.iter_batched(
|| {
(
runner.one_shot(),
calling_account_id.clone(),
mint_tx_bytes.clone(),
)
},
|(r, c, i)| r.call(RAW_CALL, c, i),
BatchSize::SmallInput,
)
});

// Measure mint gas usage; don't use `one_shot` because we want to keep this state change for
// the next benchmark where we transfer some of the minted tokens.
let (output, maybe_error) =
runner.call(RAW_CALL, calling_account_id.clone(), mint_tx_bytes.clone());
assert!(maybe_error.is_none());
let output = output.unwrap();
let gas = output.burnt_gas;
// TODO(#45): capture this in a file
println!("ETH_ERC20_MINT NEAR GAS: {:?}", gas);
#[cfg(feature = "profile_eth_gas")]
{
let eth_gas = super::parse_eth_gas(&output);
// TODO(#45): capture this in a file
println!("ETH_ERC20_MINT ETH GAS: {:?}", eth_gas);
}

// Measure transfer gas usage
let (output, maybe_err) = runner.one_shot().call(
RAW_CALL,
calling_account_id.clone(),
transfer_tx_bytes.clone(),
);
assert!(maybe_err.is_none());
let output = output.unwrap();
let gas = output.burnt_gas;
// TODO(#45): capture this in a file
println!("ETH_ERC20_TRANSFER NEAR GAS: {:?}", gas);
#[cfg(feature = "profile_eth_gas")]
{
let eth_gas = super::parse_eth_gas(&output);
// TODO(#45): capture this in a file
println!("ETH_ERC20_TRANSFER ETH GAS: {:?}", eth_gas);
}

// measure transfer wall-clock time
group.bench_function(transfer_id, |b| {
b.iter_batched(
|| {
(
runner.one_shot(),
calling_account_id.clone(),
transfer_tx_bytes.clone(),
)
},
|(r, c, i)| r.call(RAW_CALL, c, i),
BatchSize::SmallInput,
)
});

group.finish();
}

struct ERC20Constructor {
abi: ethabi::Contract,
code: Vec<u8>,
}

struct ERC20 {
abi: ethabi::Contract,
address: Address,
}

impl ERC20Constructor {
fn load() -> Self {
let artifacts_base_path = Self::solidity_artifacts_path();
let hex_path = artifacts_base_path.join("ERC20PresetMinterPauser.bin");
let hex_rep = match std::fs::read_to_string(&hex_path) {
Ok(hex) => hex,
Err(_) => {
// An error occurred opening the file, maybe the contract hasn't been compiled?
let sources_root = Self::download_solidity_sources();
solidity::compile(
sources_root,
"token/ERC20/presets/ERC20PresetMinterPauser.sol",
&artifacts_base_path,
);
// If another error occurs, then we can't handle it so we just unwrap.
std::fs::read_to_string(hex_path).unwrap()
}
};
let code = hex::decode(&hex_rep).unwrap();
let abi_path = artifacts_base_path.join("ERC20PresetMinterPauser.abi");
let reader = std::fs::File::open(abi_path).unwrap();
let abi = ethabi::Contract::load(reader).unwrap();

Self { abi, code }
}

fn deploy(&self, name: &str, symbol: &str, nonce: U256) -> EthTransaction {
let data = self
.abi
.constructor()
.unwrap()
.encode_input(
self.code.clone(),
&[
ethabi::Token::String(name.to_string()),
ethabi::Token::String(symbol.to_string()),
],
)
.unwrap();
EthTransaction {
nonce,
gas_price: Default::default(),
gas: Default::default(),
to: None,
value: Default::default(),
data,
}
}

fn download_solidity_sources() -> PathBuf {
let sources_dir = Path::new("target").join("openzeppelin-contracts");
let contracts_dir = sources_dir.join("contracts");
if contracts_dir.exists() {
contracts_dir
} else {
let url = "https://github.com/OpenZeppelin/openzeppelin-contracts";
let repo = git2::Repository::clone(url, sources_dir).unwrap();
// repo.path() gives the path of the .git directory, so we need to use the parent
repo.path().parent().unwrap().join("contracts")
}
}

fn solidity_artifacts_path() -> PathBuf {
Path::new("target").join("solidity_build")
}
}

impl ERC20 {
fn mint(&self, recipient: Address, amount: U256, nonce: U256) -> EthTransaction {
let data = self
.abi
.function("mint")
.unwrap()
.encode_input(&[
ethabi::Token::Address(recipient),
ethabi::Token::Uint(amount),
])
.unwrap();
EthTransaction {
nonce,
gas_price: Default::default(),
gas: Default::default(),
to: Some(self.address),
value: Default::default(),
data,
}
}

fn transfer(&self, recipient: Address, amount: U256, nonce: U256) -> EthTransaction {
let data = self
.abi
.function("transfer")
.unwrap()
.encode_input(&[
ethabi::Token::Address(recipient),
ethabi::Token::Uint(amount),
])
.unwrap();
EthTransaction {
nonce,
gas_price: Default::default(),
gas: Default::default(),
to: Some(self.address),
value: Default::default(),
data,
}
}
}

fn exec_transaction(
runner: &mut AuroraRunner,
tx: EthTransaction,
account: &SecretKey,
) -> VMOutcome {
let calling_account_id = "some-account.near".to_string();
let signed_tx = sign_transaction(tx, Some(runner.chain_id), &account);
let (output, maybe_err) = runner.call(
RAW_CALL,
calling_account_id,
rlp::encode(&signed_tx).to_vec(),
);
assert!(maybe_err.is_none());
output.unwrap()
}

criterion_group!(benches, eth_erc20_benchmark);
Loading