mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-25 22:17:58 +00:00
Emerge Yul recompiler (#1)
Provide a modified (and incomplete) version of ZKSync zksolc that can compile the most basic contracts
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
pub mod mock_runtime;
|
||||
|
||||
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
|
||||
let file_name = "contract.sol";
|
||||
|
||||
let contracts = revive_solidity::test_utils::build_solidity(
|
||||
[(file_name.into(), source_code.into())].into(),
|
||||
Default::default(),
|
||||
None,
|
||||
revive_solidity::SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("source should compile")
|
||||
.contracts
|
||||
.expect("source should contain at least one contract");
|
||||
|
||||
let bytecode = contracts[file_name][contract_name]
|
||||
.evm
|
||||
.as_ref()
|
||||
.expect("source should produce EVM output")
|
||||
.assembly_text
|
||||
.as_ref()
|
||||
.expect("source should produce assembly text");
|
||||
|
||||
hex::decode(bytecode).expect("hex encoding should always be valid")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloy_primitives::U256;
|
||||
|
||||
use crate::mock_runtime::{self, State};
|
||||
|
||||
#[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 (instance, export) = mock_runtime::prepare(&code);
|
||||
|
||||
let state = crate::mock_runtime::call(state, &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, &instance, export);
|
||||
assert_eq!(state.output.flags, 0);
|
||||
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
|
||||
}
|
||||
|
||||
#[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 = alloy_primitives::U256::try_from(13).unwrap();
|
||||
let expected = alloy_primitives::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 (instance, export) = mock_runtime::prepare(&code);
|
||||
let state = crate::mock_runtime::call(state, &instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received =
|
||||
alloy_primitives::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 = alloy_primitives::I256::try_from(5i32).unwrap();
|
||||
let expected = alloy_primitives::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 (instance, export) = mock_runtime::prepare(&code);
|
||||
let state = crate::mock_runtime::call(state, &instance, export);
|
||||
|
||||
assert_eq!(state.output.flags, 0);
|
||||
|
||||
let received =
|
||||
alloy_primitives::I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||
assert_eq!(received, expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
//! Mock environment used for integration tests.
|
||||
//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk
|
||||
use std::collections::HashMap;
|
||||
|
||||
use alloy_primitives::U256;
|
||||
use parity_scale_codec::Encode;
|
||||
use polkavm::{
|
||||
Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module,
|
||||
ModuleConfig, ProgramBlob, Trap,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct State {
|
||||
pub input: Vec<u8>,
|
||||
pub output: CallOutput,
|
||||
pub value: u128,
|
||||
pub storage: HashMap<U256, U256>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CallOutput {
|
||||
pub flags: u32,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for CallOutput {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
flags: u32::MAX,
|
||||
data: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(input: Vec<u8>) -> Self {
|
||||
Self {
|
||||
input,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_output(&mut self) {
|
||||
self.output = Default::default();
|
||||
}
|
||||
|
||||
pub fn assert_storage_key(&self, at: U256, expect: U256) {
|
||||
assert_eq!(self.storage[&at], expect);
|
||||
}
|
||||
}
|
||||
|
||||
fn link_host_functions(engine: &Engine) -> Linker<State> {
|
||||
let mut linker = Linker::new(engine);
|
||||
|
||||
linker
|
||||
.func_wrap(
|
||||
"input",
|
||||
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
|
||||
let (mut caller, state) = caller.split();
|
||||
|
||||
assert_ne!(0, caller.read_u32(out_len_ptr)?);
|
||||
|
||||
caller.write_memory(out_ptr, &state.input)?;
|
||||
caller.write_memory(out_len_ptr, &(state.input.len() as u32).encode())?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
linker
|
||||
.func_wrap(
|
||||
"seal_return",
|
||||
|caller: Caller<State>, flags: u32, data_ptr: u32, data_len: u32| -> Result<(), Trap> {
|
||||
let (caller, state) = caller.split();
|
||||
|
||||
state.output.flags = flags;
|
||||
state.output.data = caller.read_memory_into_vec(data_ptr, data_len)?;
|
||||
|
||||
Err(Default::default())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
linker
|
||||
.func_wrap(
|
||||
"value_transferred",
|
||||
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
|
||||
let (mut caller, state) = caller.split();
|
||||
|
||||
let value = state.value.encode();
|
||||
|
||||
caller.write_memory(out_ptr, &value)?;
|
||||
caller.write_memory(out_len_ptr, &(value.len() as u32).encode())?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
linker
|
||||
.func_wrap(
|
||||
"debug_message",
|
||||
|caller: Caller<State>, str_ptr: u32, str_len: u32| -> Result<u32, Trap> {
|
||||
let (caller, _) = caller.split();
|
||||
|
||||
let data = caller.read_memory_into_vec(str_ptr, str_len)?;
|
||||
print!("debug_message: {}", String::from_utf8(data).unwrap());
|
||||
|
||||
Ok(0)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
linker
|
||||
.func_wrap(
|
||||
"set_storage",
|
||||
|caller: Caller<State>,
|
||||
key_ptr: u32,
|
||||
key_len: u32,
|
||||
value_ptr: u32,
|
||||
value_len: u32|
|
||||
-> Result<u32, Trap> {
|
||||
let (caller, state) = caller.split();
|
||||
|
||||
assert_eq!(key_len, 32, "storage key must be 32 bytes");
|
||||
assert_eq!(value_len, 32, "storage value must be 32 bytes");
|
||||
|
||||
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
|
||||
let value = caller.read_memory_into_vec(value_ptr, value_len)?;
|
||||
|
||||
state.storage.insert(
|
||||
U256::from_be_bytes::<32>(key.try_into().unwrap()),
|
||||
U256::from_be_bytes::<32>(value.try_into().unwrap()),
|
||||
);
|
||||
|
||||
Ok(0)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
linker
|
||||
.func_wrap(
|
||||
"get_storage",
|
||||
|caller: Caller<State>,
|
||||
key_ptr: u32,
|
||||
key_len: u32,
|
||||
out_ptr: u32,
|
||||
out_len_ptr: u32|
|
||||
-> Result<u32, Trap> {
|
||||
let (mut caller, state) = caller.split();
|
||||
|
||||
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
|
||||
let out_len = caller.read_u32(out_len_ptr)?;
|
||||
assert!(out_len >= 32);
|
||||
|
||||
let value = state
|
||||
.storage
|
||||
.get(&U256::from_be_bytes::<32>(key.try_into().unwrap()))
|
||||
.map(U256::to_be_bytes::<32>)
|
||||
.unwrap_or_default();
|
||||
|
||||
caller.write_memory(out_ptr, &value[..])?;
|
||||
caller.write_memory(out_len_ptr, &32u32.to_le_bytes())?;
|
||||
|
||||
Ok(0)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
linker
|
||||
}
|
||||
|
||||
pub fn prepare(code: &[u8]) -> (InstancePre<State>, ExportIndex) {
|
||||
let blob = ProgramBlob::parse(code).unwrap();
|
||||
|
||||
let engine = Engine::new(&Config::new()).unwrap();
|
||||
|
||||
let mut module_config = ModuleConfig::new();
|
||||
module_config.set_gas_metering(Some(GasMeteringKind::Sync));
|
||||
|
||||
let module = Module::from_blob(&engine, &module_config, &blob).unwrap();
|
||||
let export = module.lookup_export("call").unwrap();
|
||||
let func = link_host_functions(&engine)
|
||||
.instantiate_pre(&module)
|
||||
.unwrap();
|
||||
|
||||
(func, export)
|
||||
}
|
||||
|
||||
pub fn call(mut state: State, on: &InstancePre<State>, export: ExportIndex) -> State {
|
||||
state.reset_output();
|
||||
|
||||
let mut state_args = polkavm::StateArgs::default();
|
||||
state_args.set_gas(polkavm::Gas::MAX);
|
||||
|
||||
let call_args = polkavm::CallArgs::new(&mut state, export);
|
||||
|
||||
match on.instantiate().unwrap().call(state_args, call_args) {
|
||||
Err(polkavm::ExecutionError::Trap(_)) => state,
|
||||
Err(other) => panic!("unexpected error: {other}"),
|
||||
Ok(_) => panic!("unexpected return"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user