|
| 1 | +# Foundry Environment Variables Guide |
| 2 | + |
| 3 | +This guide shows the **recommended Foundry-native approaches** for reading environment variables in deployment scripts, without custom parsers. |
| 4 | + |
| 5 | +## 🎯 Recommended Approaches |
| 6 | + |
| 7 | +### 1. **Comma-Separated Arrays** ⭐ **BEST FOR MOST CASES** |
| 8 | + |
| 9 | +**Use Case:** Arrays of addresses, numbers, or simple types |
| 10 | + |
| 11 | +**Environment Format:** |
| 12 | +```bash |
| 13 | +# Multiple addresses |
| 14 | +INIT_SIGNERS=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,0x025ceeba2ab2a27d53d963393999eeebe83dc4ae |
| 15 | + |
| 16 | +# Multiple numbers |
| 17 | +CHAIN_IDS=1,137,56,43114 |
| 18 | + |
| 19 | +# Multiple strings (if supported) |
| 20 | +NETWORKS=ethereum,polygon,bsc |
| 21 | +``` |
| 22 | + |
| 23 | +**Solidity Code:** |
| 24 | +```solidity |
| 25 | +// Read address array |
| 26 | +address[] memory signers = vm.envAddress("INIT_SIGNERS", ","); |
| 27 | +
|
| 28 | +// Read uint array |
| 29 | +uint256[] memory chainIds = vm.envUint("CHAIN_IDS", ","); |
| 30 | +
|
| 31 | +// Convert addresses to bytes32 if needed |
| 32 | +bytes32[] memory guardians = new bytes32[](signers.length); |
| 33 | +for (uint i = 0; i < signers.length; i++) { |
| 34 | + guardians[i] = bytes32(uint256(uint160(signers[i]))); |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +**✅ Advantages:** |
| 39 | +- Native Foundry support |
| 40 | +- Clean, readable format |
| 41 | +- No parsing needed |
| 42 | +- Works with all basic types |
| 43 | + |
| 44 | +### 2. **JSON with vm.parseJson** (For Complex Data) |
| 45 | + |
| 46 | +**Use Case:** Complex nested data structures |
| 47 | + |
| 48 | +**Environment Format:** |
| 49 | +```bash |
| 50 | +# Simple array |
| 51 | +INIT_SIGNERS='["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5","0x025ceeba2ab2a27d53d963393999eeebe83dc4ae"]' |
| 52 | + |
| 53 | +# Complex object |
| 54 | +CONFIG='{"guardians":["0x..."],"chainId":1,"enabled":true}' |
| 55 | +``` |
| 56 | + |
| 57 | +**Solidity Code:** |
| 58 | +```solidity |
| 59 | +// Simple array |
| 60 | +string memory signersJson = vm.envString("INIT_SIGNERS"); |
| 61 | +address[] memory signers = abi.decode(vm.parseJson(signersJson), (address[])); |
| 62 | +
|
| 63 | +// Complex object with key selection |
| 64 | +string memory configJson = vm.envString("CONFIG"); |
| 65 | +address[] memory guardians = abi.decode(vm.parseJson(configJson, ".guardians"), (address[])); |
| 66 | +uint256 chainId = abi.decode(vm.parseJson(configJson, ".chainId"), (uint256)); |
| 67 | +bool enabled = abi.decode(vm.parseJson(configJson, ".enabled"), (bool)); |
| 68 | +``` |
| 69 | + |
| 70 | +**⚠️ Requirements:** |
| 71 | +- JSON must be properly formatted |
| 72 | +- Use single quotes in .env files to prevent shell parsing issues |
| 73 | +- May need escaping for complex nested structures |
| 74 | + |
| 75 | +### 3. **Individual Environment Variables** (Simple & Reliable) |
| 76 | + |
| 77 | +**Use Case:** When you have a known, small number of values |
| 78 | + |
| 79 | +**Environment Format:** |
| 80 | +```bash |
| 81 | +GUARDIAN_1=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5 |
| 82 | +GUARDIAN_2=0x025ceeba2ab2a27d53d963393999eeebe83dc4ae |
| 83 | +GUARDIAN_COUNT=2 |
| 84 | +``` |
| 85 | + |
| 86 | +**Solidity Code:** |
| 87 | +```solidity |
| 88 | +uint256 guardianCount = vm.envUint("GUARDIAN_COUNT"); |
| 89 | +bytes32[] memory guardians = new bytes32[](guardianCount); |
| 90 | +
|
| 91 | +for (uint i = 0; i < guardianCount; i++) { |
| 92 | + string memory key = string(abi.encodePacked("GUARDIAN_", vm.toString(i + 1))); |
| 93 | + address guardian = vm.envAddress(key); |
| 94 | + guardians[i] = bytes32(uint256(uint160(guardian))); |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +**✅ Advantages:** |
| 99 | +- Most reliable |
| 100 | +- Easy to debug |
| 101 | +- No parsing issues |
| 102 | +- Clear variable names |
| 103 | + |
| 104 | +## 📋 Available Foundry VM Methods |
| 105 | + |
| 106 | +```solidity |
| 107 | +// Basic types |
| 108 | +vm.envString("KEY") // string |
| 109 | +vm.envAddress("KEY") // address |
| 110 | +vm.envUint("KEY") // uint256 |
| 111 | +vm.envInt("KEY") // int256 |
| 112 | +vm.envBytes32("KEY") // bytes32 |
| 113 | +vm.envBytes("KEY") // bytes |
| 114 | +vm.envBool("KEY") // bool |
| 115 | +
|
| 116 | +// Arrays with delimiter |
| 117 | +vm.envAddress("KEY", ",") // address[] |
| 118 | +vm.envUint("KEY", ",") // uint256[] |
| 119 | +vm.envInt("KEY", ",") // int256[] |
| 120 | +vm.envBytes32("KEY", ",") // bytes32[] |
| 121 | +vm.envString("KEY", ",") // string[] |
| 122 | +
|
| 123 | +// With default values |
| 124 | +vm.envOr("KEY", defaultValue) |
| 125 | +
|
| 126 | +// JSON parsing |
| 127 | +vm.parseJson(jsonString) // Parse entire JSON |
| 128 | +vm.parseJson(jsonString, ".key") // Parse specific key |
| 129 | +``` |
| 130 | + |
| 131 | +## 🛠️ Our Implementation |
| 132 | + |
| 133 | +**Current Deploy.s.sol uses Approach #1 (Comma-Separated):** |
| 134 | + |
| 135 | +```solidity |
| 136 | +// ✅ Clean, native Foundry approach |
| 137 | +address[] memory signerAddresses = vm.envAddress("INIT_SIGNERS", ","); |
| 138 | +
|
| 139 | +// Convert to bytes32 for Wormhole |
| 140 | +bytes32[] memory initialSigners = new bytes32[](signerAddresses.length); |
| 141 | +for (uint i = 0; i < signerAddresses.length; i++) { |
| 142 | + initialSigners[i] = bytes32(uint256(uint160(signerAddresses[i]))); |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +**Environment Format:** |
| 147 | +```bash |
| 148 | +INIT_SIGNERS=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,0x025ceeba2ab2a27d53d963393999eeebe83dc4ae |
| 149 | +``` |
| 150 | + |
| 151 | +## 🚫 What to Avoid |
| 152 | + |
| 153 | +- ❌ Custom string parsing functions |
| 154 | +- ❌ Complex regex operations |
| 155 | +- ❌ Manual hex conversion (use vm.envAddress instead) |
| 156 | +- ❌ Hardcoded values in scripts |
| 157 | +- ❌ Unescaped JSON in environment files |
| 158 | + |
| 159 | +## 💡 Best Practices |
| 160 | + |
| 161 | +1. **Use comma-separated for simple arrays** |
| 162 | +2. **Use individual vars for small, known sets** |
| 163 | +3. **Use JSON only for complex nested data** |
| 164 | +4. **Always validate environment variables exist** |
| 165 | +5. **Use `vm.envOr()` for optional values with defaults** |
| 166 | +6. **Keep environment files well-documented** |
| 167 | +7. **Test with different environment configurations** |
| 168 | + |
| 169 | +## 🧪 Testing |
| 170 | + |
| 171 | +```bash |
| 172 | +# Test with current environment |
| 173 | +forge script script/Deploy.s.sol |
| 174 | + |
| 175 | +# Test with specific environment file |
| 176 | +cp .env.test .env && forge script script/Deploy.s.sol |
| 177 | + |
| 178 | +# Test individual components |
| 179 | +forge script script/Deploy.s.sol --sig "deployWormhole()" |
| 180 | +``` |
0 commit comments