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: impl contract calls #26

Merged
merged 9 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# Ignore RISCV emulator files
**/rvemu.*
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ resolver = "2"
members = ["eth-riscv-interpreter", "eth-riscv-syscalls", "r55"]
default-members = ["eth-riscv-interpreter", "eth-riscv-syscalls", "r55"]

exclude = ["contract-derive", "erc20", "eth-riscv-runtime"]
exclude = ["contract-derive", "erc20", "erc20x", "eth-riscv-runtime"]

[workspace.package]
version = "0.1.0"
Expand All @@ -17,7 +17,8 @@ repository = "https://github.com/leonardoalt/r5"
eth-riscv-interpreter = { path = "eth-riscv-interpreter" }
eth-riscv-syscalls = { path = "eth-riscv-syscalls" }

env_logger = "0.11.5"
eyre = "0.6.12"
log = "0.4.22"
thiserror = "2.0.3"

tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
1 change: 1 addition & 0 deletions contract-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
alloy-core = { version = "0.7.4", default-features = false }
alloy-sol-types = { version = "0.7.4", default-features = false }

[lib]
proc-macro = true
151 changes: 136 additions & 15 deletions contract-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
extern crate proc_macro;
use alloy_core::primitives::keccak256;
use alloy_sol_types::SolValue;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, ImplItem, ItemImpl, DeriveInput, Data, Fields};
use syn::{parse_macro_input, Data, DeriveInput, Fields, ImplItem, ItemImpl, ItemTrait, TraitItem};
use syn::{FnArg, ReturnType};

#[proc_macro_derive(Event, attributes(indexed))]
pub fn event_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;

let fields = if let Data::Struct(data) = &input.data {
if let Fields::Named(fields) = &data.fields {
&fields.named
Expand All @@ -25,9 +26,7 @@ pub fn event_derive(input: TokenStream) -> TokenStream {
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
let indexed_fields: Vec<_> = fields
.iter()
.filter(|f| {
f.attrs.iter().any(|attr| attr.path.is_ident("indexed"))
})
.filter(|f| f.attrs.iter().any(|attr| attr.path.is_ident("indexed")))
.map(|f| &f.ident)
.collect();

Expand Down Expand Up @@ -62,7 +61,7 @@ pub fn event_derive(input: TokenStream) -> TokenStream {
#(
if !first { signature.extend_from_slice(b","); }
first = false;

signature.extend_from_slice(self.#field_names.sol_type_name().as_bytes());
let encoded = self.#field_names.abi_encode();

Expand Down Expand Up @@ -174,40 +173,40 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
$arg.sol_type_name().as_bytes()
};
}

#[macro_export]
macro_rules! emit {
($event:ident, $($field:expr),*) => {{
use alloy_sol_types::SolValue;
use alloy_core::primitives::{keccak256, B256, U256, I256};
use alloc::vec::Vec;

let mut signature = alloc::vec![];
signature.extend_from_slice($event::NAME.as_bytes());
signature.extend_from_slice(b"(");

let mut first = true;
let mut topics = alloc::vec![B256::default()];
let mut data = Vec::new();

$(
if !first { signature.extend_from_slice(b","); }
first = false;

signature.extend_from_slice(get_type_signature!($field));
let encoded = $field.abi_encode();

let field_ident = stringify!($field);
if $event::INDEXED_FIELDS.contains(&field_ident) && topics.len() < 4 {
topics.push(B256::from_slice(&encoded));
} else {
data.extend_from_slice(&encoded);
}
)*

signature.extend_from_slice(b")");
topics[0] = B256::from(keccak256(&signature));

if !data.is_empty() {
eth_riscv_runtime::emit_log(&data, &topics);
} else if topics.len() > 1 {
Expand Down Expand Up @@ -275,4 +274,126 @@ fn is_payable(method: &syn::ImplItemMethod) -> bool {
}
false
})
}
}

#[proc_macro_attribute]
0xrusowsky marked this conversation as resolved.
Show resolved Hide resolved
pub fn interface(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemTrait);
let trait_name = &input.ident;

let method_impls: Vec<_> = input
.items
.iter()
.map(|item| {
if let TraitItem::Method(method) = item {
let method_name = &method.sig.ident;
let selector_bytes = keccak256(method_name.to_string())[..4]
.try_into()
.unwrap_or_default();
let method_selector = u32::from_be_bytes(selector_bytes);

// Extract argument types and names, skipping self
let arg_types: Vec<_> = method
.sig
.inputs
.iter()
.skip(1)
.map(|arg| {
if let FnArg::Typed(pat_type) = arg {
let ty = &*pat_type.ty;
quote! { #ty }
} else {
panic!("Expected typed arguments");
}
})
.collect();
let arg_names: Vec<_> = (0..method.sig.inputs.len() - 1)
.map(|i| format_ident!("arg{}", i))
.collect();

// Get the return type
let return_type = match &method.sig.output {
ReturnType::Default => quote! { () },
ReturnType::Type(_, ty) =>
quote! { #ty },
};

// Generate calldata with different encoding depending on # of args
let args_encoding = if arg_names.is_empty() {
quote! {
let mut complete_calldata = Vec::with_capacity(4);
complete_calldata.extend_from_slice(&[
#method_selector.to_be_bytes()[0],
#method_selector.to_be_bytes()[1],
#method_selector.to_be_bytes()[2],
#method_selector.to_be_bytes()[3],
]);
}
} else if arg_names.len() == 1 {
quote! {
let mut args_calldata = #(#arg_names),*.abi_encode();
let mut complete_calldata = Vec::with_capacity(4 + args_calldata.len());
complete_calldata.extend_from_slice(&[
#method_selector.to_be_bytes()[0],
#method_selector.to_be_bytes()[1],
#method_selector.to_be_bytes()[2],
#method_selector.to_be_bytes()[3],
]);
complete_calldata.append(&mut args_calldata);
}
} else {
quote! {
let mut args_calldata = (#(#arg_names),*).abi_encode();
let mut complete_calldata = Vec::with_capacity(4 + args_calldata.len());
complete_calldata.extend_from_slice(&[
#method_selector.to_be_bytes()[0],
#method_selector.to_be_bytes()[1],
#method_selector.to_be_bytes()[2],
#method_selector.to_be_bytes()[3],
]);
complete_calldata.append(&mut args_calldata);
}
};

Some(quote! {
pub fn #method_name(&self, #(#arg_names: #arg_types),*) -> Option<#return_type> {
use alloy_sol_types::SolValue;
use alloc::vec::Vec;

#args_encoding

// Make the call
let result = eth_riscv_runtime::call_contract(
self.address,
0_u64,
&complete_calldata,
32_u64 // TODO: Figure out how to use SolType to get the return size

)?;

// Decode result
<#return_type>::abi_decode(&result, true).ok()
}
})
} else {
panic!("Expected methods arguments");
}
})
.collect();

let expanded = quote! {
pub struct #trait_name {
address: Address,
}

impl #trait_name {
pub fn new(address: Address) -> Self {
Self { address }
}

#(#method_impls)*
}
};

TokenStream::from(expanded)
}
8 changes: 8 additions & 0 deletions erc20x/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[target.riscv64imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-T../r5-rust-rt.x",
"-C", "inline-threshold=275"
]

[build]
target = "riscv64imac-unknown-none-elf"
23 changes: 23 additions & 0 deletions erc20x/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "erc20x"
version = "0.1.0"
edition = "2021"

[dependencies]
contract-derive = { path = "../contract-derive" }
eth-riscv-runtime = { path = "../eth-riscv-runtime" }

alloy-core = { version = "0.7.4", default-features = false }
alloy-sol-types = { version = "0.7.4", default-features = false }

[[bin]]
name = "runtime"
path = "src/lib.rs"

[[bin]]
name = "deploy"
path = "src/deploy.rs"

[profile.release]
lto = true
opt-level = "z"
25 changes: 25 additions & 0 deletions erc20x/src/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#![no_std]
#![no_main]

extern crate alloc;
use alloc::vec::Vec;

use eth_riscv_runtime::return_riscv;

#[eth_riscv_runtime::entry]
fn main() -> !
{
//decode constructor arguments
//constructor(ars);
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);
prepended_runtime.extend_from_slice(runtime);

let prepended_runtime_slice: &[u8] = &prepended_runtime;

let result_ptr = prepended_runtime_slice.as_ptr() as u64;
let result_len = prepended_runtime_slice.len() as u64;
return_riscv(result_ptr, result_len);
}
31 changes: 31 additions & 0 deletions erc20x/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#![no_std]
#![no_main]

use core::default::Default;

use contract_derive::{contract, interface, payable, Event};
use eth_riscv_runtime::types::Mapping;

use alloy_core::primitives::{address, Address, U256};

extern crate alloc;
use alloc::{string::String, vec::Vec};

#[derive(Default)]
pub struct ERC20x;

#[interface]
trait IERC20 {
fn balance_of(&self, owner: Address) -> u64;
}

#[contract]
impl ERC20x {
pub fn x_balance_of(&self, owner: Address, target: Address) -> u64 {
let token = IERC20::new(target);
match token.balance_of(owner) {
Some(balance) => balance,
_ => eth_riscv_runtime::revert(),
}
}
}
21 changes: 21 additions & 0 deletions eth-riscv-runtime/src/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![no_std]

extern crate alloc;
use alloc::vec::Vec;
use alloy_core::primitives::{Address, Bytes, B256};

pub fn call_contract(addr: Address, value: u64, data: &[u8], ret_size: u64) -> Option<Bytes> {
let mut ret_data = Vec::with_capacity(ret_size as usize);
ret_data.resize(ret_size as usize, 0);

crate::call(
addr,
value,
data.as_ptr() as u64,
data.len() as u64,
ret_data.as_ptr() as u64,
ret_size,
);

Some(Bytes::from(ret_data))
}
Loading
Loading