evm-foundry

Foundry development for EVM chains including Celo. Use when working with forge, cast, anvil, writing Solidity contracts, testing, deploying, or verifying contracts with Foundry.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "evm-foundry" with this command: npx skills add celo-org/agent-skills/celo-org-agent-skills-evm-foundry

Foundry Development for EVM Chains

This skill covers Foundry setup and development for EVM-compatible chains with emphasis on Celo.

When to Use

  • Setting up a new Foundry project
  • Writing and compiling Solidity smart contracts with forge
  • Testing contracts with forge test
  • Deploying contracts to Celo or other EVM chains
  • Verifying contracts on block explorers
  • Interacting with contracts using cast

Foundry Tools

ToolPurpose
forgeBuild, test, debug, deploy, and verify smart contracts
castInteract with contracts and retrieve chain data from CLI
anvilRun a local Ethereum development node with forking support
chiselFast Solidity REPL for interactive development

Installation

# Install Foundryup (the Foundry installer)
curl -L https://foundry.paradigm.xyz | bash

# Install Foundry tools
foundryup

For Windows, use Git BASH or WSL (Foundryup doesn't support PowerShell or Command Prompt).

Quick Start

Initialize Project

forge init my-project
cd my-project

Project Structure

my-project/
├── foundry.toml      # Configuration
├── src/              # Contract source files
│   └── Counter.sol
├── test/             # Test files
│   └── Counter.t.sol
├── script/           # Deployment scripts
│   └── Counter.s.sol
└── lib/              # Dependencies

Celo Network Information

NetworkChain IDRPC Endpoint
Celo Mainnet42220https://forno.celo.org
Celo Sepolia11142220https://forno.celo-sepolia.celo-testnet.org

Configuration

foundry.toml for Celo

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.28"
optimizer = true
optimizer_runs = 200

[rpc_endpoints]
celo = "https://forno.celo.org"
celo_sepolia = "https://forno.celo-sepolia.celo-testnet.org"
localhost = "http://127.0.0.1:8545"

[etherscan]
celo = { key = "${CELOSCAN_API_KEY}", chain = 42220, url = "https://api.celoscan.io/api" }
celo_sepolia = { key = "${CELOSCAN_API_KEY}", chain = 11142220, url = "https://api.etherscan.io/v2/api" }

Environment Variables (.env)

PRIVATE_KEY=your_private_key_here
CELOSCAN_API_KEY=your_celoscan_api_key_here

Load environment variables:

source .env

Writing Contracts

Basic Contract

// src/MyContract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract MyContract {
    string public name;
    address public owner;

    event NameChanged(string oldName, string newName);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    constructor(string memory _name) {
        name = _name;
        owner = msg.sender;
    }

    function setName(string memory _newName) external onlyOwner {
        string memory oldName = name;
        name = _newName;
        emit NameChanged(oldName, _newName);
    }
}

Using OpenZeppelin

forge install OpenZeppelin/openzeppelin-contracts

Add to remappings.txt:

@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
// src/MyToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }

    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);
    }
}

Compilation

# Compile all contracts
forge build

# Clean and rebuild
forge clean && forge build

# Check contract sizes
forge build --sizes

Testing

Test File Structure

// test/MyContract.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Test, console} from "forge-std/Test.sol";
import {MyContract} from "../src/MyContract.sol";

contract MyContractTest is Test {
    MyContract public myContract;
    address public owner;
    address public user;

    function setUp() public {
        owner = address(this);
        user = makeAddr("user");
        myContract = new MyContract("Initial Name");
    }

    function test_InitialName() public view {
        assertEq(myContract.name(), "Initial Name");
    }

    function test_Owner() public view {
        assertEq(myContract.owner(), owner);
    }

    function test_SetName() public {
        myContract.setName("New Name");
        assertEq(myContract.name(), "New Name");
    }

    function test_SetNameEmitsEvent() public {
        vm.expectEmit(true, true, true, true);
        emit MyContract.NameChanged("Initial Name", "New Name");
        myContract.setName("New Name");
    }

    function test_RevertWhen_NonOwnerSetsName() public {
        vm.prank(user);
        vm.expectRevert("Not owner");
        myContract.setName("Hacked");
    }

    function testFuzz_SetName(string memory newName) public {
        myContract.setName(newName);
        assertEq(myContract.name(), newName);
    }
}

Running Tests

# Run all tests
forge test

# Run with verbosity (show logs)
forge test -vvv

# Run specific test
forge test --match-test test_SetName

# Run specific contract
forge test --match-contract MyContractTest

# Run with gas reporting
forge test --gas-report

# Run with coverage
forge coverage

# Fork testing against Celo mainnet
forge test --fork-url https://forno.celo.org

Deployment

Deployment Script

// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Script, console} from "forge-std/Script.sol";
import {MyContract} from "../src/MyContract.sol";

contract DeployScript is Script {
    function setUp() public {}

    function run() public {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

        vm.startBroadcast(deployerPrivateKey);

        MyContract myContract = new MyContract("My Contract Name");
        console.log("Contract deployed to:", address(myContract));

        vm.stopBroadcast();
    }
}

Deploy Commands

# Deploy to local Anvil
forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --broadcast

# Deploy to Celo Sepolia
forge script script/Deploy.s.sol --rpc-url https://forno.celo-sepolia.celo-testnet.org --broadcast --private-key $PRIVATE_KEY

# Deploy to Celo Mainnet
forge script script/Deploy.s.sol --rpc-url https://forno.celo.org --broadcast --private-key $PRIVATE_KEY

# Deploy with verification
forge script script/Deploy.s.sol --rpc-url https://forno.celo-sepolia.celo-testnet.org --broadcast --verify --private-key $PRIVATE_KEY

Quick Deploy with forge create

# Deploy to Celo Sepolia
forge create src/MyContract.sol:MyContract \
  --rpc-url https://forno.celo-sepolia.celo-testnet.org \
  --private-key $PRIVATE_KEY \
  --constructor-args "My Contract Name"

# Deploy with verification
forge create src/MyContract.sol:MyContract \
  --rpc-url https://forno.celo-sepolia.celo-testnet.org \
  --private-key $PRIVATE_KEY \
  --constructor-args "My Contract Name" \
  --verify \
  --etherscan-api-key $CELOSCAN_API_KEY

Verification

Verify Existing Contract

# Verify on Celo Sepolia
forge verify-contract \
  --chain-id 11142220 \
  <CONTRACT_ADDRESS> \
  src/MyContract.sol:MyContract \
  --etherscan-api-key $CELOSCAN_API_KEY \
  --watch

# Verify on Celo Mainnet
forge verify-contract \
  --chain-id 42220 \
  <CONTRACT_ADDRESS> \
  src/MyContract.sol:MyContract \
  --etherscan-api-key $CELOSCAN_API_KEY \
  --watch

# With constructor arguments
forge verify-contract \
  --chain-id 11142220 \
  <CONTRACT_ADDRESS> \
  src/MyContract.sol:MyContract \
  --etherscan-api-key $CELOSCAN_API_KEY \
  --constructor-args $(cast abi-encode "constructor(string)" "My Contract Name") \
  --watch

Cast Commands

Reading Data

# Get balance
cast balance <ADDRESS> --rpc-url https://forno.celo.org

# Call view function
cast call <CONTRACT_ADDRESS> "name()(string)" --rpc-url https://forno.celo.org

# Get storage slot
cast storage <CONTRACT_ADDRESS> 0 --rpc-url https://forno.celo.org

# Get block number
cast block-number --rpc-url https://forno.celo.org

Writing Data

# Send transaction
cast send <CONTRACT_ADDRESS> "setName(string)" "New Name" \
  --rpc-url https://forno.celo.org \
  --private-key $PRIVATE_KEY

# Transfer CELO
cast send <TO_ADDRESS> --value 1ether \
  --rpc-url https://forno.celo.org \
  --private-key $PRIVATE_KEY

Utility Commands

# Encode function call
cast calldata "setName(string)" "New Name"

# Decode function call
cast 4byte-decode <CALLDATA>

# Convert units
cast to-wei 1 ether
cast from-wei 1000000000000000000

# Get ABI-encoded constructor args
cast abi-encode "constructor(string)" "My Contract Name"

Anvil (Local Node)

# Start local node
anvil

# Start with fork of Celo mainnet
anvil --fork-url https://forno.celo.org

# Start with specific block
anvil --fork-url https://forno.celo.org --fork-block-number 12345678

Block Explorers

Additional Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

evm-hardhat

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

web3-polymarket

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

define-architecture

No summary provided by upstream source.

Repository SourceNeeds Review