Initial commit
This commit is contained in:
554
skills/skill/references/deployment.md
Normal file
554
skills/skill/references/deployment.md
Normal file
@@ -0,0 +1,554 @@
|
||||
# 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));
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user