Skip to content

Conversation

@0xrusowsky
Copy link
Contributor

@0xrusowsky 0xrusowsky commented Mar 14, 2025

TODO: i need to update the CI as right now this fails to compile


this PR adds support for x-contract creation via the CREATE opcode, but it also comes with extra benefits
Note that it is just a PoC, and i'm obviously open to try different alternatives if u have suggestions! If u validate the design though, i'll go ahead an implement Uniswap V2 as another example

low-level implementation

It is quite similar to the CALL syscall, but it uses a CREATE interpreter action instead.

However, in this case the returned data (address of the created contract) is not available in the interpreter's return_data_buffer. Because of that, i had to create a new syscall (ReturnCreateAddress) to specifically return the cached address. Note that i made sure to use the ID of an opcode that doesn't require a syscall 0x01 ADD as it is supported by the RISCV emulator

dev-ex

the challenge of this PR was to allow users to deploy a new contract with something as simple as
ERC20::deploy(owner) which ideally returns an interface instance, so that users can already interact with it.

to do, so the "deployer" contract needs to embed the bytecode of the "deployed" contract, which requires a specific compilation order + the ability to access such bytecode.

i've tried several approaches, and this was the one that i like the most in terms of dev-ex (and one of the few that i managed to get working).

my proposed solution: the r55-compile binary, which:

  1. scans over the directories in examples/
  2. identifies which libs are r55 contracts based on their cargo.toml files
  3. identifies their contract dependencies
     erc20 = { path = "../erc20", features = ["interface-only"] }
  4. auto-generates a deployable.rs file in the "deployer" lib (in the example erc20x/deployable.rs) which creates a deployable struct for each contract dependency
    //! Auto-generated based on Cargo.toml dependencies
    //! This file provides Deployable implementations for contract dependencies
    use alloy_core::primitives::{Address, Bytes};
    use eth_riscv_runtime::{create::Deployable, InitInterface, ReadOnly};
    use core::include_bytes;
    
    use erc20::IERC20;
    
    const ERC20_BYTECODE: &'static [u8] = include_bytes!("../../../r55-output-bytecode/erc20.bin");
    
    pub struct ERC20;
    
    impl Deployable for ERC20 {
        type Interface = IERC20<ReadOnly>;
    
        fn __runtime() -> &'static [u8] {
            ERC20_BYTECODE
        }
    }
  5. orchestrates the compilation of all contracts in the correct order

additional benefits of this approach: it also generates a module r55/generated/mod.rs so that all tests cases can directly access the compiled bytecode and deploy contracts


since i know it is not ideal for users to have to run a script, i tried to make it as easy as possible (i created an alias), and the workflow would be:

  1. setup your cargo.toml with the corresponding contracts deps
  2. run cargo compile -> autogenerates the deployable impl
  3. develop you contract (importing the deployable deps)
    use erc20::{ERC20Error, IERC20};
    
    mod deployable;
    use deployable::ERC20;
    
    #[derive(Default, )]
    pub struct ERC20x;
    
    #[contract]
    impl ERC20x {
        // Deploys a new ERC20 token instance
        pub fn x_deploy(&mut self, owner: Address) -> (Address, Address) {
            let token = ERC20::deploy(owner).with_ctx(self);            // IERC20<ReadWrite>
            let owner = token.owner().expect("Unable to get owner");
    
            (token.address(), owner)
        }
  4. run cargo compile again so that ur contract is compiled
  5. run cargo test-r55

question: how does r55 integration when developing smart contracts looks like? how do we pack it? note that my proposal is limited to the examples directory right now, although obviously we could make it read the path form an env var or something.

@leonardoalt
Copy link
Collaborator

merged the other PR, not sure if this one needs a merge/rebase

@0xrusowsky
Copy link
Contributor Author

i'd have to update github actions for this one, as it needs to run the compilation binary first.

how does the approach look like? are u on board? or would u do it differently? @leonardoalt

@leonardoalt
Copy link
Collaborator

So the problem we're solving here is that:

  • I'm developing MyContract
  • MyContract deploys OtherContract
  • Therefore when compiling MyContract, OtherContract needs to be compiled already

I like the approach with the Deployable trait! I think the usage should be a bit cleaner/more automated.
If MyContract has OtherContract as a dependency, wouldn't it already be compiled when we compile MyContract?
We'd only need to find the artifacts which could be placed in target/r55 eg. We could have build.rs files to orchestrate that. (mostly spitballing here, not aware of the implications of implementation details)

@0xrusowsky
Copy link
Contributor Author

0xrusowsky commented Mar 18, 2025

exactly, when we have a contract dependency, we need that contract to be compiled before time so that we can access its bytecode.

despite cargo would automatically compile it, the problem is that it must be compiled twice:

  • 1st: without flags (runtime)
  • 2nd: with the deploy flag

so without the custom script that i wrote, compilation would fail all the time.

the problem that i was facing with custom build scripts for each contract is that those must be compiled with nightly and the riscv target, whereas r55 is compiled with stable 1.84.

i was having issues running "sub" cargo build commands for the individual contracts when triggered from the r55 build (stable 1.84).

hence why i ended up using a custom script that orchestrates everything.

@gakonst
Copy link
Collaborator

gakonst commented Mar 27, 2025

I think worth merging this in, and then we can move on to trying out examples.

@0xrusowsky
Copy link
Contributor Author

I think worth merging this in, and then we can move on to trying out examples.

cool, i'll update the CI to run the compilation script + make sure all tests pass

@leonardoalt
Copy link
Collaborator

I think this needs a rebase/merge now?

leonardoalt
leonardoalt previously approved these changes Apr 15, 2025
@0xrusowsky 0xrusowsky dismissed leonardoalt’s stale review April 15, 2025 09:34

The merge-base changed after approval.

leonardoalt
leonardoalt previously approved these changes Apr 15, 2025
leonardoalt
leonardoalt previously approved these changes Apr 15, 2025
@0xrusowsky 0xrusowsky requested a review from leonardoalt April 15, 2025 09:39
@0xrusowsky 0xrusowsky dismissed leonardoalt’s stale review April 15, 2025 09:45

The merge-base changed after approval.

leonardoalt
leonardoalt previously approved these changes Apr 15, 2025
@0xrusowsky 0xrusowsky dismissed leonardoalt’s stale review April 15, 2025 09:52

The merge-base changed after approval.

leonardoalt
leonardoalt previously approved these changes Apr 15, 2025
@leonardoalt leonardoalt reopened this Apr 15, 2025
@leonardoalt leonardoalt dismissed their stale review April 15, 2025 09:57

The merge-base changed after approval.

@leonardoalt leonardoalt merged commit 31f7868 into r55-eth:main Apr 15, 2025
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants