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: built in deployment #29

Merged
merged 4 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
68 changes: 49 additions & 19 deletions contract-derive/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use alloy_core::primitives::keccak256;
use alloy_sol_types::SolValue;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{FnArg, Ident, ImplItemMethod, ReturnType, TraitItemMethod};

Expand All @@ -15,7 +13,7 @@ impl<'a> From<&'a ImplItemMethod> for MethodInfo<'a> {
fn from(method: &'a ImplItemMethod) -> Self {
Self {
name: &method.sig.ident,
args: method.sig.inputs.iter().skip(1).cloned().collect(),
args: method.sig.inputs.iter().cloned().collect(),
return_type: &method.sig.output,
}
}
Expand All @@ -25,13 +23,33 @@ impl<'a> From<&'a TraitItemMethod> for MethodInfo<'a> {
fn from(method: &'a TraitItemMethod) -> Self {
Self {
name: &method.sig.ident,
args: method.sig.inputs.iter().skip(1).cloned().collect(),
args: method.sig.inputs.iter().cloned().collect(),
return_type: &method.sig.output,
}
}
}

// Helper function to generate intercate impl from user-defined methods
// Helper function to get the parameter names + types of a method
pub fn get_arg_props<'a>(
skip_first_arg: bool,
method: &'a MethodInfo<'a>,
) -> (Vec<Ident>, Vec<&syn::Type>) {
method
.args
.iter()
.skip(if skip_first_arg { 1 } else { 0 })
0xrusowsky marked this conversation as resolved.
Show resolved Hide resolved
.enumerate()
.map(|(i, arg)| {
if let FnArg::Typed(pat_type) = arg {
(format_ident!("arg{}", i), &*pat_type.ty)
} else {
panic!("Expected typed arguments");
}
})
.unzip()
}

// Helper function to generate interface impl from user-defined methods
pub fn generate_interface<T>(
methods: &[&T],
interface_name: &Ident,
Expand All @@ -44,27 +62,14 @@ where
// Generate implementation
let method_impls = methods.iter().map(|method| {
let name = method.name;
let args = &method.args;
let return_type = method.return_type;
let method_selector = u32::from_be_bytes(
keccak256(name.to_string())[..4]
.try_into()
.unwrap_or_default(),
);

// Simply use index for arg names, and extract types
let (arg_names, arg_types): (Vec<_>, Vec<_>) = args
.iter()
.enumerate()
.map(|(i, arg)| {
if let FnArg::Typed(pat_type) = arg {
let ty = &*pat_type.ty;
(format_ident!("arg{}", i), ty)
} else {
panic!("Expected typed arguments");
}
})
.unzip();
let (arg_names, arg_types) = get_arg_props(true, method);

let calldata = if arg_names.is_empty() {
quote! {
Expand Down Expand Up @@ -128,3 +133,28 @@ where
}
}
}

// Helper function to generate the deployment code
pub fn generate_deployment_code(
_struct_name: &Ident,
constructor: Option<&ImplItemMethod>,
) -> quote::__private::TokenStream {
quote! {
use alloc::vec::Vec;

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

let runtime: &[u8] = include_bytes!("../target/riscv64imac-unknown-none-elf/release/runtime");
0xrusowsky marked this conversation as resolved.
Show resolved Hide resolved
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;
eth_riscv_runtime::return_riscv(result_ptr, result_len);
}
}
}
58 changes: 37 additions & 21 deletions contract-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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, Data, DeriveInput, Fields, FnArg, ImplItem, ImplItemMethod, ItemImpl,
ItemTrait, ReturnType, TraitItem,
parse_macro_input, Data, DeriveInput, Fields, ImplItem, ImplItemMethod, ItemImpl, ItemTrait,
ReturnType, TraitItem,
};

mod helpers;
use crate::helpers::MethodInfo;

#[proc_macro_derive(Event, attributes(indexed))]
pub fn event_derive(input: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -104,34 +104,31 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
panic!("Expected a struct.");
};

let mut constructor = None;
let mut public_methods: Vec<&ImplItemMethod> = Vec::new();

// Iterate over the items in the impl block to find pub methods
// Iterate over the items in the impl block to find pub methods + constructor
for item in input.items.iter() {
if let ImplItem::Method(method) = item {
if let syn::Visibility::Public(_) = method.vis {
if method.sig.ident == "new" {
constructor = Some(method);
} else if let syn::Visibility::Public(_) = method.vis {
public_methods.push(method);
}
}
}

let match_arms: Vec<_> = public_methods.iter().enumerate().map(|(_, method)| {
let match_arms: Vec<_> = public_methods.iter().map(|method| {
let method_name = &method.sig.ident;
let method_selector = u32::from_be_bytes(
keccak256(
method_name.to_string()
)[..4].try_into().unwrap_or_default()
);
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 method_info = MethodInfo::from(*method);
let (arg_names, arg_types) = helpers::get_arg_props(true, &method_info);

let arg_names: Vec<_> = (0..method.sig.inputs.len() - 1).map(|i| format_ident!("arg{}", i)).collect();
// Check if there are payable methods
let checks = if !is_payable(&method) {
quote! {
if eth_riscv_runtime::msg_value() > U256::from(0) {
Expand All @@ -141,6 +138,7 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
} else {
quote! {}
};

// Check if the method has a return type
let return_handling = match &method.sig.output {
ReturnType::Default => {
Expand All @@ -156,7 +154,7 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
let result_bytes = result.abi_encode();
let result_size = result_bytes.len() as u64;
let result_ptr = result_bytes.as_ptr() as u64;
return_riscv(result_ptr, result_size);
eth_riscv_runtime::return_riscv(result_ptr, result_size);
}
}
};
Expand Down Expand Up @@ -225,24 +223,37 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
let interface_name = format_ident!("I{}", struct_name);
let interface = helpers::generate_interface(&public_methods, &interface_name);

// Generate initcode for deployments
let deployment_code = helpers::generate_deployment_code(struct_name, constructor);

// Generate the complete output with module structure
let output = quote! {
use eth_riscv_runtime::*;
use alloy_sol_types::SolValue;

// Deploy module
#[cfg(feature = "deploy")]
pub mod deploy {
use super::*;
#deployment_code
}

// Public interface module
#[cfg(not(feature = "deploy"))]
pub mod interface {
use super::*;
#interface
}

// Generate the call method implementation privately
// only when not in `interface-only` mode
#[cfg(not(feature = "interface-only"))]
#[cfg(not(any(feature = "deploy", feature = "interface-only")))]
mod implementation {
use super::*;
use alloy_sol_types::SolValue;
use eth_riscv_runtime::*;

#input

#emit_helper

impl Contract for #struct_name {
Expand Down Expand Up @@ -271,11 +282,16 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
}

// Always export the interface
// Export initcode when `deploy` mode
#[cfg(feature = "deploy")]
pub use deploy::*;

// Always export the interface when not deploying
#[cfg(not(feature = "deploy"))]
pub use interface::*;

// Only export contract impl when not in `interface-only` mode
#[cfg(not(feature = "interface-only"))]
// Only export contract impl when not in `interface-only` or `deploy` modes
#[cfg(not(any(feature = "deploy", feature = "interface-only")))]
pub use implementation::*;
};

Expand Down
4 changes: 3 additions & 1 deletion erc20/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[features]
default = []
deploy = []
interface-only = []

[dependencies]
Expand All @@ -20,7 +21,8 @@ path = "src/lib.rs"

[[bin]]
name = "deploy"
path = "src/deploy.rs"
path = "src/lib.rs"
required-features = ["deploy"]

[profile.release]
lto = true
Expand Down
25 changes: 0 additions & 25 deletions erc20/src/deploy.rs

This file was deleted.

7 changes: 6 additions & 1 deletion erc20x/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name = "erc20x"
version = "0.1.0"
edition = "2021"

[features]
default = []
deploy = []

[dependencies]
contract-derive = { path = "../contract-derive" }
eth-riscv-runtime = { path = "../eth-riscv-runtime" }
Expand All @@ -17,7 +21,8 @@ path = "src/lib.rs"

[[bin]]
name = "deploy"
path = "src/deploy.rs"
path = "src/lib.rs"
required-features = ["deploy"]

[profile.release]
lto = true
Expand Down
25 changes: 0 additions & 25 deletions erc20x/src/deploy.rs

This file was deleted.

5 changes: 2 additions & 3 deletions erc20x/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

use core::default::Default;

use alloy_core::primitives::{address, Address, U256};
use contract_derive::{contract, interface};
use alloy_core::primitives::{Address, U256};
use contract_derive::contract;

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

use erc20::IERC20;

Expand Down
7 changes: 6 additions & 1 deletion erc20x_standalone/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name = "erc20x_standalone"
version = "0.1.0"
edition = "2021"

[features]
default = []
deploy = []

[dependencies]
contract-derive = { path = "../contract-derive" }
eth-riscv-runtime = { path = "../eth-riscv-runtime" }
Expand All @@ -16,7 +20,8 @@ path = "src/lib.rs"

[[bin]]
name = "deploy"
path = "src/deploy.rs"
path = "src/lib.rs"
required-features = ["deploy"]

[profile.release]
lto = true
Expand Down
Loading
Loading