Skip to content

frederikrothenberger/chain-fusion-donations

Repository files navigation

Chain Fusion Starter

hero3

Join the discussion

Join Discussion

Watch workshops

DappCon24

DappCon24

Get started:

No matter what setup you pick from below, run ./deploys.sh from the project root to deploy the project. To understand the steps involved in deploying the project locally, examine the comments in deploy.sh. This script will

  • start anvil
  • start dfx
  • deploy the EVM contract
  • generate a number of jobs
  • deploy the chain_fusion canister

If you want to check that the chain_fusion_backend really processed the events, you can either look at the logs output by running ./deploy.sh – keep an eye open for the Successfully ran job message – or you can call the EVM contract to get the results of the jobs. To do this, run cast call --rpc-url=127.0.0.1:8545 0x5fbdb2315678afecb367f032d93f642f64180aa3 "getResult(uint)(string)" <job_id> where <job_id> is the id of the job you want to get the result for. This should always return "6765" for processed jobs, which is the 20th fibonacci number, and "" for unprocessed jobs.

If you want to create more jobs, simply run cast send --rpc-url=127.0.0.1:8545 0x5fbdb2315678afecb367f032d93f642f64180aa3 "newJob()" --private-key=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --value 0.01ether.

You can learn more about how to use cast here.

In the cloud:

Open in GitHub Codespaces

This is the setup we recommend for the codespace

Screenshot 2024-04-30 at 15 32 36

For the best performance, connect to the codespace from your local VS Code installation. To do this, click on the three dots next to the codespace name and then "Open in Visual Studio Code"

Screenshot 2024-04-30 at 15 40 01

Locally:

Make sure you have you have Docker and VS Code installed and running, then click the button below

Open locally in Dev Containers

Or do the manual setup:

Make sure that Node.js >= 21, foundry, caddy and dfx >= 0.18 are installed on your system.

Run the following commands in a new, empty project directory:

git clone https://github.com/letmejustputthishere/chain-fusion-starter.git # Download this starter project
cd chain-fusion-starter # Navigate to the project directory

Overview

This project demonstrates how to use the Internet Computer as a coprocessor for EVM smart contracts. The coprocessor listens to events emitted by an EVM smart contract, processes them, and optionally sends the results back. Note that we say EVM smart contracts, as you can not only interact with the Ethereum network, but other networks that are using the Ethereum Virtual Machine (EVM), such as Polygon and Avalanche.

This is an early project and should be considered as a proof of concept. It is not production-ready and should not be used in production environments. There are quite some TODOs in the code which will be addressed over time. If you have any questions or suggestions, feel free to open an issue, start a discussion or reach out to me on the DFINITY Developer Forum or X.

What is a coprocessor?

The concept of coprocessors originated in computer architecture as a technique to enhance performance. Traditional computers rely on a single central processing unit (CPU) to handle all computations. However, the CPU became overloaded as workloads grew more complex.

Coprocessors were introduced to offload specific tasks from the CPU to specialized hardware. We see the same happening in the EVM ecosystem. EVM smart contracts, and Ethereum in particular, are a very constrained computing environment. Coprocessors and stateful Layer 2 solutions enable to extend the capabilities of the EVM by offloading specific tasks to more powerful environments.

You can read more about coprocessors in the context of Ethereum in the article "A Brief Into to Coprocessors". The first paragraph of this section was directly taken from this article.

Why use ICP as a coprocessor for Ethereum?

Canister smart contracts on ICP can securely read from EVM smart contracts (using HTTPS Outcalls or the EVM RPC canister) and write to them (using Chain-key Signatures, i.e. Threshold ECDSA). Hence, there are no additional parties needed to relay messages between the two networks, and no additional work needs to be done on the EVM side to verify the results of the computation as the EVM smart contract just needs to check for the proper sender.

Furthermore, canister smart contracts have many capabilities and properties that can be leveraged to extend the reach of smart contracts:

For more context on how ICP can extend Ethereum, check out this presentation from EthereumZuri 2024.

Architecture

image

EVM Smart contract

The contract src/foundry/Coprocessor.sol emits an event NewJob when the newJob function is called. The newJob function transfers the ETH sent with the call to newJob to the account controlled by the chain_fusion_backend canister and emits the event. We send ETH to the chain_fusion_backend canister to pay for the processing of the job result and the transaction fees for sending the result back to the EVM smart contract.

    function newJob() public payable {
        // Require at least 0.01 ETH to be sent with the call
        require(msg.value >= 0.01 ether, "Minimum 0.01 ETH not met");

        // Forward the ETH received to the coprocessor address
        // To pay for the submission of the job result back to the EVM
        // contract.
        (bool success, ) = coprocessor.call{value: msg.value}("");
        require(success, "Failed to send Ether");

        // Emit the new job event
        emit NewJob(job_id);

        // Increment job counter
        job_id++;
    }

The contract also has a callback function that can only be called by the chain_fusion_backend canister. This function is called by the chain_fusion_backend canister to send the results of the processing back to the contract.

    function callback(string calldata _result, uint256 _job_id) public {
        require(
            msg.sender == coprocessor,
            "Only the coprocessor can call this function"
        );
        jobs[_job_id] = _result;
    }

The source code of the contract can be found in src/foundry/Coprocessor.sol.

For local deployment of the EVM smart contract and submitting transactions we use foundry. You can take a look at the steps needed to deploy the contract locally in the deploy.sh script which runs script/Coprocessor.s.sol. Make sure to check both files to understand the deployment process.

chain fusion canister

The chain_fusion_backend canister listens to events emitted by the Ethereum smart contract by periodically calling the eth_getLogs RPC method via the EVM RPC canister. When an event is received, the canister can do all kinds of synchronous and asynchronous processing. When the processing is done, the canister sends the results back by creating a transaction calling the callback function of the contract. The transaction is signed using threshold signatures and sent to the Ethereum network via the EVM RPC canister. You can learn more about how the EVM RPC canister works and how to integrate with it here.

The logic for the job that is run on each event can be found in src/chain_fusion_backend/job.rs. The job is a simple example that just calculates fibonacci numbers. You can replace this job with any other job you want to run on each event. The reason we picked this job is that it is computationally expensive and can be used to demonstrate the capabilities of the ICP as a coprocessor. Calculating the 20th fibonacci number wouldn't be possible on the EVM due to gas limits, but it is possible on the ICP.

pub async fn job(event_source: LogSource, event: LogEntry) {
    mutate_state(|s| s.record_processed_log(event_source.clone()));
    // because we deploy the canister with topics only matching
    // NewJob events we can safely assume that the event is a NewJob.
    let new_job_event = NewJobEvent::from(event);
    // this calculation would likely exceed an ethereum blocks gas limit
    // but can easily be calculated on the IC
    let result = fibonacci(20);
    // we write the result back to the evm smart contract, creating a signature
    // on the transaction with chain key ecdsa and sending it to the evm via the
    // evm rpc canister
    submit_result(result.to_string(), new_job_event.job_id).await;
    println!("Successfully ran job #{:?}", &new_job_event.job_id);
}

While not in use for this specific example, there is a src/chain_fusion_backend/src/storage.rs module that can be used to write data to the canisters stable memory. This can be useful for storing big amounts of data (up to 400 GiB) in a canister. In our example, it can be used to store assets that are then served from the canister via HTTP.

Develop

The chain_fusion canister has been structured in a way that all the coprocessing logic lives in src/chain_fusion_backend/src/job.rs and developers don't need to recreate or touch the code responsible for fetching new events, creating signatures or sending transactions. They can solely focus on writing jobs to run upon receiving a new event from an EVM smart contract.

You can find the full flow in the following sequence diagram with Ethereum as an example EVM chain (note that this flow can be applied to any EVM chain):

image

chain_fusion starter use-cases

Here you can find a number of examples leveraging the chain_fusion starter logic:

Build your own use-case on top of the chain_fusion starter and share it with the community! Some ideas you could explore:

  • A referral canister that distributes rewards to users based on their interactions with an EVM smart contract
  • A ckNFT canister that mints an NFT on the ICP when an EVM helper smart contract emits an ReceivedNft, similar to the EthDepositHelper contract the ckETH minter uses. This could enable users to trade NFTs on the ICP without having to pay gas fees on Ethereum.
  • Price oracles for DeFi applications via exchange rate canister
  • Prediction market resolution
  • Soulbound NFT metadata and assets stored in a canister

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published