Build with Cannon

Start by installing/upgrading Cannon:

npm i -g @usecannon/cli

Run the setup command to prepare your development environment:

cannon setup

Cannon relies on IPFS for file storage. You can run an IPFS node locally or rely on a remote pinning service (like Pinata or run your own IPFS cluster). We recommend the former for local development and the latter when publishing packages. The setup command will walk you through this step-by-step.

Select your framework

Create a Cannonfile

Create a new Foundry project with forge init sample-project. This will generate the following contract:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}

Create a cannonfile.toml in the root directory of the project with the following contents. If you plan to publish this package, you should customize the name. This will deploy the contract and set the number to 420:

name = "sample-foundry-project"
version = "0.1"
description = "Sample Foundry Project"

[setting.number]
defaultValue = "420"
description="Initialization value for the number"

[contract.counter]
artifact = "Counter"

[invoke.set_number]
target = ["counter"]
func = "setNumber"
args = ["<%= settings.number %>"]

Now build the cannonfile for local development and testing:

cannon build

This created a local deployment of your nascent protocol. You can now run this package locally using the command-line tool. (Here, we add the --registry-priority local option to ensure we’re using the version of this package that you just built, regardless of what others have published.)

cannon sample-foundry-project --registry-priority local

Deploy Your Protocol

Deploying is just building on a remote network!

cannon build --network REPLACE_WITH_RPC_ENDPOINT --private-key REPLACE_WITH_KEY_THAT_HAS_GAS_TOKENS

Verify your project's contracts on Etherscan:

cannon verify sample-foundry-project --api-key REPLACE_WITH_ETHERSCAN_API_KEY --chain-id REPLACE_WITH_CHAIN_ID

Finally, publish your package on the Cannon registry:

cannon publish sample-foundry-project --private-key REPLACE_WITH_KEY_THAT_HAS_ETH_ON_MAINNET

Import and Provision Packages

You can use packages in your Cannonfiles with the import and provision actions.

import  packages to reference the addresses in their deployment data. Find which networks each package has deployment data for on the registry explorer.

For example, the Synthetix Sandbox contains a Cannonfile that deploys the sample integration contract connected to the official deployment addresses. The relevant code looks like this:

[import.synthetix_omnibus]
source ="synthetix-omnibus:latest"

[contract.sample_integration]
artifact = "SampleIntegration"
args = [
    "<%= imports.synthetix_omnibus.contracts.system.CoreProxy %>",
    "<%= imports.synthetix_omnibus.contracts.system.USDProxy %>"
]

provision packages to deploy new instances of their protocol's contracts.

For example, the Synthetix Sandbox contains a Cannonfile that provisions a new instance of Synthetix and sets up a custom development environment. This is a simplified version of the relevant code:

[provision.synthetix]
source = "synthetix:latest"
owner = "<%= settings.owner %>"

[invoke.createPool]
target = ["synthetix.CoreProxy"]
from = "<%= settings.user %>"
func = "createPool"
args = [
    "1",
    "<%= settings.owner %>"
]

Test Your Protocol

Install Cannon for Foundry:

forge install usecannon/cannon-std

Grant your Foundry project permission to read from the filesystem. Add the following line to your foundry.toml  file:

fs_permissions = [{ access = "read", path = "./"}]

Include the Cannon.sol library in your tests. Here's an example:

pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "forge-std/console.sol";

import "cannon-std/Cannon.sol";

import "../src/SampleIntegration.sol";

contract SampleIntegrationTest is Test {
    using Cannon for Vm;

    SampleIntegration sampleIntegration;

    function setUp() public {
        sampleIntegration = SampleIntegration(vm.getAddress("SampleIntegration"));
    }

    function testFailSetThresholdRequiresOwner() public {
        vm.expectRevert();
        sampleIntegration.setThreshold(3);
    }
}

Use the test command to run them. (Note that the --chain-id option can be used to run tests against a forked network.)

npx cannon test

Supported byOptimismandSafe