Tutorial: Upgrading ATS Contracts
This comprehensive guide covers upgrading facets and configurations in the Asset Tokenization Studio (ATS) smart contract system without disrupting existing tokens.
Table of Contents
- Overview
- Understanding Versioning
- Upgrade Scenarios
- Safe Upgrade Process
- Step-by-Step Guides
- Testing Upgrades
- Production Deployment
- Rollback Procedures
- Best Practices
Overview
The ATS upgrade system uses the Diamond Pattern (EIP-2535) with a Business Logic Resolver (BLR) to enable safe, versioned upgrades of facet implementations without requiring token redeployment.
Key Concepts
Business Logic Resolver (BLR)
- Central registry mapping resolver keys → facet addresses
- Maintains global version counter across all facets
- Manages configurations defining facet sets for token types
Global Versioning
- Single
latestVersioncounter incremented on any facet registration - All previously registered facets must be re-registered together
- Ensures atomic updates across the system
Configurations
- Define which facets compose a token type (Equity, Bond)
- Each configuration has independent version history
- Tokens can pin to specific config versions or auto-update
Resolver Proxy Pattern
- Each token is a proxy contract
- Routes function calls to facets via BLR resolution
- Can use pinned version or always-latest version
Understanding Versioning
Three-Level Version System
┌─────────────────────────────────────────┐
│ BLR Global Version (latestVersion) │ ← Increments on any facet registration
│ Current: 5 │
└─────────────────────────────────────────┘
│
├─ Facet Version Histories
│ ├─ AccessControlFacet: v1, v2, v3, v4, v5
│ ├─ BondFacet: v1, v2, v3, v4, v5
│ └─ RewardsFacet: (none), (none), (none), v4, v5
│
└─ Configuration Versions
├─ Equity Config: v1, v2, v3
│ └─ v3 uses facet global version 5
└─ Bond Config: v1, v2
└─ v2 uses facet global version 4
Version Resolution Modes
Mode 1: Pinned Version (Recommended for Production)
// Token configuration
configurationVersion = 2; // Fixed to version 2
// Resolution: Always uses exact configuration version 2
// Upgrades require explicit update transaction
Mode 2: Auto-Update (Development/Testing)
// Token configuration
configurationVersion = 0; // or LATEST_VERSION
// Resolution: Always resolves to latest configuration version
// Upgrades happen automatically on next function call
Upgrade Scenarios
Scenario 1: Bug Fix in Single Facet
Situation: Critical bug found in BondFacet
Impact: Only affects bonds using latest version
Approach:
- Deploy fixed
BondFacetv2 - Register all facets (v2 for Bond, existing versions for others)
- Create new Bond configuration v2
- Coordinate bond token upgrades (or auto-update if using version 0)
Scenario 2: New Feature Facet
Situation: Adding new RewardsFacet
Impact: Available only to new configurations
Approach:
- Deploy new
RewardsFacet - Register all facets including new
RewardsFacet - Create Equity configuration v2 including rewards
- New tokens use v2, existing tokens remain on v1
Scenario 3: Multiple Facet Updates
Situation: Major upgrade affecting 5+ facets
Impact: Requires comprehensive testing
Approach:
- Deploy all updated facets
- Register all facets together (atomic update)
- Create new configurations
- Staged rollout to production tokens
Scenario 4: Infrastructure Upgrade (BLR or Factory)
Situation: Upgrading BLR implementation contract
Impact: Affects entire system
Approach:
- Deploy new BLR implementation
- Use
ProxyAdmin.upgradeAndCall()on BLR proxy - Test thoroughly before facet updates
- No token-level changes required (proxy pattern)
Safe Upgrade Process
Pre-Upgrade Checklist
- Code review completed
- Unit tests passing (100% coverage for changes)
- Integration tests passing
- Gas analysis performed
- Security audit completed (for major changes)
- Testnet deployment successful
- Rollback plan documented
- Stakeholder notification prepared
Upgrade Phases
Phase 1: Development
- Implement facet changes
- Write comprehensive tests
- Document breaking changes
Phase 2: Testing
- Deploy to local network
- Deploy to testnet (previewnet/testnet)
- Perform integration testing
- Load testing (if applicable)
Phase 3: Staging
- Deploy to staging environment
- Mirror production configuration
- Perform final validation
Phase 4: Production
- Deploy during maintenance window
- Monitor for issues
- Gradual rollout if possible
Phase 5: Post-Upgrade
- Verify all functionality
- Monitor events and transactions
- Stakeholder communication
Step-by-Step Guides
Guide 1: Upgrading a Single Facet
Scenario: Fix bug in BondFacet
Step 1: Prepare New Implementation
cd packages/ats/contracts
# Make your changes to contracts/layer_2/bond/Bond.sol
# Update version in comments/NatSpec
Step 2: Compile and Test
# Compile contracts
npm run compile
# Run specific tests
npm run test -- test/layer_2/bond/Bond.test.ts
# Run all tests
npm run test
Step 3: Deploy New Facet to Testnet
Create deployment script: scripts/maintenance/upgradeBondFacet.ts
import { ethers } from "hardhat";
import { BusinessLogicResolver } from "../../typechain-types";
import { registerFacets } from "../infrastructure/operations/registerFacets";
import { atsRegistry } from "../domain/atsRegistry";
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying BondFacet upgrade from:", deployer.address);
// Deploy new BondFacet implementation
const BondFacetFactory = await ethers.getContractFactory("BondFacet");
const newBondFacet = await BondFacetFactory.deploy();
await newBondFacet.waitForDeployment();
const newBondAddress = await newBondFacet.getAddress();
console.log("New BondFacet deployed to:", newBondAddress);
// Get BLR instance
const blrAddress = process.env.BLR_PROXY_ADDRESS;
const blr = await ethers.getContractAt("BusinessLogicResolver", blrAddress);
// Get current facet addresses
const currentVersion = await blr.getLatestVersion();
console.log("Current BLR version:", currentVersion);
// Prepare registration data - MUST include ALL facets
const facetsToRegister = [
{
name: "AccessControlFacet",
address: process.env.ACCESS_CONTROL_FACET_ADDRESS, // Existing
resolverKey: atsRegistry.getFacetDefinition("AccessControlFacet").resolverKey.value,
},
{
name: "BondFacet",
address: newBondAddress, // NEW VERSION
resolverKey: atsRegistry.getFacetDefinition("BondFacet").resolverKey.value,
},
// ... ALL other facets with existing addresses
];
// Register all facets (increments version)
console.log("Registering facets...");
const result = await registerFacets(blr, facetsToRegister, {
confirmations: 2,
});
console.log("Registration complete!");
console.log("New BLR version:", await blr.getLatestVersion());
console.log("Transaction hash:", result.transactionHash);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Step 4: Execute Deployment
# Set environment variables
export BLR_PROXY_ADDRESS=0x123...
export ACCESS_CONTROL_FACET_ADDRESS=0x456...
# ... all other facet addresses
# Deploy to testnet
npx hardhat run scripts/maintenance/upgradeBondFacet.ts --network hedera-testnet
Step 5: Create New Configuration
// scripts/maintenance/createBondConfigV2.ts
import { ethers } from "hardhat";
import { createBondConfiguration } from "../domain/bond/createConfiguration";
async function main() {
const blr = await ethers.getContractAt("BusinessLogicResolver", process.env.BLR_PROXY_ADDRESS);
// Get all facet addresses (now includes new BondFacet)
const facetAddresses = new Map([
["BondFacet", process.env.NEW_BOND_FACET_ADDRESS],
// ... all other facets
]);
// Create Bond configuration v2
console.log("Creating Bond configuration v2...");
const result = await createBondConfiguration(blr, facetAddresses, {
batchSize: 15,
confirmations: 2,
});
console.log("Bond configuration v2 created!");
console.log("Config ID:", result.configurationId);
console.log("Version:", result.version);
console.log("Facet count:", result.facetCount);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
npx hardhat run scripts/maintenance/createBondConfigV2.ts --network hedera-testnet
Step 6: Verify Upgrade
npx hardhat console --network hedera-testnet
> const blr = await ethers.getContractAt('BusinessLogicResolver', '0x123...')
> // Check latest version
> await blr.getLatestVersion()
2n // Incremented from 1
> // Check Bond configuration version
> await blr.getConfigurationVersion('0x0000000000000000000000000000000000000000000000000000000000000002')
2n // New configuration version
> // Verify BondFacet address
> await blr.resolveLatestBusinessLogic(BOND_RESOLVER_KEY)
'0xNEW_BOND_FACET_ADDRESS'
Guide 2: Adding a New Facet to Existing Configuration
Scenario: Add RewardsFacet to Equity tokens
Step 1: Deploy New Facet
// Assuming RewardsFacet already developed (see adding-facets.md)
const RewardsFacetFactory = await ethers.getContractFactory("RewardsFacet");
const rewardsFacet = await RewardsFacetFactory.deploy();
await rewardsFacet.waitForDeployment();
Step 2: Register All Facets (Including New One)
const facetsToRegister = [
// ... all existing facets
{
name: "RewardsFacet",
address: await rewardsFacet.getAddress(),
resolverKey: _REWARDS_RESOLVER_KEY,
},
];
await registerFacets(blr, facetsToRegister);
Step 3: Create Equity Configuration v2 with Rewards
Update scripts/domain/equity/createConfiguration.ts:
export async function createEquityConfiguration(
blr: BusinessLogicResolver,
facetAddresses: Map<string, string>,
options?: CreateConfigurationOptions,
): Promise<CreateConfigurationResult> {
const facetConfigurations: FacetConfiguration[] = [
// ... existing 43 facets
{
facetName: "RewardsFacet", // NEW FACET
resolverKey: atsRegistry.getFacetDefinition("RewardsFacet").resolverKey.value,
address: facetAddresses.get("RewardsFacet")!,
},
];
// Create configuration with 44 facets
const result = await createBatchConfiguration(blr, EQUITY_CONFIG_ID, facetConfigurations, options);
return result;
}
Step 4: Deploy New Equity Tokens
// New equity tokens automatically get RewardsFacet
const factory = await ethers.getContractAt("TREXFactory", factoryAddress);
await factory.createEquityToken(
EQUITY_CONFIG_ID,
0, // Use latest version (now includes rewards)
equityInitData,
);
Step 5: Existing Tokens (Optional Upgrade)
Existing equity tokens on v1 (without rewards) can stay on v1 or upgrade:
Option A: Stay on v1 (No rewards)
- No action required
- Tokens continue using v1 configuration
Option B: Upgrade to v2 (Get rewards)
- Requires governance approval (if ownership allows)
- Call
upgradeConfiguration()on proxy (if implemented)
Guide 3: Upgrading Infrastructure Contracts (BLR)
Scenario: Fix critical bug in BLR implementation
Step 1: Deploy New BLR Implementation
const NewBLRFactory = await ethers.getContractFactory("BusinessLogicResolverV2");
const newBLRImpl = await NewBLRFactory.deploy();
await newBLRImpl.waitForDeployment();
console.log("New BLR implementation:", await newBLRImpl.getAddress());
Step 2: Prepare Upgrade via ProxyAdmin
import { upgradeProxy } from "../infrastructure/operations/upgradeProxy";
const proxyAdmin = await ethers.getContractAt("ProxyAdmin", process.env.PROXY_ADMIN_ADDRESS);
const blrProxy = process.env.BLR_PROXY_ADDRESS;
const newBLRImpl = process.env.NEW_BLR_IMPL_ADDRESS;
// Option 1: Simple upgrade (no reinitialization)
await upgradeProxy(proxyAdmin, {
proxyAddress: blrProxy,
newImplementationAddress: newBLRImpl,
});
// Option 2: Upgrade with reinitialization
const initData = newBLRImpl.interface.encodeFunctionData("reinitialize", [
/* reinit params */
]);
await upgradeProxy(proxyAdmin, {
proxyAddress: blrProxy,
newImplementationAddress: newBLRImpl,
initData: initData,
});
Step 3: Verify Upgrade
> const blr = await ethers.getContractAt('BusinessLogicResolverV2', blrProxyAddress)
> // Verify implementation address
> const implSlot = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'
> const impl = await ethers.provider.getStorage(blrProxyAddress, implSlot)
> console.log('Implementation:', ethers.getAddress('0x' + impl.slice(26)))
> // Test new functionality
> await blr.newFunctionFromV2()
Guide 4: Configuration Selector Blacklisting
Scenario: Deprecate specific function across all tokens
Step 1: Identify Function Selector
const deprecatedSelector = ethers.id("deprecatedFunction(uint256)").slice(0, 10);
console.log("Selector to blacklist:", deprecatedSelector);
// Output: 0x12345678
Step 2: Blacklist Selector in Configuration
const blr = await ethers.getContractAt("BusinessLogicResolver", blrAddress);
// Blacklist for Equity configuration
await blr.addSelectorsToBlacklist(EQUITY_CONFIG_ID, [deprecatedSelector]);
console.log("Selector blacklisted for Equity tokens");
Step 3: Verify Blacklisting
> const equityToken = await ethers.getContractAt('IEquity', tokenAddress)
> // Attempt to call deprecated function
> await equityToken.deprecatedFunction(123)
// Error: Function selector is blacklisted
Testing Upgrades
Local Testing
# Start local Hardhat network
npx hardhat node
# In separate terminal, run upgrade script
npx hardhat run scripts/maintenance/upgradeBondFacet.ts --network localhost
Testnet Testing
# Deploy complete system to testnet
npm run deploy:hedera:testnet
# Create test tokens
npx hardhat run scripts/domain/factory/deployBondToken.ts --network hedera-testnet
# Perform upgrade
npx hardhat run scripts/maintenance/upgradeBondFacet.ts --network hedera-testnet
# Test upgraded functionality
npx hardhat run scripts/test/testBondUpgrade.ts --network hedera-testnet
Upgrade Test Script Example
// scripts/test/testBondUpgrade.ts
import { ethers } from "hardhat";
async function main() {
const bondTokenAddress = process.env.BOND_TOKEN_ADDRESS;
const bond = await ethers.getContractAt("IBond", bondTokenAddress);
console.log("Testing upgraded Bond functionality...");
// Test 1: Old functionality still works
const bondDetails = await bond.getBondDetails();
console.log("✓ getBondDetails() works");
// Test 2: New functionality (if added)
if (bond.interface.hasFunction("newBondFunction")) {
await bond.newBondFunction(/* params */);
console.log("✓ newBondFunction() works");
}
// Test 3: Bug fix verification
// ... test specific bug fix
console.log("All upgrade tests passed!");
}
main().catch(console.error);
Production Deployment
Pre-Deployment
-
Announce maintenance window
Maintenance Window: 2025-01-15 02:00-04:00 UTC
Expected downtime: None (upgrade is seamless)
Affected tokens: All Bond tokens (auto-update enabled) -
Verify all prerequisites
# Check deployer balance
npx hardhat console --network hedera-mainnet
> (await ethers.provider.getBalance(deployerAddress)).toString()
# Verify current state
> const blr = await ethers.getContractAt('BusinessLogicResolver', blrAddress)
> await blr.getLatestVersion()
Deployment
# Set production environment
export NETWORK=hedera-mainnet
export BLR_PROXY_ADDRESS=0xPRODUCTION_BLR
# ... other production addresses
# Execute upgrade
npx hardhat run scripts/maintenance/upgradeBondFacet.ts --network hedera-mainnet
# Monitor transaction
# Use Hedera Mirror Node Explorer
Post-Deployment
-
Verify upgrade
> const blr = await ethers.getContractAt('BusinessLogicResolver', blrAddress)
> await blr.getLatestVersion() // Should be incremented -
Test live tokens
> const bondToken = await ethers.getContractAt('IBond', productionBondAddress)
> await bondToken.getBondDetails() // Should use new implementation -
Monitor events
# Check for errors in recent transactions
curl "https://mainnet.mirrornode.hedera.com/api/v1/contracts/0.0.12345678/results?limit=10" -
Stakeholder notification
Upgrade Complete: Bond facet v2 deployed
- New version: 2
- All bond tokens updated automatically
- No user action required
Rollback Procedures
Scenario: Critical Bug in New Facet
Option 1: Deploy Previous Version Again
// Register old facet address again (creates new version)
const facetsToRegister = [
{
name: "BondFacet",
address: previousBondFacetAddress, // Old working version
resolverKey: BOND_RESOLVER_KEY,
},
// ... all other facets
];
await registerFacets(blr, facetsToRegister);
// Create new configuration using old facet
await createBondConfiguration(blr, facetAddresses);
Option 2: Pin Tokens to Previous Configuration
// If using versioned configurations, pin to previous version
// (Requires governance or admin action on token)
await bondToken.updateConfigurationVersion(previousVersion);
Option 3: Emergency Pause
// Pause affected facet functionality
const pauseFacet = await ethers.getContractAt("IPause", tokenAddress);
await pauseFacet.pause();
console.log("Token paused while investigating issue");
Best Practices
Version Management
- Document every version: Maintain changelog with version details
- Semantic versioning: Follow semver for major/minor/patch
- Git tags: Tag repository with version numbers
- Configuration matrix: Track which tokens use which versions
Testing Standards
- 100% test coverage: For modified facets
- Integration tests: Test facet interactions
- Gas benchmarks: Compare before/after upgrade
- Load testing: For performance-critical upgrades
Communication
- Advance notice: 7+ days for major upgrades
- Detailed changelogs: Publish comprehensive notes
- Migration guides: If breaking changes
- Support availability: During and after upgrade
Monitoring
- Event tracking: Monitor upgrade-related events
- Error alerts: Set up alerts for failures
- Performance metrics: Track gas usage changes
- User impact: Monitor transaction success rates
Security
- Multi-sig for production: Require multiple approvals
- Time locks: Enforce delay between proposal and execution
- Emergency procedures: Document emergency contacts
- Insurance: Consider smart contract insurance
Documentation
Every upgrade should document:
- Version number and date
- Changes made (bug fixes, features, optimizations)
- Breaking changes
- Migration steps (if applicable)
- Test results
- Deployment transactions
Example Upgrade Checklist
# Upgrade Checklist: BondFacet v1.2.3
## Pre-Upgrade
- [x] Code review completed (2025-01-10)
- [x] Unit tests: 100% coverage
- [x] Integration tests: Passed
- [x] Security audit: Completed (no critical issues)
- [x] Testnet deployment: Successful (0.0.12345678)
- [x] Gas analysis: -5% reduction in redemption
- [x] Stakeholder notification: Sent (2025-01-08)
## Deployment
- [x] Deployed new BondFacet: 0.0.12345679
- [x] Registered facets: Version 3
- [x] Created Bond config v3: Success
- [x] Transaction hash: 0xabcd...
## Post-Deployment
- [x] Verified BLR version: 3
- [x] Tested live bond token: Passed
- [x] Monitored for 24h: No issues
- [x] Documentation updated
- [x] Final notification sent: 2025-01-15
## Rollback Plan
- Previous BondFacet: 0.0.12345670
- Previous config version: 2
- Emergency contact: admin@example.com
Related Documentation
Support
For upgrade assistance:
- GitHub Issues: asset-tokenization-studio/issues
- Emergency Contact: (Include production support contact)