Skip to content

Latest commit

 

History

336 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Enso Shortcuts Client Contracts

Client contracts for running Enso Shortcuts.

Install

Requires Foundry.

$ forge install
$ pnpm install
$ pnpm foundry:update
$ forge build

Tests

$ forge test

Unit & Integration Testing - Write BTTs with Bulloak

Bulloak is a Solidity test generator based on the Branching Tree Technique (BTT). See the Bulloak repo for full documentation, examples, and advanced usage.

Requirements

How to Write and Scaffold BTT Trees

  1. Create a Tree File

    Create a <ContractName>.tree file (e.g., ERC4337CloneFactory.tree). Each tree describes the branching logic of a function or contract using ASCII art.

    Example:

    ERC4337CloneFactory_DelegateDeploy
    # when EnsoReceiver does not exist
    ## it should emit CloneDeployed event
    ## it should deploy clone
    ## it should initialize clone
    # when EnsoReceiver already exists
    ## it should revert
    
  2. Convert to Tree Structure

    Use ASCII tree tools or an LLM to convert your outline to a tree with ├, └, and │ characters. You can group multiple trees in a single file, but only one can be scaffolded at a time (comment out others).

    Example:

    // ERC4337CloneFactory_Deploy
    // ├── when EnsoReceiver does not exist
    // │   ├── it should emit CloneDeployed event
    // │   ├── it should deploy clone
    // │   └── it should initialize clone
    // └── when EnsoReceiver already exists
    //     └── it should revert
    
    ERC4337CloneFactory_DelegateDeploy
    ├── when EnsoReceiver does not exist
    │   ├── it should emit CloneDeployed event
    │   ├── it should deploy clone
    │   └── it should initialize clone
    └── when EnsoReceiver already exists
        └── it should revert
    
  3. Scaffold the Solidity Test

    Run:

    bulloak scaffold path/to/YourContract.tree

    This generates a .t.sol test file with the contract and test stubs.

  4. Review and Finalize

    • Set the correct SPDX license and pragma.
    • Add necessary imports.
    • Update the contract name and inheritance as needed.
    • Naming pattern suggestion: <ContractName>_<Method>[_When<Condition>][_As<Role>][_Unit|Int|Fork][_Concrete|Fuzz]_Test

Mutation Testing

Mutation testing helps you measure the effectiveness of your test suite by introducing small changes ("mutants") to your code and checking if your tests catch them.

Requirements

  • Certora Gambit (for mutation generation)
  • Node.js (for running the mutation test script)
  • A working gambit.config.json (see below)

Setup

  1. Create or update your Gambit config

    Make sure you have a gambit.config.json in your project root. See Certora Gambit docs for config options.

  2. Generate Mutations

    gambit mutate --json gambit.config.json

    This will create mutated versions of your contracts in gambit_out/mutants/.

  3. Run Mutation Tests

    You can run mutation tests using the provided script:

    node ./mutationTest.mjs --matchContract 'EnsoReceiver_.*_Unit_Concrete_Test'
    • Use the --matchContract flag with a regex to select which test contracts to run against each mutant.
    • You can also use --matchMutant to filter which mutants to test (by contract name or pattern).

    Example:

    node ./mutationTest.mjs --matchContract 'EnsoReceiver_.*_Unit_Concrete_Test' --matchMutant EnsoReceiver

    This will run all test contracts matching the pattern against all mutants of EnsoReceiver.

    Tip: You can use more complex regexes to match multiple contracts, e.g. --matchContract 'EnsoReceiver_.*_(Unit|Fork)_Concrete_Test'

  4. (Optional) Automate with a Script

    For more complex or repeated runs, you can create a JS script in test/scripts/, e.g. test/scripts/runEnsoCheckoutMutationTests.mjs.

Script Options and Advanced Usage

The mutationTest.mjs script is based on ibourn/gambit-mutation-testing. For a full list of available options and advanced usage, see the original script documentation.

Some useful options include:

  • --matchContract "<pattern>" – Only run tests for contracts matching the regex.
  • --noMatchContract "<pattern>" – Exclude contracts matching the regex.
  • --matchTest "<pattern>" – Only run test functions matching the regex.
  • --noMatchTest "<pattern>" – Exclude test functions matching the regex.
  • --matchMutant "<pattern>" – Only test mutants for source files matching the pattern.
  • --verbose true – Show detailed output in the console.
  • --debug true – Save detailed logs to the testLogs folder.

How It Works

  • The script will:
    • Backup the original contract file (in src/)
    • Replace it with each mutant, one at a time
    • Run your Foundry tests with the specified contract filter
    • Restore the original file after each mutant
    • Log results and mutation score

Best Practices & Troubleshooting

  • Do not stage or commit mutated files during or after an interrupted test run. Always ensure the original contract files are restored before committing.
  • Always use a regex for --matchContract if you want to match multiple contracts. Example: --matchContract 'EnsoReceiver_.*_Unit_Concrete_Test'
  • Do not stage or commit files in gambit_out/ or tempBackup/—these are generated and temporary.
  • If you see errors about missing files (e.g., ENOENT: no such file or directory, lstat 'delegate/EnsoReceiver.sol'), make sure your source files are in src/ and the script is up to date (see #Path Issues below).
  • Check your test coverage: Surviving mutants indicate untested or weakly tested code paths.
  • If you reorganize or rename your contracts, ensure the mutation config and script is updated accordingly.

For more details and advanced script options, see the gambit-mutation-testing script documentation.

Deployment

Signing key (Foundry keystore)

This repo never reads a raw private key from .env. Deployer scripts broadcast keylessly (vm.startBroadcast() with no argument) and the signer comes from an encrypted Foundry keystore, picked by name with --account.

cast wallet import enso-deployer --interactive   # create one (hidden prompt)
cast wallet list                                 # which ones exist
cast wallet address --account enso-deployer      # its public address

Heads up: anything that resolves a keystore signer unlocks it, so --account asks for the password even when nothing is broadcast. No tty, no run.

Then copy .env.example to .env and fill in RPC URLs and verifier keys. Owners are not read from .env; they come from ChainOwner.ownerFor(chainId). The standalone solver deployers read an OWNER variable instead.

Deploy

Preferred — the wrapper picks the right per-network flags and uses the enso-deployer keystore (override with DEPLOYER_ACCOUNT):

$ ./.bash/deploy.sh <Deployer.s.sol> <network> broadcast <verifier>

<verifier> is one of etherscan (Etherscan v2 chains, key from ETHEREUM_BLOCKSCAN_KEY), blockscout (per-network URL in the wrapper), routescan, tempo, or sourcify (keyless, any chain Sourcify lists; the only route for Arc mainnet, whose Blockscout is behind Cloudflare Access). Leave it out to deploy without verifying.

Or invoke forge script directly:

$ forge script Deployer --account enso-deployer --broadcast --rpc-url <network>

Dry run

Drop the broadcast argument and the script only simulates. Nothing is sent.

$ DEPLOYER_ADDRESS=<public address> ./.bash/deploy.sh <Deployer.s.sol> <network>

DEPLOYER_ADDRESS makes the wrapper simulate with --sender, so there is no password prompt and no tty needed. CI can run it.

Worth knowing:

  • Deploys go through the CREATE2 factory, so the sender does not change the resulting addresses. A dry run predicts exactly what a broadcast produces.
  • Check those addresses against the ones your integrations already expect. A mismatch means the bytecode moved.
  • It cannot prove the sender may transact on a permissioned chain. Gas estimation succeeds for any address.

Verification

Example of how manually verifying a contract with constructor args after deployment:

forge verify-contract \
--watch \
--chain polygon \
0xDb5b96dC4CE3E0E44d30279583F926363eFaE29f \
src/helpers/FeeSplitter.sol:FeeSplitter \
--verifier etherscan \
--etherscan-api-key <string:etherscan-api-key> \
--constructor-args $(cast abi-encode "constructor(address,address[],uint16[])" "0x6AA68C46eD86161eB318b1396F7b79E386e88676" "[0xBfC330020E3267Cea008718f1712f1dA7F0d32A9,0x6AA68C46eD86161eB318b1396F7b79E386e88676]" "[1,1]")

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages