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

feat: constructor support #31

Merged
merged 21 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from 16 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
27 changes: 25 additions & 2 deletions contract-derive/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,39 @@ where

// Helper function to generate the deployment code
pub fn generate_deployment_code(
_struct_name: &Ident,
struct_name: &Ident,
constructor: Option<&ImplItemMethod>,
) -> quote::__private::TokenStream {
// Decode constructor args + trigger constructor logic
let constructor_code = match constructor {
Some(method) => {
let method_info = MethodInfo::from(method);
let (arg_names, arg_types) = get_arg_props_all(&method_info);
quote! {
impl #struct_name { #method }

// Get encoded constructor args
let calldata = eth_riscv_runtime::msg_data();

let (#(#arg_names),*) = <(#(#arg_types),*)>::abi_decode(&calldata, true)
.expect("Failed to decode constructor args");
#struct_name::new(#(#arg_names),*);
}
}
None => quote! {
#struct_name::default();
},
};

quote! {
use alloc::vec::Vec;
use alloy_core::primitives::U32;

#[no_mangle]
pub extern "C" fn main() -> ! {
// TODO: figure out constructor
#constructor_code

// Return runtime code
let runtime: &[u8] = include_bytes!("../target/riscv64imac-unknown-none-elf/release/runtime");
let mut prepended_runtime = Vec::with_capacity(1 + runtime.len());
prepended_runtime.push(0xff);
Expand Down
10 changes: 9 additions & 1 deletion contract-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
}

let input_methods: Vec<_> = public_methods
.iter()
.map(|method| quote! { #method })
.collect();
let match_arms: Vec<_> = public_methods.iter().map(|method| {
let method_name = &method.sig.ident;
let method_selector = u32::from_be_bytes(
Expand Down Expand Up @@ -235,6 +239,10 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
#[cfg(feature = "deploy")]
pub mod deploy {
use super::*;
use alloy_sol_types::SolValue;
use eth_riscv_runtime::*;

#emit_helper
#deployment_code
}

Expand All @@ -253,9 +261,9 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
use alloy_sol_types::SolValue;
use eth_riscv_runtime::*;

#input
#emit_helper

impl #struct_name { #(#input_methods)* }
impl Contract for #struct_name {
fn call(&self) {
self.call_with_data(&msg_data());
Expand Down
18 changes: 18 additions & 0 deletions erc20/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ pub struct Mint {

#[contract]
impl ERC20 {
pub fn new(to: Address, value: U256) -> Self {
let value = value.to::<u64>();

// init the contract
let erc20 = ERC20::default();

// pre-mint some tokens
erc20.balances.write(to, value);
log::emit(Transfer::new(
address!("0000000000000000000000000000000000000000"),
to,
value,
));

// return the initialized contract
erc20
}

pub fn balance_of(&self, owner: Address) -> u64 {
self.balances.read(owner)
}
Expand Down
62 changes: 55 additions & 7 deletions r55/src/exec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloy_core::primitives::Keccak256;
use alloy_core::primitives::{Keccak256, U32};
use core::{cell::RefCell, ops::Range};
use eth_riscv_interpreter::setup_from_elf;
use eth_riscv_syscalls::Syscall;
Expand All @@ -13,21 +13,40 @@ use revm::{
};
use rvemu::{emulator::Emulator, exception::Exception};
use std::{collections::BTreeMap, rc::Rc, sync::Arc};
use tracing::{debug, trace, warn};
use tracing::{debug, info, trace, warn};

use super::error::{Error, Result, TxResult};
use super::gas;
use super::syscall_gas;

const R5_REST_OF_RAM_INIT: u64 = 0x80300000; // Defined at `r5-rust-rt.x`

pub fn deploy_contract(db: &mut InMemoryDB, bytecode: Bytes) -> Result<Address> {
pub fn deploy_contract(
db: &mut InMemoryDB,
bytecode: Bytes,
encoded_args: Option<Vec<u8>>,
) -> Result<Address> {
// Craft initcode: [0xFF][codesize][bytecode][constructor_args]
let codesize = U32::from(bytecode.len());
debug!("CODESIZE: {}", codesize);

let mut init_code = Vec::new();
init_code.push(0xff);
init_code.extend_from_slice(&Bytes::from(codesize.to_be_bytes_vec()));
init_code.extend_from_slice(&bytecode);
if let Some(args) = encoded_args {
debug!("ENCODED_ARGS: {:#?}", Bytes::from(args.clone()));
init_code.extend_from_slice(&args);
}
debug!("INITCODE SIZE: {}", init_code.len());

// Run CREATE tx
let mut evm = Evm::builder()
.with_db(db)
.modify_tx_env(|tx| {
tx.caller = address!("000000000000000000000000000000000000000A");
tx.transact_to = TransactTo::Create;
tx.data = bytecode;
tx.data = Bytes::from(init_code);
tx.value = U256::from(0);
})
.append_handler_register(handle_register)
Expand All @@ -39,9 +58,13 @@ pub fn deploy_contract(db: &mut InMemoryDB, bytecode: Bytes) -> Result<Address>
match result {
ExecutionResult::Success {
output: Output::Create(_value, Some(addr)),
logs,
..
} => {
debug!("Deployed at addr: {:?}", addr);
info!(
"NEW DEPLOYMENT:\n> contract address: {:?}\n> logs: {:#?}\n",
addr, logs
);
Ok(addr)
}
result => Err(Error::UnexpectedExecResult(result)),
Expand Down Expand Up @@ -95,10 +118,27 @@ fn riscv_context(frame: &Frame) -> Option<RVEmu> {
let interpreter = frame.interpreter();

let Some((0xFF, bytecode)) = interpreter.bytecode.split_first() else {
warn!("NOT RISCV CONTRACT!");
return None;
};

match setup_from_elf(bytecode, &interpreter.contract.input) {
let (code, calldata) = if frame.is_create() {
let (code_size, init_code) = bytecode.split_at(4);
let Some((0xFF, bytecode)) = init_code.split_first() else {
warn!("NOT RISCV CONTRACT!");
return None;
};
let code_size = U32::from_be_slice(code_size).to::<usize>() - 1; // deduct control byte `0xFF`
let end_of_args = init_code.len() - 34; // deduct control byte + ignore empty (32 byte) word appended by revm

(&bytecode[..code_size], &bytecode[code_size..end_of_args])
} else if frame.is_call() {
(bytecode, interpreter.contract.input.as_ref())
} else {
todo!("Support EOF")
};

match setup_from_elf(code, calldata) {
Ok(emu) => Some(RVEmu {
emu,
returned_data_destiny: None,
Expand All @@ -119,6 +159,7 @@ pub fn handle_register<EXT, DB: Database>(handler: &mut EvmHandler<'_, EXT, DB>)
handler.execution.call = Arc::new(move |ctx, inputs| {
let result = old_handle(ctx, inputs);
if let Ok(FrameOrResult::Frame(frame)) = &result {
trace!("Creating new CALL frame");
call_stack_inner.borrow_mut().push(riscv_context(frame));
}
result
Expand All @@ -130,6 +171,7 @@ pub fn handle_register<EXT, DB: Database>(handler: &mut EvmHandler<'_, EXT, DB>)
handler.execution.create = Arc::new(move |ctx, inputs| {
let result = old_handle(ctx, inputs);
if let Ok(FrameOrResult::Frame(frame)) = &result {
trace!("Creating new CREATE frame");
call_stack_inner.borrow_mut().push(riscv_context(frame));
}
result
Expand Down Expand Up @@ -272,7 +314,7 @@ fn execute_riscv(
Syscall::SLoad => {
let key: u64 = emu.cpu.xregs.read(10);
debug!(
"> SLOAD ({}) - Key: {}",
"> SLOAD ({}) - Key: {:#02x}",
interpreter.contract.target_address, key
);
match host.sload(interpreter.contract.target_address, U256::from(key)) {
Expand Down Expand Up @@ -306,6 +348,12 @@ fn execute_riscv(
let second: u64 = emu.cpu.xregs.read(12);
let third: u64 = emu.cpu.xregs.read(13);
let fourth: u64 = emu.cpu.xregs.read(14);
debug!(
"> SSTORE ({}) - Key: {:#02x}, Value: {}",
interpreter.contract.target_address,
key,
U256::from_limbs([first, second, third, fourth])
);
let result = host.sstore(
interpreter.contract.target_address,
U256::from(key),
Expand Down
29 changes: 23 additions & 6 deletions r55/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use r55::{
test_utils::{add_balance_to_db, get_selector_from_sig, initialize_logger},
};
use revm::{
primitives::{address, Address},
primitives::{address, Address, U256},
InMemoryDB,
};
use tracing::{debug, error, info};
Expand All @@ -20,22 +20,24 @@ fn erc20() {

let mut db = InMemoryDB::default();

let alice: Address = address!("000000000000000000000000000000000000000A");
add_balance_to_db(&mut db, alice, 1e18 as u64);

let constructor = (alice, U256::from(1_000_000_u64)).abi_encode();

let bytecode = compile_with_prefix(compile_deploy, ERC20_PATH).unwrap();
let bytecode_x = compile_with_prefix(compile_deploy, ERC20X_PATH).unwrap();
let addr1 = deploy_contract(&mut db, bytecode).unwrap();
let addr2 = deploy_contract(&mut db, bytecode_x).unwrap();
let addr1 = deploy_contract(&mut db, bytecode, Some(constructor)).unwrap();
let addr2 = deploy_contract(&mut db, bytecode_x, None).unwrap();

let selector_balance = get_selector_from_sig("balance_of");
let selector_x_balance = get_selector_from_sig("x_balance_of");
let selector_mint = get_selector_from_sig("mint");
let alice: Address = address!("000000000000000000000000000000000000000A");
let value_mint: u64 = 42;
let mut calldata_balance = alice.abi_encode();
let mut calldata_mint = (alice, value_mint).abi_encode();
let mut calldata_x_balance = (alice, addr1).abi_encode();

add_balance_to_db(&mut db, alice, 1e18 as u64);

let mut complete_calldata_balance = selector_balance.to_vec();
complete_calldata_balance.append(&mut calldata_balance);

Expand All @@ -45,6 +47,21 @@ fn erc20() {
let mut complete_calldata_x_balance = selector_x_balance.to_vec();
complete_calldata_x_balance.append(&mut calldata_x_balance);

info!("----------------------------------------------------------");
info!("-- INITIAL BALANCE OF TX ---------------------------------");
info!("----------------------------------------------------------");
debug!(
"Tx Calldata:\n> {:#?}",
Bytes::from(complete_calldata_balance.clone())
);
match run_tx(&mut db, &addr1, complete_calldata_balance.clone()) {
Ok(res) => info!("{}", res),
Err(e) => {
error!("Error when executing tx! {:#?}", e);
panic!()
}
};

info!("----------------------------------------------------------");
info!("-- MINT TX -----------------------------------------------");
info!("----------------------------------------------------------");
Expand Down
2 changes: 1 addition & 1 deletion r55/tests/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn deploy_erc20x_without_contract_dependencies() {
}
};

match deploy_contract(&mut db, bytecode) {
match deploy_contract(&mut db, bytecode, None) {
Ok(addr) => info!("Contract deployed at {}", addr),
Err(e) => {
error!("Failed to deploy ERC20X: {:?}", e);
Expand Down
Loading