Files
2025-11-30 09:01:14 +08:00

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));
}
}
```