This guide shows the recommended Foundry-native approaches for reading environment variables in deployment scripts, without custom parsers.
Use Case: Arrays of addresses, numbers, or simple types
Environment Format:
# Multiple addresses
INIT_SIGNERS=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,0x025ceeba2ab2a27d53d963393999eeebe83dc4ae
# Multiple numbers
CHAIN_IDS=1,137,56,43114
# Multiple strings (if supported)
NETWORKS=ethereum,polygon,bsc
Solidity Code:
// Read address array
address[] memory signers = vm.envAddress("INIT_SIGNERS", ",");
// Read uint array
uint256[] memory chainIds = vm.envUint("CHAIN_IDS", ",");
// Convert addresses to bytes32 if needed
bytes32[] memory guardians = new bytes32[](signers.length);
for (uint i = 0; i < signers.length; i++) {
guardians[i] = bytes32(uint256(uint160(signers[i])));
}
✅ Advantages:
Use Case: Complex nested data structures
Environment Format:
# Simple array
INIT_SIGNERS='["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5","0x025ceeba2ab2a27d53d963393999eeebe83dc4ae"]'
# Complex object
CONFIG='{"guardians":["0x..."],"chainId":1,"enabled":true}'
Solidity Code:
// Simple array
string memory signersJson = vm.envString("INIT_SIGNERS");
address[] memory signers = abi.decode(vm.parseJson(signersJson), (address[]));
// Complex object with key selection
string memory configJson = vm.envString("CONFIG");
address[] memory guardians = abi.decode(vm.parseJson(configJson, ".guardians"), (address[]));
uint256 chainId = abi.decode(vm.parseJson(configJson, ".chainId"), (uint256));
bool enabled = abi.decode(vm.parseJson(configJson, ".enabled"), (bool));
⚠️ Requirements:
Use Case: When you have a known, small number of values
Environment Format:
GUARDIAN_1=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5
GUARDIAN_2=0x025ceeba2ab2a27d53d963393999eeebe83dc4ae
GUARDIAN_COUNT=2
Solidity Code:
uint256 guardianCount = vm.envUint("GUARDIAN_COUNT");
bytes32[] memory guardians = new bytes32[](guardianCount);
for (uint i = 0; i < guardianCount; i++) {
string memory key = string(abi.encodePacked("GUARDIAN_", vm.toString(i + 1)));
address guardian = vm.envAddress(key);
guardians[i] = bytes32(uint256(uint160(guardian)));
}
✅ Advantages:
// Basic types
vm.envString("KEY") // string
vm.envAddress("KEY") // address
vm.envUint("KEY") // uint256
vm.envInt("KEY") // int256
vm.envBytes32("KEY") // bytes32
vm.envBytes("KEY") // bytes
vm.envBool("KEY") // bool
// Arrays with delimiter
vm.envAddress("KEY", ",") // address[]
vm.envUint("KEY", ",") // uint256[]
vm.envInt("KEY", ",") // int256[]
vm.envBytes32("KEY", ",") // bytes32[]
vm.envString("KEY", ",") // string[]
// With default values
vm.envOr("KEY", defaultValue)
// JSON parsing
vm.parseJson(jsonString) // Parse entire JSON
vm.parseJson(jsonString, ".key") // Parse specific key
Current Deploy.s.sol uses Approach #1 (Comma-Separated):
// ✅ Clean, native Foundry approach
address[] memory signerAddresses = vm.envAddress("INIT_SIGNERS", ",");
// Convert to bytes32 for Wormhole
bytes32[] memory initialSigners = new bytes32[](signerAddresses.length);
for (uint i = 0; i < signerAddresses.length; i++) {
initialSigners[i] = bytes32(uint256(uint160(signerAddresses[i])));
}
Environment Format:
INIT_SIGNERS=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,0x025ceeba2ab2a27d53d963393999eeebe83dc4ae
vm.envOr() for optional values with defaults# Test with current environment
forge script script/Deploy.s.sol
# Test with specific environment file
cp .env.test .env && forge script script/Deploy.s.sol
# Test individual components
forge script script/Deploy.s.sol --sig "deployWormhole()"