Skip to main content

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​

  1. Keep Phases Focused

    • Each deployment phase should have a clear, single purpose
    • Avoid mixing concerns in the same phase
  2. Plan for Rollbacks

    • Design upgrades with rollback mechanisms
    • Test rollback procedures before deployment
  3. Documentation in Code

    • Add detailed comments explaining deployment steps
    • Document parameter meanings and valid values
  4. Progressive Testing

    • Test each phase individually
    • Test complete upgrade paths end-to-end
  5. Parameter Validation

    • Validate parameters during deployment
    • Use defensive coding to handle missing or invalid parameters

Next Steps​