555 lines
14 KiB
Markdown
555 lines
14 KiB
Markdown
# Foundry Deployment Guide
|
|
|
|
Complete guide to deploying and verifying smart contracts with Foundry.
|
|
|
|
## forge create (Single Contract)
|
|
|
|
Quick deployment for single contracts.
|
|
|
|
### Basic Usage
|
|
|
|
```bash
|
|
# Deploy with constructor args
|
|
forge create src/Token.sol:Token \
|
|
--rpc-url sepolia \
|
|
--private-key $PRIVATE_KEY \
|
|
--constructor-args "MyToken" "MTK" 18
|
|
|
|
# Deploy and verify
|
|
forge create src/Token.sol:Token \
|
|
--rpc-url sepolia \
|
|
--private-key $PRIVATE_KEY \
|
|
--broadcast \
|
|
--verify \
|
|
--etherscan-api-key $ETHERSCAN_API_KEY \
|
|
--constructor-args "MyToken" "MTK" 18
|
|
|
|
# Deploy with value (payable constructor)
|
|
forge create src/Vault.sol:Vault \
|
|
--rpc-url sepolia \
|
|
--private-key $PRIVATE_KEY \
|
|
--value 1ether
|
|
```
|
|
|
|
### Using Ledger
|
|
|
|
```bash
|
|
forge create src/Token.sol:Token \
|
|
--rpc-url mainnet \
|
|
--ledger \
|
|
--mnemonic-derivation-path "m/44'/60'/0'/0/0"
|
|
```
|
|
|
|
## Solidity Scripts (Recommended)
|
|
|
|
More powerful and flexible deployment method.
|
|
|
|
### Basic Deploy Script
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {Token} from "../src/Token.sol";
|
|
|
|
contract DeployToken is Script {
|
|
function run() external {
|
|
// Load private key from environment
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
address deployer = vm.addr(deployerKey);
|
|
|
|
console.log("Deploying from:", deployer);
|
|
console.log("Balance:", deployer.balance);
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
Token token = new Token("MyToken", "MTK", 18);
|
|
console.log("Token deployed to:", address(token));
|
|
|
|
// Initial setup
|
|
token.mint(deployer, 1_000_000e18);
|
|
|
|
vm.stopBroadcast();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Running Scripts
|
|
|
|
```bash
|
|
# Dry run (simulation only)
|
|
forge script script/Deploy.s.sol:DeployToken --rpc-url sepolia
|
|
|
|
# Broadcast transactions
|
|
forge script script/Deploy.s.sol:DeployToken \
|
|
--rpc-url sepolia \
|
|
--broadcast
|
|
|
|
# Broadcast and verify
|
|
forge script script/Deploy.s.sol:DeployToken \
|
|
--rpc-url sepolia \
|
|
--broadcast \
|
|
--verify
|
|
|
|
# Resume failed deployment
|
|
forge script script/Deploy.s.sol:DeployToken \
|
|
--rpc-url sepolia \
|
|
--broadcast \
|
|
--resume
|
|
```
|
|
|
|
### Script Execution Phases
|
|
|
|
1. **Local Simulation**: Run script, collect `vm.broadcast()` transactions
|
|
2. **On-chain Simulation**: (with `--rpc-url`) Simulate against chain state
|
|
3. **Broadcasting**: (with `--broadcast`) Send transactions to network
|
|
4. **Verification**: (with `--verify`) Verify contracts on Etherscan
|
|
|
|
### Complex Deployment
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {Token} from "../src/Token.sol";
|
|
import {Staking} from "../src/Staking.sol";
|
|
import {Governance} from "../src/Governance.sol";
|
|
|
|
contract DeployProtocol is Script {
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
address admin = vm.envAddress("ADMIN_ADDRESS");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
// Deploy token
|
|
Token token = new Token("Protocol Token", "PTK", 18);
|
|
console.log("Token:", address(token));
|
|
|
|
// Deploy staking with token reference
|
|
Staking staking = new Staking(address(token));
|
|
console.log("Staking:", address(staking));
|
|
|
|
// Deploy governance with token and staking
|
|
Governance gov = new Governance(
|
|
address(token),
|
|
address(staking),
|
|
admin
|
|
);
|
|
console.log("Governance:", address(gov));
|
|
|
|
// Setup permissions
|
|
token.grantRole(token.MINTER_ROLE(), address(staking));
|
|
staking.setGovernance(address(gov));
|
|
|
|
vm.stopBroadcast();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Configuration-Based Deployment
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {Token} from "../src/Token.sol";
|
|
|
|
contract DeployConfigured is Script {
|
|
struct Config {
|
|
string name;
|
|
string symbol;
|
|
uint256 initialSupply;
|
|
address admin;
|
|
}
|
|
|
|
function run() external {
|
|
Config memory config = getConfig();
|
|
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
Token token = new Token(config.name, config.symbol, 18);
|
|
token.mint(config.admin, config.initialSupply);
|
|
|
|
if (config.admin != vm.addr(deployerKey)) {
|
|
token.transferOwnership(config.admin);
|
|
}
|
|
|
|
vm.stopBroadcast();
|
|
|
|
console.log("Deployed:", address(token));
|
|
}
|
|
|
|
function getConfig() internal view returns (Config memory) {
|
|
uint256 chainId = block.chainid;
|
|
|
|
if (chainId == 1) {
|
|
return Config({
|
|
name: "Production Token",
|
|
symbol: "PROD",
|
|
initialSupply: 100_000_000e18,
|
|
admin: 0x1234567890123456789012345678901234567890
|
|
});
|
|
} else if (chainId == 11155111) {
|
|
return Config({
|
|
name: "Test Token",
|
|
symbol: "TEST",
|
|
initialSupply: 1_000_000e18,
|
|
admin: vm.envAddress("TEST_ADMIN")
|
|
});
|
|
} else {
|
|
revert("Unsupported chain");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Deterministic Deployment (CREATE2)
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {Token} from "../src/Token.sol";
|
|
|
|
contract DeployDeterministic is Script {
|
|
// Deterministic deployment factory (present on most chains)
|
|
address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
|
|
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
bytes32 salt = keccak256("my-token-v1");
|
|
|
|
// Predict address
|
|
bytes memory bytecode = abi.encodePacked(
|
|
type(Token).creationCode,
|
|
abi.encode("MyToken", "MTK", 18)
|
|
);
|
|
address predicted = computeCreate2Address(salt, keccak256(bytecode));
|
|
console.log("Predicted address:", predicted);
|
|
|
|
// Check if already deployed
|
|
if (predicted.code.length > 0) {
|
|
console.log("Already deployed!");
|
|
return;
|
|
}
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
Token token = new Token{salt: salt}("MyToken", "MTK", 18);
|
|
require(address(token) == predicted, "Address mismatch");
|
|
|
|
vm.stopBroadcast();
|
|
|
|
console.log("Deployed to:", address(token));
|
|
}
|
|
|
|
function computeCreate2Address(bytes32 salt, bytes32 initCodeHash)
|
|
internal
|
|
view
|
|
returns (address)
|
|
{
|
|
return address(uint160(uint256(keccak256(abi.encodePacked(
|
|
bytes1(0xff),
|
|
address(this),
|
|
salt,
|
|
initCodeHash
|
|
)))));
|
|
}
|
|
}
|
|
```
|
|
|
|
## Multi-Chain Deployment
|
|
|
|
### Sequential Deployment
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {Token} from "../src/Token.sol";
|
|
|
|
contract DeployMultiChain is Script {
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
|
|
// Deploy to Ethereum
|
|
vm.createSelectFork("mainnet");
|
|
vm.startBroadcast(deployerKey);
|
|
Token mainnetToken = new Token("Token", "TKN", 18);
|
|
vm.stopBroadcast();
|
|
console.log("Mainnet:", address(mainnetToken));
|
|
|
|
// Deploy to Arbitrum
|
|
vm.createSelectFork("arbitrum");
|
|
vm.startBroadcast(deployerKey);
|
|
Token arbitrumToken = new Token("Token", "TKN", 18);
|
|
vm.stopBroadcast();
|
|
console.log("Arbitrum:", address(arbitrumToken));
|
|
|
|
// Deploy to Optimism
|
|
vm.createSelectFork("optimism");
|
|
vm.startBroadcast(deployerKey);
|
|
Token optimismToken = new Token("Token", "TKN", 18);
|
|
vm.stopBroadcast();
|
|
console.log("Optimism:", address(optimismToken));
|
|
}
|
|
}
|
|
```
|
|
|
|
Run with:
|
|
|
|
```bash
|
|
forge script script/DeployMultiChain.s.sol \
|
|
--broadcast \
|
|
--multi \
|
|
--slow \
|
|
--verify
|
|
```
|
|
|
|
## Contract Verification
|
|
|
|
### Auto-Verification
|
|
|
|
```bash
|
|
# During deployment
|
|
forge create src/Token.sol:Token \
|
|
--rpc-url sepolia \
|
|
--private-key $KEY \
|
|
--verify \
|
|
--etherscan-api-key $ETHERSCAN_KEY \
|
|
--constructor-args "Name" "SYM" 18
|
|
|
|
# With script
|
|
forge script script/Deploy.s.sol --broadcast --verify
|
|
```
|
|
|
|
### Manual Verification
|
|
|
|
```bash
|
|
# Verify existing contract
|
|
forge verify-contract \
|
|
--chain sepolia \
|
|
--compiler-version 0.8.30 \
|
|
--num-of-optimizations 200 \
|
|
--constructor-args $(cast abi-encode "constructor(string,string,uint8)" "Name" "SYM" 18) \
|
|
0xYourContractAddress \
|
|
src/Token.sol:Token
|
|
|
|
# Check verification status
|
|
forge verify-check \
|
|
--chain sepolia \
|
|
$GUID
|
|
```
|
|
|
|
### Verification with Libraries
|
|
|
|
```bash
|
|
forge verify-contract \
|
|
--chain mainnet \
|
|
--libraries src/lib/Math.sol:Math:0xLibraryAddress \
|
|
--libraries src/lib/Utils.sol:Utils:0xUtilsAddress \
|
|
0xContractAddress \
|
|
src/MyContract.sol:MyContract
|
|
```
|
|
|
|
### Configuration for Verification
|
|
|
|
```toml
|
|
# foundry.toml
|
|
[etherscan]
|
|
mainnet = { key = "${ETHERSCAN_API_KEY}" }
|
|
sepolia = { key = "${ETHERSCAN_API_KEY}" }
|
|
arbitrum = { key = "${ARBISCAN_API_KEY}" }
|
|
optimism = { key = "${OPTIMISTIC_ETHERSCAN_API_KEY}" }
|
|
base = { key = "${BASESCAN_API_KEY}" }
|
|
polygon = { key = "${POLYGONSCAN_API_KEY}" }
|
|
```
|
|
|
|
## Broadcast Artifacts
|
|
|
|
Scripts save transaction data to `broadcast/` directory:
|
|
|
|
```
|
|
broadcast/
|
|
└── Deploy.s.sol/
|
|
└── 11155111/ # Chain ID
|
|
├── run-latest.json # Latest run
|
|
├── run-1699999999.json # Timestamped runs
|
|
└── receipts/
|
|
└── ...
|
|
```
|
|
|
|
### Reading Artifacts in Scripts
|
|
|
|
```solidity
|
|
function readDeployment() internal view returns (address) {
|
|
string memory root = vm.projectRoot();
|
|
string memory path = string.concat(
|
|
root,
|
|
"/broadcast/Deploy.s.sol/11155111/run-latest.json"
|
|
);
|
|
|
|
string memory json = vm.readFile(path);
|
|
bytes memory contractAddr = json.parseRaw(".transactions[0].contractAddress");
|
|
return abi.decode(contractAddr, (address));
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Environment Variables
|
|
|
|
```solidity
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
address admin = vm.envAddress("ADMIN_ADDRESS");
|
|
string memory rpcUrl = vm.envString("RPC_URL");
|
|
```
|
|
|
|
### 2. Validate Before Broadcasting
|
|
|
|
```solidity
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
address deployer = vm.addr(deployerKey);
|
|
|
|
// Pre-flight checks
|
|
require(deployer.balance > 0.1 ether, "Insufficient balance");
|
|
require(block.chainid == 11155111, "Wrong network");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### 3. Log Everything
|
|
|
|
```solidity
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
Token token = new Token("Name", "SYM", 18);
|
|
console.log("Token deployed to:", address(token));
|
|
console.log(" - Name:", token.name());
|
|
console.log(" - Symbol:", token.symbol());
|
|
console.log(" - Owner:", token.owner());
|
|
|
|
vm.stopBroadcast();
|
|
```
|
|
|
|
### 4. Use Numbered Scripts
|
|
|
|
```
|
|
script/
|
|
├── 01_DeployToken.s.sol
|
|
├── 02_DeployStaking.s.sol
|
|
├── 03_ConfigurePermissions.s.sol
|
|
└── 04_TransferOwnership.s.sol
|
|
```
|
|
|
|
### 5. Test Scripts
|
|
|
|
```solidity
|
|
contract DeployTokenTest is Test {
|
|
DeployToken deployer;
|
|
|
|
function setUp() public {
|
|
deployer = new DeployToken();
|
|
// Set required env vars
|
|
vm.setEnv("PRIVATE_KEY", vm.toString(uint256(1)));
|
|
}
|
|
|
|
function testDeploy() public {
|
|
deployer.run();
|
|
// Verify deployment
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. Handle Failures Gracefully
|
|
|
|
```solidity
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
try new Token("Name", "SYM", 18) returns (Token token) {
|
|
console.log("Success:", address(token));
|
|
} catch Error(string memory reason) {
|
|
console.log("Failed:", reason);
|
|
}
|
|
|
|
vm.stopBroadcast();
|
|
}
|
|
```
|
|
|
|
## Upgrade Patterns
|
|
|
|
### Transparent Proxy
|
|
|
|
```solidity
|
|
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
|
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
|
|
|
contract DeployUpgradeable is Script {
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
// Deploy implementation
|
|
TokenV1 impl = new TokenV1();
|
|
|
|
// Deploy proxy admin
|
|
ProxyAdmin admin = new ProxyAdmin(msg.sender);
|
|
|
|
// Deploy proxy
|
|
bytes memory initData = abi.encodeCall(TokenV1.initialize, ("Name", "SYM"));
|
|
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
|
address(impl),
|
|
address(admin),
|
|
initData
|
|
);
|
|
|
|
vm.stopBroadcast();
|
|
|
|
console.log("Implementation:", address(impl));
|
|
console.log("ProxyAdmin:", address(admin));
|
|
console.log("Proxy:", address(proxy));
|
|
}
|
|
}
|
|
```
|
|
|
|
### UUPS Proxy
|
|
|
|
```solidity
|
|
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
|
|
contract DeployUUPS is Script {
|
|
function run() external {
|
|
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
|
|
vm.startBroadcast(deployerKey);
|
|
|
|
// Deploy implementation
|
|
TokenV1 impl = new TokenV1();
|
|
|
|
// Deploy proxy
|
|
bytes memory initData = abi.encodeCall(TokenV1.initialize, ("Name", "SYM"));
|
|
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData);
|
|
|
|
vm.stopBroadcast();
|
|
|
|
console.log("Implementation:", address(impl));
|
|
console.log("Proxy:", address(proxy));
|
|
}
|
|
}
|
|
```
|