mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-22 20:51:13 +00:00
Integrate benchmarks and differential tests against an EVM interpreter (#7)
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "revive-benchmarks"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Cyrill Leutwiler <cyrill@parity.io>",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["bench-pvm-interpreter"]
|
||||
bench-pvm-interpreter = []
|
||||
bench-pvm = []
|
||||
bench-evm = ["revive-differential"]
|
||||
bench-extensive = []
|
||||
|
||||
[dependencies]
|
||||
hex = { workspace = true }
|
||||
polkavm = { workspace = true }
|
||||
revive-integration = { path = "../integration" }
|
||||
revive-differential = { path = "../differential", optional = true }
|
||||
alloy-primitives = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "execute"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "prepare"
|
||||
harness = false
|
||||
@@ -0,0 +1,185 @@
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{
|
||||
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId,
|
||||
Criterion,
|
||||
};
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
use polkavm::BackendKind;
|
||||
|
||||
use revive_benchmarks::prepare_pvm;
|
||||
use revive_integration::cases::Contract;
|
||||
|
||||
fn bench<P, L, I, M>(mut group: BenchmarkGroup<'_, M>, parameters: &[P], labels: &[L], contract: I)
|
||||
where
|
||||
P: Clone,
|
||||
L: std::fmt::Display,
|
||||
I: Fn(P) -> Contract,
|
||||
M: Measurement,
|
||||
{
|
||||
assert_eq!(parameters.len(), labels.len());
|
||||
|
||||
for (p, l) in parameters.iter().zip(labels.iter()) {
|
||||
#[cfg(feature = "bench-evm")]
|
||||
{
|
||||
let contract = contract(p.clone());
|
||||
let vm = revive_differential::prepare(contract.evm_runtime, contract.calldata);
|
||||
group.bench_with_input(BenchmarkId::new("EVM", l), p, move |b, _| {
|
||||
b.iter(|| {
|
||||
revive_differential::execute(vm.clone());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench-pvm-interpreter")]
|
||||
{
|
||||
let contract = contract(p.clone());
|
||||
let (state, mut instance, export) = prepare_pvm(
|
||||
&contract.pvm_runtime,
|
||||
&contract.calldata,
|
||||
BackendKind::Interpreter,
|
||||
);
|
||||
group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| {
|
||||
b.iter(|| {
|
||||
revive_integration::mock_runtime::call(state.clone(), &mut instance, export);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench-pvm")]
|
||||
{
|
||||
let contract = contract(p.clone());
|
||||
let (state, mut instance, export) = prepare_pvm(
|
||||
&contract.pvm_runtime,
|
||||
&contract.calldata,
|
||||
BackendKind::Compiler,
|
||||
);
|
||||
group.bench_with_input(BenchmarkId::new("PVM", l), p, |b, _| {
|
||||
b.iter(|| {
|
||||
revive_integration::mock_runtime::call(state.clone(), &mut instance, export);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
fn group_extensive<'error, M>(
|
||||
c: &'error mut Criterion<M>,
|
||||
group_name: &str,
|
||||
) -> BenchmarkGroup<'error, M>
|
||||
where
|
||||
M: Measurement,
|
||||
{
|
||||
let mut group = c.benchmark_group(group_name);
|
||||
group
|
||||
.sample_size(10)
|
||||
.measurement_time(Duration::from_secs(60));
|
||||
group
|
||||
}
|
||||
|
||||
fn bench_baseline(c: &mut Criterion) {
|
||||
let parameters = &[0u8];
|
||||
|
||||
bench(
|
||||
c.benchmark_group("Baseline"),
|
||||
parameters,
|
||||
parameters,
|
||||
|_| Contract::baseline(),
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_odd_product(c: &mut Criterion) {
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let group = group_extensive(c, "OddProduct");
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let group = c.benchmark_group("OddProduct");
|
||||
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let parameters = &[2_000_000i32, 4_000_000, 8_000_000, 120_000_000];
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let parameters = &[10_000, 100_000];
|
||||
|
||||
bench(group, parameters, parameters, Contract::odd_product);
|
||||
}
|
||||
|
||||
fn bench_triangle_number(c: &mut Criterion) {
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let group = group_extensive(c, "TriangleNumber");
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let group = c.benchmark_group("TriangleNumber");
|
||||
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let parameters = &[3_000_000i64, 6_000_000, 12_000_000, 180_000_000];
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let parameters = &[10_000, 100_000];
|
||||
|
||||
bench(group, parameters, parameters, Contract::triangle_number);
|
||||
}
|
||||
|
||||
fn bench_fibonacci_recurisve(c: &mut Criterion) {
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let group = c.benchmark_group("FibonacciRecursive");
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let group = group_extensive(c, "FibonacciRecursive");
|
||||
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let parameters = &[26, 30, 34, 38];
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let parameters = &[12, 16, 20];
|
||||
|
||||
bench(group, parameters, parameters, Contract::fib_recursive);
|
||||
}
|
||||
|
||||
fn bench_fibonacci_iterative(c: &mut Criterion) {
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let group = c.benchmark_group("FibonacciIterative");
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let group = group_extensive(c, "FibonacciIterative");
|
||||
|
||||
#[cfg(feature = "bench-extensive")]
|
||||
let parameters = &[256, 100000, 1000000, 100000000];
|
||||
#[cfg(not(feature = "bench-extensive"))]
|
||||
let parameters = &[64, 128, 256];
|
||||
|
||||
bench(group, parameters, parameters, Contract::fib_iterative);
|
||||
}
|
||||
|
||||
fn bench_fibonacci_binet(c: &mut Criterion) {
|
||||
let parameters = &[64, 128, 256];
|
||||
|
||||
bench(
|
||||
c.benchmark_group("FibonacciBinet"),
|
||||
parameters,
|
||||
parameters,
|
||||
Contract::fib_binet,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_sha1(c: &mut Criterion) {
|
||||
let parameters = &[vec![0xff], vec![0xff; 64], vec![0xff; 512]];
|
||||
let labels = parameters.iter().map(|p| p.len()).collect::<Vec<_>>();
|
||||
|
||||
bench(
|
||||
c.benchmark_group("SHA1"),
|
||||
parameters,
|
||||
&labels,
|
||||
Contract::sha1,
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
name = execute;
|
||||
config = Criterion::default();
|
||||
targets = bench_baseline,
|
||||
bench_odd_product,
|
||||
bench_triangle_number,
|
||||
bench_fibonacci_recurisve,
|
||||
bench_fibonacci_iterative,
|
||||
bench_fibonacci_binet,
|
||||
bench_sha1
|
||||
);
|
||||
criterion_main!(execute);
|
||||
@@ -0,0 +1,172 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use polkavm::BackendKind;
|
||||
|
||||
use revive_benchmarks::instantiate_engine;
|
||||
use revive_integration::cases::Contract;
|
||||
|
||||
fn bench(
|
||||
c: &mut Criterion,
|
||||
group_name: &str,
|
||||
#[cfg(feature = "bench-evm")] evm_runtime: Vec<u8>,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] pvm_runtime: Vec<u8>,
|
||||
) {
|
||||
let mut group = c.benchmark_group(group_name);
|
||||
let code_size = 0;
|
||||
|
||||
#[cfg(feature = "bench-evm")]
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("Evm", code_size),
|
||||
&evm_runtime,
|
||||
|b, code| b.iter(|| revive_differential::prepare(code.clone(), Vec::new())),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench-pvm-interpreter")]
|
||||
{
|
||||
let engine = instantiate_engine(BackendKind::Interpreter);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("PVMInterpreterCompile", code_size),
|
||||
&(&pvm_runtime, engine),
|
||||
|b, (code, engine)| {
|
||||
b.iter(|| {
|
||||
revive_integration::mock_runtime::recompile_code(code, engine);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench-pvm-interpreter")]
|
||||
{
|
||||
let engine = instantiate_engine(BackendKind::Interpreter);
|
||||
let module = revive_integration::mock_runtime::recompile_code(&pvm_runtime, &engine);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("PVMInterpreterInstantiate", code_size),
|
||||
&(module, engine),
|
||||
|b, (module, engine)| {
|
||||
b.iter(|| {
|
||||
revive_integration::mock_runtime::instantiate_module(module, engine);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench-pvm")]
|
||||
{
|
||||
let engine = instantiate_engine(BackendKind::Compiler);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("PVMCompile", code_size),
|
||||
&(&pvm_runtime, engine),
|
||||
|b, (code, engine)| {
|
||||
b.iter(|| {
|
||||
revive_integration::mock_runtime::recompile_code(code, engine);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench-pvm")]
|
||||
{
|
||||
let engine = instantiate_engine(BackendKind::Compiler);
|
||||
let module = revive_integration::mock_runtime::recompile_code(&pvm_runtime, &engine);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("PVMInstantiate", code_size),
|
||||
&(module, engine),
|
||||
|b, (module, engine)| {
|
||||
b.iter(|| {
|
||||
revive_integration::mock_runtime::instantiate_module(module, engine);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_baseline(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareBaseline",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::baseline().evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::baseline().pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_odd_product(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareOddProduct",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::odd_product(0).evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::baseline().pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_triangle_number(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareTriangleNumber",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::triangle_number(0).evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::triangle_number(0).pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_fibonacci_recursive(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareFibonacciRecursive",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::fib_recursive(0).evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::fib_recursive(0).pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_fibonacci_iterative(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareFibonacciIterative",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::fib_iterative(0).evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::fib_iterative(0).pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_fibonacci_binet(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareFibonacciBinet",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::fib_binet(0).evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::fib_binet(0).pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_sha1(c: &mut Criterion) {
|
||||
bench(
|
||||
c,
|
||||
"PrepareSHA1",
|
||||
#[cfg(feature = "bench-evm")]
|
||||
Contract::sha1(Default::default()).evm_runtime,
|
||||
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
|
||||
Contract::sha1(Default::default()).pvm_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
name = prepare;
|
||||
config = Criterion::default();
|
||||
targets = bench_baseline,
|
||||
bench_odd_product,
|
||||
bench_triangle_number,
|
||||
bench_fibonacci_recursive,
|
||||
bench_fibonacci_iterative,
|
||||
bench_fibonacci_binet,
|
||||
bench_sha1
|
||||
);
|
||||
criterion_main!(prepare);
|
||||
@@ -0,0 +1,24 @@
|
||||
use polkavm::{BackendKind, Config, Engine, ExportIndex, Instance, SandboxKind};
|
||||
use revive_integration::mock_runtime;
|
||||
use revive_integration::mock_runtime::State;
|
||||
|
||||
pub fn prepare_pvm(
|
||||
code: &[u8],
|
||||
input: &[u8],
|
||||
backend: BackendKind,
|
||||
) -> (State, Instance<State>, ExportIndex) {
|
||||
let mut config = Config::new();
|
||||
config.set_backend(Some(backend));
|
||||
config.set_sandbox(Some(SandboxKind::Linux));
|
||||
|
||||
let (instance, export_index) = mock_runtime::prepare(code, Some(config));
|
||||
|
||||
(State::new(input.to_vec()), instance, export_index)
|
||||
}
|
||||
|
||||
pub fn instantiate_engine(backend: BackendKind) -> Engine {
|
||||
let mut config = Config::new();
|
||||
config.set_backend(Some(backend));
|
||||
config.set_sandbox(Some(SandboxKind::Linux));
|
||||
mock_runtime::setup(Some(config))
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "revive-differential"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
evm-interpreter = { workspace = true }
|
||||
primitive-types = { workspace = true }
|
||||
@@ -0,0 +1,154 @@
|
||||
use evm_interpreter::{
|
||||
interpreter::{EtableInterpreter, RunInterpreter},
|
||||
trap::CallCreateTrap,
|
||||
Context, Etable, ExitError, Log, Machine, RuntimeBackend, RuntimeBaseBackend,
|
||||
RuntimeEnvironment, RuntimeState, TransactionContext, Valids,
|
||||
};
|
||||
use primitive_types::{H160, H256, U256};
|
||||
|
||||
static RUNTIME_ETABLE: Etable<RuntimeState, UnimplementedHandler, CallCreateTrap> =
|
||||
Etable::runtime();
|
||||
|
||||
pub struct UnimplementedHandler;
|
||||
|
||||
impl RuntimeEnvironment for UnimplementedHandler {
|
||||
fn block_hash(&self, _number: U256) -> H256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_number(&self) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_coinbase(&self) -> H160 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_timestamp(&self) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_difficulty(&self) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_randomness(&self) -> Option<H256> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_gas_limit(&self) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn block_base_fee_per_gas(&self) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn chain_id(&self) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeBaseBackend for UnimplementedHandler {
|
||||
fn balance(&self, _address: H160) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn code_size(&self, _address: H160) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn code_hash(&self, _address: H160) -> H256 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn code(&self, _address: H160) -> Vec<u8> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn storage(&self, _address: H160, _index: H256) -> H256 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn exists(&self, _address: H160) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn nonce(&self, _address: H160) -> U256 {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeBackend for UnimplementedHandler {
|
||||
fn original_storage(&self, _address: H160, _index: H256) -> H256 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn deleted(&self, _address: H160) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
fn is_cold(&self, _address: H160, _index: Option<H256>) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn mark_hot(&mut self, _address: H160, _index: Option<H256>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn log(&mut self, _log: Log) -> Result<(), ExitError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn mark_delete(&mut self, _address: H160) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reset_storage(&mut self, _address: H160) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_code(&mut self, _address: H160, _code: Vec<u8>) -> Result<(), ExitError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn reset_balance(&mut self, _address: H160) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn deposit(&mut self, _address: H160, _value: U256) {
|
||||
unimplemented!()
|
||||
}
|
||||
fn withdrawal(&mut self, _address: H160, _value: U256) -> Result<(), ExitError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn inc_nonce(&mut self, _address: H160) -> Result<(), ExitError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PreparedEvm {
|
||||
pub valids: Valids,
|
||||
pub vm: Machine<RuntimeState>,
|
||||
}
|
||||
|
||||
pub fn prepare(code: Vec<u8>, data: Vec<u8>) -> PreparedEvm {
|
||||
let state = RuntimeState {
|
||||
context: Context {
|
||||
address: H160::default(),
|
||||
caller: H160::default(),
|
||||
apparent_value: U256::default(),
|
||||
},
|
||||
transaction_context: TransactionContext {
|
||||
gas_price: U256::default(),
|
||||
origin: H160::default(),
|
||||
}
|
||||
.into(),
|
||||
retbuf: Vec::new(),
|
||||
};
|
||||
|
||||
PreparedEvm {
|
||||
valids: Valids::new(&code[..]),
|
||||
vm: evm_interpreter::Machine::new(code.into(), data.to_vec().into(), 1024, 0xFFFF, state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(pre: PreparedEvm) -> Vec<u8> {
|
||||
let mut vm = EtableInterpreter::new_valid(pre.vm, &RUNTIME_ETABLE, pre.valids);
|
||||
vm.run(&mut UnimplementedHandler {})
|
||||
.exit()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
vm.retval.clone()
|
||||
}
|
||||
@@ -8,12 +8,13 @@ edition = "2021"
|
||||
[dependencies]
|
||||
polkavm = { workspace = true }
|
||||
alloy-primitives = { workspace = true }
|
||||
parity-scale-codec = { workspace = true }
|
||||
revive-solidity = { path = "../solidity" }
|
||||
era-compiler-llvm-context = { path = "../llvm-context" }
|
||||
alloy-sol-types = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
|
||||
revive-solidity = { path = "../solidity" }
|
||||
revive-differential = { path = "../differential" }
|
||||
era-compiler-llvm-context = { path = "../llvm-context" }
|
||||
|
||||
[dev-dependencies]
|
||||
alloy-sol-types = { workspace = true }
|
||||
sha1 = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
contract Baseline {
|
||||
function baseline() public payable {}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
contract Computation {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
// https://medium.com/coinmonks/fibonacci-in-solidity-8477d907e22a
|
||||
|
||||
contract FibonacciRecursive {
|
||||
@@ -24,9 +26,11 @@ contract FibonacciIterative {
|
||||
uint a = 1;
|
||||
b = 1;
|
||||
for (uint i = 2; i < n; i++) {
|
||||
uint c = a + b;
|
||||
a = b;
|
||||
b = c;
|
||||
unchecked {
|
||||
uint c = a + b;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
contract MSize {
|
||||
uint[] public data;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
contract Value {
|
||||
function value() public payable returns (uint ret) {
|
||||
ret = msg.value;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
contract MStore8 {
|
||||
function mStore8(uint value) public pure returns (uint256 word) {
|
||||
assembly {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
use alloy_primitives::U256;
|
||||
use alloy_sol_types::{sol, SolCall};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Contract {
|
||||
pub evm_runtime: Vec<u8>,
|
||||
pub pvm_runtime: Vec<u8>,
|
||||
pub calldata: Vec<u8>,
|
||||
}
|
||||
|
||||
sol!(contract Baseline { function baseline() public payable; });
|
||||
|
||||
sol!(contract Computation {
|
||||
function odd_product(int32 n) public pure returns (int64);
|
||||
function triangle_number(int64 n) public pure returns (int64 sum);
|
||||
});
|
||||
|
||||
sol!(
|
||||
contract FibonacciRecursive {
|
||||
function fib3(uint n) public pure returns (uint);
|
||||
}
|
||||
);
|
||||
|
||||
sol!(
|
||||
contract FibonacciIterative {
|
||||
function fib3(uint n) external pure returns (uint b);
|
||||
}
|
||||
);
|
||||
|
||||
sol!(
|
||||
contract FibonacciBinet {
|
||||
function fib3(uint n) external pure returns (uint a);
|
||||
}
|
||||
);
|
||||
|
||||
sol!(
|
||||
contract SHA1 {
|
||||
function sha1(bytes memory data) public pure returns (bytes20 ret);
|
||||
}
|
||||
);
|
||||
|
||||
impl Contract {
|
||||
pub fn baseline() -> Self {
|
||||
let code = include_str!("../contracts/Baseline.sol");
|
||||
let name = "Baseline";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: Baseline::baselineCall::new(()).abi_encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn odd_product(n: i32) -> Self {
|
||||
let code = include_str!("../contracts/Computation.sol");
|
||||
let name = "Computation";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: Computation::odd_productCall::new((n,)).abi_encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn triangle_number(n: i64) -> Self {
|
||||
let code = include_str!("../contracts/Computation.sol");
|
||||
let name = "Computation";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: Computation::triangle_numberCall::new((n,)).abi_encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fib_recursive(n: u32) -> Self {
|
||||
let code = include_str!("../contracts/Fibonacci.sol");
|
||||
let name = "FibonacciRecursive";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: FibonacciRecursive::fib3Call::new((U256::from(n),)).abi_encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fib_iterative(n: u32) -> Self {
|
||||
let code = include_str!("../contracts/Fibonacci.sol");
|
||||
let name = "FibonacciIterative";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: FibonacciIterative::fib3Call::new((U256::from(n),)).abi_encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fib_binet(n: u32) -> Self {
|
||||
let code = include_str!("../contracts/Fibonacci.sol");
|
||||
let name = "FibonacciBinet";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: FibonacciBinet::fib3Call::new((U256::from(n),)).abi_encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sha1(pre: Vec<u8>) -> Self {
|
||||
let code = include_str!("../contracts/SHA1.sol");
|
||||
let name = "SHA1";
|
||||
|
||||
Self {
|
||||
evm_runtime: crate::compile_evm_bin_runtime(name, code),
|
||||
pvm_runtime: crate::compile_blob(name, code),
|
||||
calldata: SHA1::sha1Call::new((pre,)).abi_encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
-318
@@ -1,9 +1,42 @@
|
||||
use cases::Contract;
|
||||
use mock_runtime::State;
|
||||
|
||||
pub mod cases;
|
||||
pub mod mock_runtime;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Compile the blob of `contract_name` found in given `source_code`.
|
||||
/// The `solc` optimizer will be enabled
|
||||
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
|
||||
compile_blob_with_options(contract_name, source_code, true)
|
||||
compile_blob_with_options(
|
||||
contract_name,
|
||||
source_code,
|
||||
true,
|
||||
revive_solidity::SolcPipeline::Yul,
|
||||
)
|
||||
}
|
||||
|
||||
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
|
||||
/// The `solc` optimizer will be enabled
|
||||
pub fn compile_evm_bin_runtime(contract_name: &str, source_code: &str) -> Vec<u8> {
|
||||
let file_name = "contract.sol";
|
||||
|
||||
let contracts = revive_solidity::test_utils::build_solidity_with_options_evm(
|
||||
[(file_name.into(), source_code.into())].into(),
|
||||
Default::default(),
|
||||
None,
|
||||
revive_solidity::SolcPipeline::Yul,
|
||||
true,
|
||||
)
|
||||
.expect("source should compile");
|
||||
let bin_runtime = &contracts
|
||||
.get(contract_name)
|
||||
.unwrap_or_else(|| panic!("contract '{}' didn't produce bin-runtime", contract_name))
|
||||
.object;
|
||||
|
||||
hex::decode(bin_runtime).expect("bin-runtime shold be hex encoded")
|
||||
}
|
||||
|
||||
/// Compile the blob of `contract_name` found in given `source_code`.
|
||||
@@ -11,6 +44,7 @@ pub fn compile_blob_with_options(
|
||||
contract_name: &str,
|
||||
source_code: &str,
|
||||
solc_optimizer_enabled: bool,
|
||||
pipeline: revive_solidity::SolcPipeline,
|
||||
) -> Vec<u8> {
|
||||
let file_name = "contract.sol";
|
||||
|
||||
@@ -18,7 +52,7 @@ pub fn compile_blob_with_options(
|
||||
[(file_name.into(), source_code.into())].into(),
|
||||
Default::default(),
|
||||
None,
|
||||
revive_solidity::SolcPipeline::Yul,
|
||||
pipeline,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
solc_optimizer_enabled,
|
||||
)
|
||||
@@ -37,323 +71,15 @@ pub fn compile_blob_with_options(
|
||||
hex::decode(bytecode).expect("hex encoding should always be valid")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloy_primitives::{FixedBytes, Keccak256, I256, U256};
|
||||
use alloy_sol_types::{sol, SolCall};
|
||||
use sha1::Digest;
|
||||
pub fn assert_success(contract: Contract, differential: bool) -> State {
|
||||
let (mut instance, export) = mock_runtime::prepare(&contract.pvm_runtime, None);
|
||||
let state = mock_runtime::call(State::new(contract.calldata.clone()), &mut instance, export);
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
use crate::mock_runtime::{self, State};
|
||||
|
||||
#[test]
|
||||
fn fibonacci() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract Fibonacci {
|
||||
function fib3(uint n) public pure returns (uint);
|
||||
}
|
||||
);
|
||||
|
||||
for contract in ["FibonacciIterative", "FibonacciRecursive", "FibonacciBinet"] {
|
||||
let code = crate::compile_blob(contract, include_str!("../contracts/Fibonacci.sol"));
|
||||
|
||||
let parameter = U256::from(6);
|
||||
let input = Fibonacci::fib3Call::new((parameter,)).abi_encode();
|
||||
|
||||
let state = State::new(input);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
let expected = U256::from(8);
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
if differential {
|
||||
let evm = revive_differential::prepare(contract.evm_runtime, contract.calldata);
|
||||
assert_eq!(state.output.data.clone(), revive_differential::execute(evm));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flipper() {
|
||||
let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol"));
|
||||
let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec());
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
assert_eq!(state.output.flags, 0);
|
||||
assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap());
|
||||
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
assert_eq!(state.output.flags, 0);
|
||||
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_keccak_256() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract TestSha3 {
|
||||
function test(string memory _pre) external payable returns (bytes32);
|
||||
}
|
||||
);
|
||||
let source = r#"contract TestSha3 {
|
||||
function test(string memory _pre) external payable returns (bytes32 hash) {
|
||||
hash = keccak256(bytes(_pre));
|
||||
}
|
||||
}"#;
|
||||
let code = crate::compile_blob("TestSha3", source);
|
||||
|
||||
let param = "hello";
|
||||
let input = TestSha3::testCall::new((param.to_string(),)).abi_encode();
|
||||
|
||||
let state = State::new(input);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let mut hasher = Keccak256::new();
|
||||
hasher.update(param);
|
||||
let expected = hasher.finalize();
|
||||
let received = FixedBytes::<32>::from_slice(&state.output.data);
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn erc20() {
|
||||
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triangle_number() {
|
||||
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
|
||||
let param = U256::try_from(13).unwrap();
|
||||
let expected = U256::try_from(91).unwrap();
|
||||
|
||||
// function triangle_number(int64)
|
||||
let mut input = 0x0f760610u32.to_be_bytes().to_vec();
|
||||
input.extend_from_slice(¶m.to_be_bytes::<32>());
|
||||
|
||||
let state = State::new(input);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn odd_product() {
|
||||
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
|
||||
let param = I256::try_from(5i32).unwrap();
|
||||
let expected = I256::try_from(945i64).unwrap();
|
||||
|
||||
// function odd_product(int32)
|
||||
let mut input = 0x00261b66u32.to_be_bytes().to_vec();
|
||||
input.extend_from_slice(¶m.to_be_bytes::<32>());
|
||||
|
||||
let state = State::new(input);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msize_plain() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract MSize {
|
||||
function mSize() public pure returns (uint);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob_with_options(
|
||||
"MSize",
|
||||
include_str!("../contracts/MSize.sol"),
|
||||
false,
|
||||
);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let input = MSize::mSizeCall::new(()).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
// Solidity always stores the "free memory pointer" (32 byte int) at offset 64.
|
||||
let expected = U256::try_from(64 + 32).unwrap();
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferred_value() {
|
||||
sol!(
|
||||
contract Value {
|
||||
function value() public payable returns (uint);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob("Value", include_str!("../contracts/Value.sol"));
|
||||
let mut state = State::new(Value::valueCall::SELECTOR.to_vec());
|
||||
state.value = 0x1;
|
||||
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let expected = I256::try_from(state.value).unwrap();
|
||||
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msize_non_word_sized_access() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract MSize {
|
||||
function mStore100() public pure returns (uint);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob_with_options(
|
||||
"MSize",
|
||||
include_str!("../contracts/MSize.sol"),
|
||||
false,
|
||||
);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let input = MSize::mStore100Call::new(()).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
// https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#mstore-mload
|
||||
// "Unlike EVM, where the memory growth is in words, on zkEVM the memory growth is counted in bytes."
|
||||
// "For example, if you write mstore(100, 0) the msize on zkEVM will be 132, but on the EVM it will be 160."
|
||||
let expected = U256::try_from(132).unwrap();
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mstore8() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract MStore8 {
|
||||
function mStore8(uint value) public pure returns (uint256 word);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob_with_options(
|
||||
"MStore8",
|
||||
include_str!("../contracts/mStore8.sol"),
|
||||
false,
|
||||
);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let mut assert = |parameter, expected| {
|
||||
let input = MStore8::mStore8Call::new((parameter,)).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
};
|
||||
|
||||
for (parameter, expected) in [
|
||||
(U256::MIN, U256::MIN),
|
||||
(
|
||||
U256::from(1),
|
||||
U256::from_str_radix(
|
||||
"452312848583266388373324160190187140051835877600158453279131187530910662656",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(2),
|
||||
U256::from_str_radix(
|
||||
"904625697166532776746648320380374280103671755200316906558262375061821325312",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(255),
|
||||
U256::from_str_radix(
|
||||
"115339776388732929035197660848497720713218148788040405586178452820382218977280",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(256),
|
||||
U256::from(0),
|
||||
),
|
||||
(
|
||||
U256::from(257),
|
||||
U256::from_str_radix(
|
||||
"452312848583266388373324160190187140051835877600158453279131187530910662656",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(258),
|
||||
U256::from_str_radix(
|
||||
"904625697166532776746648320380374280103671755200316906558262375061821325312",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(123456789),
|
||||
U256::from_str_radix(
|
||||
"9498569820248594155839807363993929941088553429603327518861754938149123915776",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::MAX,
|
||||
U256::from_str_radix(
|
||||
"115339776388732929035197660848497720713218148788040405586178452820382218977280",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
] {
|
||||
assert(parameter, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sha1() {
|
||||
sol!(
|
||||
contract SHA1 {
|
||||
function sha1(bytes memory data) public pure returns (bytes20);
|
||||
}
|
||||
);
|
||||
|
||||
let code =
|
||||
crate::compile_blob_with_options("SHA1", include_str!("../contracts/sha1.sol"), false);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let pre = vec![0xffu8; 512];
|
||||
let mut hasher = sha1::Sha1::new();
|
||||
hasher.update(&pre);
|
||||
let hash = hasher.finalize();
|
||||
|
||||
let input = SHA1::sha1Call::new((pre,)).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let expected = FixedBytes::<20>::from_slice(&hash[..]);
|
||||
let received = FixedBytes::<20>::from_slice(&state.output.data[..20]);
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use alloy_primitives::{Keccak256, U256};
|
||||
use parity_scale_codec::Encode;
|
||||
use polkavm::{
|
||||
Caller, Config, Engine, ExportIndex, GasMeteringKind, Instance, Linker, Module, ModuleConfig,
|
||||
ProgramBlob, Trap,
|
||||
@@ -61,7 +60,7 @@ fn link_host_functions(engine: &Engine) -> Linker<State> {
|
||||
assert!(state.input.len() <= caller.read_u32(out_len_ptr).unwrap() as usize);
|
||||
|
||||
caller.write_memory(out_ptr, &state.input)?;
|
||||
caller.write_memory(out_len_ptr, &(state.input.len() as u32).encode())?;
|
||||
caller.write_memory(out_len_ptr, &(state.input.len() as u32).to_le_bytes())?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
@@ -91,7 +90,7 @@ fn link_host_functions(engine: &Engine) -> Linker<State> {
|
||||
let value = state.value.to_le_bytes();
|
||||
|
||||
caller.write_memory(out_ptr, &value)?;
|
||||
caller.write_memory(out_len_ptr, &(value.len() as u32).encode())?;
|
||||
caller.write_memory(out_len_ptr, &(value.len() as u32).to_le_bytes())?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
@@ -200,14 +199,12 @@ pub fn recompile_code(code: &[u8], engine: &Engine) -> Module {
|
||||
let mut module_config = ModuleConfig::new();
|
||||
module_config.set_gas_metering(Some(GasMeteringKind::Sync));
|
||||
|
||||
Module::new(&engine, &module_config, code).unwrap()
|
||||
Module::new(engine, &module_config, code).unwrap()
|
||||
}
|
||||
|
||||
pub fn instantiate_module(module: &Module, engine: &Engine) -> (Instance<State>, ExportIndex) {
|
||||
let export = module.lookup_export("call").unwrap();
|
||||
let func = link_host_functions(&engine)
|
||||
.instantiate_pre(module)
|
||||
.unwrap();
|
||||
let func = link_host_functions(engine).instantiate_pre(module).unwrap();
|
||||
let instance = func.instantiate().unwrap();
|
||||
|
||||
(instance, export)
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
use alloy_primitives::{FixedBytes, Keccak256, I256, U256};
|
||||
use alloy_sol_types::{sol, SolCall};
|
||||
use sha1::Digest;
|
||||
|
||||
use crate::{
|
||||
assert_success,
|
||||
cases::Contract,
|
||||
mock_runtime::{self, State},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn fibonacci() {
|
||||
let parameter = 6;
|
||||
|
||||
for contract in [
|
||||
Contract::fib_recursive(parameter),
|
||||
Contract::fib_iterative(parameter),
|
||||
Contract::fib_binet(parameter),
|
||||
] {
|
||||
let state = assert_success(contract, true);
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
let expected = U256::from(8);
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flipper() {
|
||||
let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol"));
|
||||
let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec());
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
assert_eq!(state.output.flags, 0);
|
||||
assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap());
|
||||
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
assert_eq!(state.output.flags, 0);
|
||||
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_keccak_256() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract TestSha3 {
|
||||
function test(string memory _pre) external payable returns (bytes32);
|
||||
}
|
||||
);
|
||||
let source = r#"contract TestSha3 {
|
||||
function test(string memory _pre) external payable returns (bytes32 hash) {
|
||||
hash = keccak256(bytes(_pre));
|
||||
}
|
||||
}"#;
|
||||
let code = crate::compile_blob("TestSha3", source);
|
||||
|
||||
let param = "hello";
|
||||
let input = TestSha3::testCall::new((param.to_string(),)).abi_encode();
|
||||
|
||||
let state = State::new(input);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let mut hasher = Keccak256::new();
|
||||
hasher.update(param);
|
||||
let expected = hasher.finalize();
|
||||
let received = FixedBytes::<32>::from_slice(&state.output.data);
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn erc20() {
|
||||
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triangle_number() {
|
||||
let state = assert_success(Contract::triangle_number(13), true);
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
let expected = U256::try_from(91).unwrap();
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn odd_product() {
|
||||
let state = assert_success(Contract::odd_product(5), true);
|
||||
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
let expected = I256::try_from(945i64).unwrap();
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msize_plain() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract MSize {
|
||||
function mSize() public pure returns (uint);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob_with_options(
|
||||
"MSize",
|
||||
include_str!("../contracts/MSize.sol"),
|
||||
false,
|
||||
revive_solidity::SolcPipeline::EVMLA,
|
||||
);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let input = MSize::mSizeCall::new(()).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
// Solidity always stores the "free memory pointer" (32 byte int) at offset 64.
|
||||
let expected = U256::try_from(64 + 32).unwrap();
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferred_value() {
|
||||
sol!(
|
||||
contract Value {
|
||||
function value() public payable returns (uint);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob("Value", include_str!("../contracts/Value.sol"));
|
||||
let mut state = State::new(Value::valueCall::SELECTOR.to_vec());
|
||||
state.value = 0x1;
|
||||
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
let state = crate::mock_runtime::call(state, &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let expected = I256::try_from(state.value).unwrap();
|
||||
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msize_non_word_sized_access() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract MSize {
|
||||
function mStore100() public pure returns (uint);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob_with_options(
|
||||
"MSize",
|
||||
include_str!("../contracts/MSize.sol"),
|
||||
false,
|
||||
revive_solidity::SolcPipeline::Yul,
|
||||
);
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let input = MSize::mStore100Call::new(()).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
// https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#mstore-mload
|
||||
// "Unlike EVM, where the memory growth is in words, on zkEVM the memory growth is counted in bytes."
|
||||
// "For example, if you write mstore(100, 0) the msize on zkEVM will be 132, but on the EVM it will be 160."
|
||||
let expected = U256::try_from(132).unwrap();
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mstore8() {
|
||||
sol!(
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
contract MStore8 {
|
||||
function mStore8(uint value) public pure returns (uint256 word);
|
||||
}
|
||||
);
|
||||
let code = crate::compile_blob("MStore8", include_str!("../contracts/mStore8.sol"));
|
||||
let (mut instance, export) = mock_runtime::prepare(&code, None);
|
||||
|
||||
let mut assert = |parameter, expected| {
|
||||
let input = MStore8::mStore8Call::new((parameter,)).abi_encode();
|
||||
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
};
|
||||
|
||||
for (parameter, expected) in [
|
||||
(U256::MIN, U256::MIN),
|
||||
(
|
||||
U256::from(1),
|
||||
U256::from_str_radix(
|
||||
"452312848583266388373324160190187140051835877600158453279131187530910662656",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(2),
|
||||
U256::from_str_radix(
|
||||
"904625697166532776746648320380374280103671755200316906558262375061821325312",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(255),
|
||||
U256::from_str_radix(
|
||||
"115339776388732929035197660848497720713218148788040405586178452820382218977280",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(U256::from(256), U256::from(0)),
|
||||
(
|
||||
U256::from(257),
|
||||
U256::from_str_radix(
|
||||
"452312848583266388373324160190187140051835877600158453279131187530910662656",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(258),
|
||||
U256::from_str_radix(
|
||||
"904625697166532776746648320380374280103671755200316906558262375061821325312",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::from(123456789),
|
||||
U256::from_str_radix(
|
||||
"9498569820248594155839807363993929941088553429603327518861754938149123915776",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
U256::MAX,
|
||||
U256::from_str_radix(
|
||||
"115339776388732929035197660848497720713218148788040405586178452820382218977280",
|
||||
10,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
] {
|
||||
assert(parameter, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sha1() {
|
||||
let pre = vec![0xffu8; 512];
|
||||
let mut hasher = sha1::Sha1::new();
|
||||
hasher.update(&pre);
|
||||
let hash = hasher.finalize();
|
||||
|
||||
let state = assert_success(Contract::sha1(pre), true);
|
||||
let expected = FixedBytes::<20>::from_slice(&hash[..]);
|
||||
let received = FixedBytes::<20>::from_slice(&state.output.data[..20]);
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
+17
-10
@@ -8,16 +8,7 @@ fn llvm_config(arg: &str) -> String {
|
||||
.unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8"))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut builder = cc::Build::new();
|
||||
llvm_config("--cxxflags")
|
||||
.split_whitespace()
|
||||
.fold(&mut builder, |builder, flag| builder.flag(flag))
|
||||
.flag("-Wno-unused-parameter")
|
||||
.cpp(true)
|
||||
.file("src/linker.cpp")
|
||||
.compile("liblinker.a");
|
||||
|
||||
fn set_rustc_link_flags() {
|
||||
println!("cargo:rustc-link-search=native={}", llvm_config("--libdir"));
|
||||
|
||||
for lib in [
|
||||
@@ -30,9 +21,25 @@ fn main() {
|
||||
"LLVMLTO",
|
||||
"LLVMTargetParser",
|
||||
"LLVMBinaryFormat",
|
||||
"LLVMDemangle",
|
||||
] {
|
||||
println!("cargo:rustc-link-lib=static={lib}");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
llvm_config("--cxxflags")
|
||||
.split_whitespace()
|
||||
.fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag))
|
||||
.flag("-Wno-unused-parameter")
|
||||
.cpp(true)
|
||||
.file("src/linker.cpp")
|
||||
.compile("liblinker.a");
|
||||
|
||||
set_rustc_link_flags();
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ pub enum Flag {
|
||||
/// The EVM legacy assembly JSON.
|
||||
#[serde(rename = "evm.legacyAssembly")]
|
||||
EVMLA,
|
||||
#[serde(rename = "evm.deployedBytecode")]
|
||||
EVMDBC,
|
||||
}
|
||||
|
||||
impl From<SolcPipeline> for Flag {
|
||||
@@ -64,6 +66,7 @@ impl std::fmt::Display for Flag {
|
||||
Self::AST => write!(f, "ast"),
|
||||
Self::Yul => write!(f, "irOptimized"),
|
||||
Self::EVMLA => write!(f, "evm.legacyAssembly"),
|
||||
Self::EVMDBC => write!(f, "evm.deployedBytecode"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ impl File {
|
||||
Self {
|
||||
per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
|
||||
per_contract: Some(HashSet::from_iter([
|
||||
SelectionFlag::EVMDBC,
|
||||
SelectionFlag::MethodIdentifiers,
|
||||
SelectionFlag::Metadata,
|
||||
SelectionFlag::from(pipeline),
|
||||
|
||||
@@ -23,3 +23,22 @@ impl Bytecode {
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output contract EVM deployed bytecode.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeployedBytecode {
|
||||
/// The bytecode object.
|
||||
pub object: String,
|
||||
}
|
||||
|
||||
impl DeployedBytecode {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(object: String) -> Self {
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use serde::Serialize;
|
||||
use crate::evmla::assembly::Assembly;
|
||||
|
||||
use self::bytecode::Bytecode;
|
||||
use self::bytecode::DeployedBytecode;
|
||||
use self::extra_metadata::ExtraMetadata;
|
||||
|
||||
///
|
||||
@@ -32,6 +33,8 @@ pub struct EVM {
|
||||
/// The contract bytecode.
|
||||
/// Is reset by that of EraVM before yielding the compiled project artifacts.
|
||||
pub bytecode: Option<Bytecode>,
|
||||
/// The contract deployed bytecode.
|
||||
pub deployed_bytecode: Option<DeployedBytecode>,
|
||||
/// The contract function signatures.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub method_identifiers: Option<BTreeMap<String, String>>,
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
|
||||
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
use crate::solc::standard_json::input::Input as SolcStandardJsonInput;
|
||||
use crate::solc::standard_json::output::contract::evm::bytecode::DeployedBytecode;
|
||||
use crate::solc::standard_json::output::Output as SolcStandardJsonOutput;
|
||||
use crate::solc::Compiler as SolcCompiler;
|
||||
use crate::warning::Warning;
|
||||
@@ -100,6 +101,59 @@ pub fn build_solidity_with_options(
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Build a Solidity contract and get the EVM bin-runtime.
|
||||
pub fn build_solidity_with_options_evm(
|
||||
sources: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
pipeline: SolcPipeline,
|
||||
solc_optimizer_enabled: bool,
|
||||
) -> anyhow::Result<BTreeMap<String, DeployedBytecode>> {
|
||||
check_dependencies();
|
||||
|
||||
inkwell::support::enable_llvm_pretty_stack_trace();
|
||||
era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM);
|
||||
let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
|
||||
|
||||
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
||||
let solc_version = solc.version()?;
|
||||
|
||||
let input = SolcStandardJsonInput::try_from_sources(
|
||||
None,
|
||||
sources.clone(),
|
||||
libraries.clone(),
|
||||
remappings,
|
||||
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
|
||||
SolcStandardJsonInputSettingsOptimizer::new(
|
||||
solc_optimizer_enabled,
|
||||
None,
|
||||
&solc_version.default,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
None,
|
||||
pipeline == SolcPipeline::Yul,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
|
||||
|
||||
let mut contracts = BTreeMap::new();
|
||||
if let Some(files) = output.contracts.as_mut() {
|
||||
for (_, file) in files.iter_mut() {
|
||||
for (name, contract) in file.iter_mut() {
|
||||
if let Some(evm) = contract.evm.as_mut() {
|
||||
if let Some(deployed_bytecode) = evm.deployed_bytecode.as_ref() {
|
||||
contracts.insert(name.clone(), deployed_bytecode.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(contracts)
|
||||
}
|
||||
|
||||
///
|
||||
/// Builds the Solidity project and returns the standard JSON output.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user