Skip to main content

Upgrades

This guide outlines the upgrade mechanisms available in Meta Contract and how to safely implement contract upgrades using Foundry.

Table of Contents

  1. Understanding Meta Contract Upgrades
  2. Preparing for an Upgrade
  3. Implementing the Upgrade
  1. Deploying the Upgrade
  2. Post-Upgrade Verification

Understanding Meta Contract Upgrades

Meta Contract uses a unique upgradeability system that allows for:

  1. Function-Level Upgrades: Ability to upgrade specific functions without affecting the entire contract.
  2. Storage Layout Preservation: Ensures that existing storage is not corrupted during upgrades.
  3. Upgrade Access Control: Restricts who can perform upgrades to maintain security.

Preparing for an Upgrade

  1. Identify the Need: Determine which functions or components need upgrading.
  2. Design the Upgrade: Plan the changes, ensuring they don't conflict with existing storage layout.
  3. 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:

  1. 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 "@mc-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);
    }
    }
  2. Create an upgrade script:

    // scripts/Upgrade.s.sol

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;

    import {MCScript} from "@mc-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

  1. 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>
  2. Run the script with sending transactions

    forge script UpgradeVoteToV2Script --rpc-url <YOUR_RPC_URL> --broadcast

Post-Upgrade Verification

  1. Functional Testing: Verify that new functions work and existing functions haven't been adversely affected.
  2. Storage Integrity: Ensure that existing data in the contract has been preserved.
  3. Event Monitoring: Check for successful upgrade events emitted by the dictionary contract.
  4. 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.