Upgrades
This guide outlines the upgrade mechanisms available in Meta Contract and how to safely implement contract upgrades using Foundry.
Table of Contents
Understanding Meta Contract Upgrades
Meta Contract uses a unique upgradeability system that allows for:
- Function-Level Upgrades: Ability to upgrade specific functions without affecting the entire contract.
- Storage Layout Preservation: Ensures that existing storage is not corrupted during upgrades.
- Upgrade Access Control: Restricts who can perform upgrades to maintain security.
Preparing for an Upgrade
- Identify the Need: Determine which functions or components need upgrading.
- Design the Upgrade: Plan the changes, ensuring they don't conflict with existing storage layout.
- Create New Implementation: Write the new implementation contract with the upgraded functions.
Implementing the Upgrade
Use Foundry and Meta Contract's DevKit to create and test the upgrade:
-
Create a new implementation contract:
// File: src/voting-system/functions/VoteV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Storage, Schema} from "bundle/voting-system/storages/Storage.sol";
contract VoteV2 {
// Implementation
function vote(uint256 proposalId) public {
Schema.Proposal storage $proposal = Storage.Proposals().getProposal(proposalId);
// Check if the proposal exists
if (!$proposal.exists()) revert VotingSystemErrors.ProposalNotFound();
// Check if the voter has already voted
if ($proposal.hasVoted(msg.sender)) revert VotingSystemErrors.AlreadyVoted();
// Record the vote
$proposal.setVote(msg.sender, true);
// Emit event
emit VoteCast(proposalId, msg.sender);
}
}
// Unit Testing
import {MCTest} from "@devkit/Flattened.sol";
import {VotingSystemErrors} from "bundle/voting-system/interfaces/VotingSystemErrors.sol";
import {VotingSystemEvents} from "bundle/voting-system/interfaces/VotingSystemEvents.sol";
contract VoteV2Test is MCTest {
function setUp() public {
address _voteV2 = address(new VoteV2());
_use(Vote.vote.selector, _voteV2);
}
function test_vote_success() public {
// Setup: Create a proposal
uint256 proposalId = 1;
Storage.Proposals().createProposal(proposalId, Schema.Proposal({exists: true, ...}));
// Test: Expect VoteCast event
vm.expectEmit();
emit VoteCast(proposalId, address(this));
// Action: Cast a vote
Vote(target).vote(proposalId);
// Assert: Check that the vote was recorded
assertTrue(Storage.Proposals().getProposal(proposalId).hasVoted(address(this)));
}
function test_vote_alreadyVoted() public {
// Setup: Create a proposal and record a vote
uint256 proposalId = 1;
Storage.Proposals().createProposal(proposalId, Schema.Proposal({exists: true, ...}));
Storage.Proposals().setVote(proposalId, address(this), true);
// Test: Expect revert
vm.expectRevert(VotingSystemErrors.AlreadyVoted.selector);
// Action: Attempt to vote again
Vote(target).vote(proposalId);
}
} -
Create an upgrade script:
// scripts/Upgrade.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {MCScript} from "@devkit/Flattened.sol";
import {VotingSystemUpgrader} from "script/VotingSystemUpgrader.sol";
contract UpgradeVoteToV2Script is MCScript {
function run() public startBroadcastWith("DEPLOYER_PRIV_KEY") {
address votingSystem = vm.envAddress("VOTING_SYSTEM_PROXY_ADDR");
VotingSystemUpgrader.upgradeVoteToV2(mc, votingSystem);
}
}// script/VotingSystemUpgrader.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {VoteV2} from "bundle/voting-system/functions/VoteV2.sol";
import {VotingSystemFacade} from "bundle/voting-system/interfaces/VotingSystemFacade.sol";
library VotingSystemUpgrader {
/**
* @dev Upgrade the VotingSystem contract
* @param mc MCDevKit storage reference
* @return votingSystem Address of the deployed VotingSystem proxy
*/
function upgradeVoteToV2(MCDevKit storage mc, address votingSystem) internal {
Dictionary memory _dictionary = mc.loadDictionary("Dictionary", mc.getDictionaryAddress(votingSystem));
address _voteV2_ = address(new VoteV2());
_dictionary.set(VoteV2.vote.selector, _voteV2);
// Upgrade facade if needed
_dictionary.upgradeFacade(address(new VotingSystemFacade()));
}
}
Deploying the Upgrade
-
Run and check the result the upgrade script using Foundry in the local environment with on-chain state:
forge script UpgradeVoteToV2Script --rpc-url <YOUR_RPC_URL>
-
Run the script with sending transactions
forge script UpgradeVoteToV2Script --rpc-url <YOUR_RPC_URL> --broadcast
Post-Upgrade Verification
- Functional Testing: Verify that new functions work and existing functions haven't been adversely affected.
- Storage Integrity: Ensure that existing data in the contract has been preserved.
- Event Monitoring: Check for successful upgrade events emitted by the dictionary contract.
- Documentation Update: Update project documentation to reflect the changes in the new version.
By following this guide, you can safely plan, implement, and deploy upgrades to your Meta Contract projects using Foundry.