Zeus Development Patterns
This guide outlines common patterns and techniques for developing with Zeus effectively.
Deployment Strategy Patterns
The Phase Pattern
Split complex deployments into clear phases:
// Phase 1: Core contract deployment with EOA
contract Phase1CoreDeployment is EOADeployer {
function run() public override {
// Deploy core contracts
}
}
// Phase 2: Configuration with multisig
contract Phase2Configuration is MultisigBuilder {
function run() public override {
// Configure core contracts
}
}
// Phase 3: Integration with multisig
contract Phase3Integration is MultisigBuilder {
function run() public override {
// Connect contracts together
}
}
Benefits:
- Clear separation of concerns
- Breaks complex migrations into manageable steps
- Allows different signers for different phases
The Feature Toggle Pattern
Use environment parameters as feature toggles:
contract FeatureToggleExample is EOADeployer {
function runAsEOA() internal override {
// Deploy base contract
address token = deploySingleton("Token", type(Token).creationCode);
// Check feature toggle
if (zBool("ENABLE_STAKING")) {
// Deploy and connect staking contract
address staking = deploySingleton("Staking", type(Staking).creationCode);
// Configure staking
Staking(staking).setToken(token);
}
}
}
Benefits:
- Enables gradual feature rollout
- Provides flexibility across environments
- Simplifies testing and incremental development
Testing Patterns
The Test Function Pattern
Include test functions directly in deployment scripts:
contract TestFunctionExample is EOADeployer {
function runAsEOA() internal override {
// Deployment logic
}
// Runs with `zeus test`
function testDeployment() public {
// Run deployment
this.deploy();
// Assert expected outcomes
address registry = getDeployedAddress("Registry");
Registry r = Registry(registry);
assert(r.owner() == zAddress("EXPECTED_OWNER"));
assert(r.isInitialized());
}
// Additional test for specific functionality
function testRegistryFeatures() public {
// Setup test environment
this.deploy();
// Test registry features
address registry = getDeployedAddress("Registry");
Registry r = Registry(registry);
r.register("test", address(this));
assert(r.lookup("test") == address(this));
}
}
Benefits:
- Tests live alongside deployment code
- Automatic execution with
zeus test
- Verifies deployment correctness
The Mock Environment Pattern
Create dedicated testing environments:
# Create a testing environment
zeus env new --name test-env --version 0.0.0
// Test-specific deployment script
contract TestEnvironmentSetup is EOADeployer {
function runAsEOA() internal override {
// Set testing-specific parameters
zUpdate("IS_TEST", true);
zUpdate("BLOCK_TIME", 1); // Fast block time for tests
// Deploy mock external dependencies
deploySingleton("MockOracle", type(MockOracle).creationCode);
// Deploy system under test
deploySingleton("Protocol", type(Protocol).creationCode);
}
}
Benefits:
- Isolates testing from production environments
- Allows testing with mocked dependencies
- Enables quicker test execution
Upgrade Patterns
The Progressive Upgrade Pattern
Perform upgrades across multiple phases:
my-upgrade/
upgrade.json
1-prepare.s.sol
2-upgrade.s.sol
3-migrate.s.sol
4-cleanup.s.sol
// upgrade.json
{
"name": "v2-upgrade",
"from": "1.0.0",
"to": "2.0.0",
"phases": [
{ "type": "eoa", "filename": "1-prepare.s.sol" },
{ "type": "multisig", "filename": "2-upgrade.s.sol" },
{ "type": "multisig", "filename": "3-migrate.s.sol" },
{ "type": "eoa", "filename": "4-cleanup.s.sol" }
]
}
Benefits:
- Breaks complex upgrades into manageable steps
- Reduces risk through incremental changes
- Allows different signers for different phases
The Parallel Implementation Pattern
Deploy new implementation alongside existing one:
contract ParallelImplementation is MultisigBuilder {
function _runAsMultisig() internal override {
// Deploy new implementation without changing proxy
address newImpl = deploySingleton(
"TokenV2Impl",
type(TokenV2).creationCode
);
// Allow time for verification and testing
// No proxy update yet - will happen in a later phase
// Record for future reference
zUpdate("NEW_TOKEN_IMPL", newImpl);
}
}
Benefits:
- Separates deployment from activation
- Allows verification before switching
- Reduces risk during complex upgrades
Parameter Management Patterns
The Environment Type Pattern
Structure parameters by environment type:
contract EnvironmentTypeExample is EOADeployer {
function runAsEOA() internal override {
string memory envType = zString("ENVIRONMENT_TYPE");
if (keccak256(bytes(envType)) == keccak256(bytes("production"))) {
// Production settings
zUpdate("MAX_DEPOSITS", 1000000 ether);
zUpdate("COOL_DOWN_PERIOD", 7 days);
} else if (keccak256(bytes(envType)) == keccak256(bytes("staging"))) {
// Staging settings
zUpdate("MAX_DEPOSITS", 10000 ether);
zUpdate("COOL_DOWN_PERIOD", 1 days);
} else {
// Development settings
zUpdate("MAX_DEPOSITS", 100 ether);
zUpdate("COOL_DOWN_PERIOD", 1 hours);
}
}
}
Benefits:
- Consistent parameter sets by environment type
- Simplifies environment creation
- Ensures appropriate settings for each context
The Parameter Namespace Pattern
Organize parameters with namespaces:
contract ParameterNamespaceExample is EOADeployer {
function runAsEOA() internal override {
// Network namespace
zUpdate("network.chainId", 1);
zUpdate("network.isTestnet", false);
// Protocol namespace
zUpdate("protocol.name", "MyProtocol");
zUpdate("protocol.version", 1);
// Governance namespace
zUpdate("governance.treasury", address(0x123...));
zUpdate("governance.multisig", address(0x456...));
}
}
Benefits:
- Organizes parameters logically
- Prevents naming conflicts
- Improves parameter discoverability
Multi-Organization Patterns
The Role-Based Access Pattern
Implement role-based access for multi-organization deployments:
contract RoleBasedExample is MultisigBuilder {
function _runAsMultisig() internal override {
// Get deployed registry
address registry = getDeployedAddress("Registry");
// Assign different roles to different organizations
bytes memory grantAdminRole = abi.encodeWithSignature(
"grantRole(bytes32,address)",
keccak256("ADMIN_ROLE"),
zAddress("ORG_A_ADMIN")
);
bytes memory grantOperatorRole = abi.encodeWithSignature(
"grantRole(bytes32,address)",
keccak256("OPERATOR_ROLE"),
zAddress("ORG_B_OPERATOR")
);
// Execute these transactions
execute(registry, 0, grantAdminRole);
execute(registry, 0, grantOperatorRole);
}
}
Benefits:
- Clear separation of responsibilities
- Enables collaboration across organizations
- Limits privileges to necessary functions
Best Practices
-
Keep Phases Focused
- Each deployment phase should have a clear, single purpose
- Avoid mixing concerns in the same phase
-
Plan for Rollbacks
- Design upgrades with rollback mechanisms
- Test rollback procedures before deployment
-
Documentation in Code
- Add detailed comments explaining deployment steps
- Document parameter meanings and valid values
-
Progressive Testing
- Test each phase individually
- Test complete upgrade paths end-to-end
-
Parameter Validation
- Validate parameters during deployment
- Use defensive coding to handle missing or invalid parameters