DeployLite is a forge script to ease contract deployments on mutliple EVM networks
DeployLite registers deployed addresses in a single json file, to be used by your frontend UI
- Idempotent Deployments: DeployLite ensures that a contract is deployed only if it is not already deployed onchain
- Immutable Variable Handling: DeployLite identifies contracts by their on-chain bytecode, even when immutable variables are present
- Metadata Management: DeployLite avoids redeploying contracts if only metadata (such as formatting or comments) has changed, without any code modifications
- Multi-Contract Support: DeployLite supports the deployment of multiple contracts in a single operation
You can use a unique DeployAll.s.sol script to deploy all your contracts, ensuring only modified contracts to be redeployed.
install forge-deploy-lite into your foundry project with:
forge install zapaz/forge-deploy-liteSet specific fs_permissions settings in your foundry.toml configuration, like this:
# to write to addresses.json
fs_permissions = [
{ access = "read-write", path = "./addresses.json"},
{ access = "read-write", path = "./out"}]Set environment variables, to be used in foundry.toml
- INFURA_API_KEY (or ALCHEMY_API_KEY or other...) for your rpc url
- ETHERSCAN_API_KEY_ETHEREUM (or other...) to verify your contract
to be used by deploy scripts:
- CHAIN is the name of the chain you are deploying to
- SENDER the address of this sender of the deployment transactions
- ACCOUNT is the name of one keystore account holding securely the private key of SENDER
Here is an example .env file for testing with anvil:
export INFURA_API_KEY="xxxxxxxxxxxxx"
export ETHERSCAN_API_KEY_ETHEREUM="xxxxxxxxxxxxx"
export CHAIN="anvil"
export SENDER="0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"
export ACCOUNT="anvil5"To keep it simple, only use deployLite for your deployment script.
The Counter deploy script is as follow, to be writen in a file DeployCounter.s.sol :
script/DeployCounter.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {DeployLite} from "lib/forge-deploy-lite/DeployLite.s.sol";
import {Counter} from "src/examples/Counter.sol";
contract DeployCounter is DeployLite {
function deployCounter() public returns (address) {
return deployLite("Counter", abi.encode(42));
}
function run() public virtual {
deployCounter();
}
}For your contract, just replace everywhere Counter by the name of your contract.
DeployLite checks onchain if bytecode is already deployed, and then stops if this is the case, or deploys contract and writes deployed address in adresses.json
(you have to pass the forge script 2 times to validate that a deployment has succeeded, if not some "Contract_last" addresses will appears in your addresses.json file)
deployLite comes in 2 flavors to optionnaly handle constructor argument
- one param when you have no constructor arguments
function deployLite(string memory name) external returns (address addr);- two params when you have constructor arguments
function deployLite(string memory name, bytes memory data) external returns (address addr);Two more advanced deploy functions are avaible, to tune the deployment process: deployStateand deployfunctions
deployStatereturn the actual state of deployment (must be outsidebroadcasting)- possible values are :
- Null: deployment not checked yet
- None: no deployment found
- New: new deployment has just been made
- Already: existing identic deployment exists, no need to redeploy
- Older: a older different deployment exists, redeploy needed
- possible values are :
function deployState(string memory name) public returns (DeployState state)deployactually deploys data bytecode for named contract (must be insidebroadcasting)
function deploy(string memory name, bytes memory data) external returns (address addr);Here is an example for Complex.sol contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {DeployLite} from "../../src/DeployLite.s.sol";
import {Complex} from "../../src/examples/Complex.sol";
contract DeployComplex is DeployLite {
function deployComplex() public returns (address) {
bytes memory args = abi.encode(1_000, 1);
DeployState state = deployState("Complex", args);
if (state == DeployState.None || state == DeployState.Older) {
vm.broadcast();
deploy("Complex", args);
}
return readAddress("Complex");
}
function run() public virtual {
deployComplex();
}
}
To deploy and verify your contract, launch the following command:
forge script script/deploy/DeployCounter.s.sol --fork-url $CHAIN --sender $SENDER --account $ACCOUNT --broadcast --verifyor via pnpm task
pnpm deploy:broadcast --verifyTo validate deployment of your contract, launch the following command:
forge script script/deploy/DeployCounter.s.sol --fork-url $CHAIN --sender $SENDERor via pnpm task
pnpm deploy:validateTo deploy AND validate your contract, launch the following command:
pnpm deploy:deployIt will run deploy:broadcast then deploy:validate
You can deploy multiple contracts at the same time, in the same block
Just write a Deploy<Contract>.s.sol for each contract and a DeployAll.s.sol script with run inluding multiple deploy("CONTRA${CT_NAME}")calls like this :
contract DeployAll is Contract, Contract2{
function run() public override(Contract, Contract2)
{
deployContract();
deployContract2();
}
}It is recommended to deploy contracts one by one the first time, then you can use DeployAll (with same compiler options), as it will only redeploy modified contracts.
here is a example of the resulting file:
addresses.json
{
"31337": {
"chainName": "local",
"Counter": "0x90193C961A926261B756D1E5bb255e67ff9498A1"
},
"11155111": {
"chainName": "sepolia",
"Counter": "0x34A1D3fff3958843C43aD80F30b94c510645C316"
}
}Note that you can get some fields like:
"Counter_last": "0x34A1D3fff3958843C43aD80F30b94c510645C316"`In this case, run the validate script to get a validation (pnpm deploy:validate) of this deployement.
Sometimes deployments fails and this ..._last address is not deployed, in this case just relaunch deploy script (pnpm deploy:broadcast). No worry to delete this field.
- document howto to also include deploy in tests
- manage zkSync Era specific deployment
- ...
Any suggestions welcome! (just open an issue or a PR)
- inspired by @wighawag great hardhat-deploy
- a forge version by @wighawag also available forge-deploy with full deploy functionnalies