|
46 | 46 |
|
47 | 47 | ## Enabling Claimability |
48 | 48 |
|
| 49 | +### By Calldata |
| 50 | + |
| 51 | +> [!IMPORTANT] |
| 52 | +> |
| 53 | +> - This step-by-step **assumes** that the claimable proxy contract **is already deployed** and that **is already paused**. If it is not paused, the first transaction should be to pause it using this calldata `cast calldata "pause()"`. |
| 54 | +> - This method **only** generates the necessary calldata to call the methods through transactions. It does **not** actually call the methods. This method is useful for copy-pasting the calldata into a multisig wallet. |
| 55 | +> - Steps 1, 2, and 4 can be batched into a single transaction in a multisig wallet. This multisig must be the `ClaimableAirdrop` contract owner. |
| 56 | +> - Step 3 must be done by the token distributor multisig as it is the one that has the tokens to be claimed. |
| 57 | +
|
| 58 | +> [!WARNING] |
| 59 | +> - Double-check the data you passing into the commands, any mistake can lead to undesired behavior. |
| 60 | +
|
| 61 | +1. Update the merkle root |
| 62 | + ``` |
| 63 | + // Example merkle_root = 0x97619aea42a289b94acc9fb98f5030576fa7449f1dd6701275815a6e99441927 |
| 64 | + cast calldata "updateMerkleRoot(bytes32)" <merkle_root> |
| 65 | + ``` |
| 66 | +2. Update the claim time limit |
| 67 | + ``` |
| 68 | + // Example timestamp = 2733427549 |
| 69 | + cast calldata "extendClaimPeriod(uint256)" <timestamp> |
| 70 | + ``` |
| 71 | +3. Approve the claimable proxy contract to spend the token from the distributor (_2.6B, taking into account the 18 decimals_) |
| 72 | + ``` |
| 73 | + // Example claim_proxy_address = 0x0234947ce63d1a5E731e5700b911FB32ec54C3c6 |
| 74 | + cast calldata "approve(address,uint256)" <claim_proxy_address> 2600000000000000000000000000 |
| 75 | + ``` |
| 76 | +4. Unpause the claimable contract (it is paused by default) |
| 77 | + ``` |
| 78 | + cast calldata "unpause()" |
| 79 | + ``` |
| 80 | + |
49 | 81 | ### Local |
50 | 82 |
|
51 | 83 | 1. Deploy the claimable contract as explained above. |
|
71 | 103 | ``` |
72 | 104 | make deploy-example MERKLE_ROOT=<claims-merkle-root> TIMESTAMP=2733427549 |
73 | 105 | ``` |
| 106 | + |
| 107 | +# Contract upgrade instructions |
| 108 | + |
| 109 | +To upgrade a contract, first make sure you pause the contract if it's not paused already (NOTE: the erc20 cannot be paused, the claim contract can though). Once that's done, clone the `aligned_layer` repo and go into the `claim_contracts` directory: |
| 110 | + |
| 111 | +> [!NOTE] |
| 112 | +> The ERC20 cannot be paused. Only the claimable airdrop proxy can be paused. |
| 113 | +
|
| 114 | +``` |
| 115 | +git clone [email protected]:yetanotherco/aligned_layer.git && cd aligned_layer/claim_contracts |
| 116 | +``` |
| 117 | + |
| 118 | +## Write the new contract implementation |
| 119 | + |
| 120 | +This implementation will most likely be a copy paste of the old implementation, only with one or few changes. In addition to that, there is one thing that MUST be done on this new contract: |
| 121 | + |
| 122 | +- Add a public `reinitalize function` with a `reinitializer()` modifier that takes in the next version number of the contract (the first version is `1`). As an example, if this is the first upgrade being done, you should add this function to the contract: |
| 123 | + |
| 124 | +> [!WARNING] |
| 125 | +> DO NOT UPDATE STORAGE VARIABLES IN THIS AND FOLLOWING UPGRADES, ONLY ADD NEW ONES. |
| 126 | +
|
| 127 | +```solidity |
| 128 | +function reinitialize() public reinitializer(2) { |
| 129 | + if (!paused()) { |
| 130 | + _pause(); |
| 131 | + } |
| 132 | + } |
| 133 | +``` |
| 134 | + |
| 135 | +Put the new implementation in a file inside the `src` directory with an appropriate name. |
| 136 | + |
| 137 | +## Write the deployment script |
| 138 | + |
| 139 | +Under the `script` directory, create a new forge script (with the `.s.sol` extension) with a name like `UpgradeContract.s.sol`, with this code in it: |
| 140 | + |
| 141 | +```solidity |
| 142 | +// SPDX-License-Identifier: MIT |
| 143 | +pragma solidity ^0.8.19; |
| 144 | +
|
| 145 | +import <path_to_upgrade_contract>; |
| 146 | +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; |
| 147 | +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; |
| 148 | +import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; |
| 149 | +import "forge-std/Script.sol"; |
| 150 | +import {Vm} from "forge-std/Vm.sol"; |
| 151 | +import {Utils} from "./Utils.sol"; |
| 152 | +
|
| 153 | +/// @notice Upgrade contract template |
| 154 | +contract UpgradeContract is Script { |
| 155 | + function run(string memory config) public { |
| 156 | + string memory root = vm.projectRoot(); |
| 157 | + string memory path = string.concat( |
| 158 | + root, |
| 159 | + "/script-config/config.", |
| 160 | + config, |
| 161 | + ".json" |
| 162 | + ); |
| 163 | + string memory config_json = vm.readFile(path); |
| 164 | +
|
| 165 | + address _currentContractProxy = stdJson.readAddress( |
| 166 | + config_json, |
| 167 | + ".contractProxy" |
| 168 | + ); |
| 169 | +
|
| 170 | + vm.broadcast(); |
| 171 | + <NameOfUpgradeContract> _newContract = new <NameOfUpgradeContract>(); |
| 172 | +
|
| 173 | + bytes memory _upgradeCalldata = abi.encodeCall( |
| 174 | + ProxyAdmin.upgradeAndCall, |
| 175 | + ( |
| 176 | + ITransparentUpgradeableProxy(_currentContractProxy), |
| 177 | + address(_newContract), |
| 178 | + abi.encodeCall(<NameOfUpgradeContract>.reinitialize, ()) |
| 179 | + ) |
| 180 | + ); |
| 181 | +
|
| 182 | + console.log( |
| 183 | + "Proxy Admin to call:", |
| 184 | + getAdminAddress(_currentContractProxy) |
| 185 | + ); |
| 186 | + console.log("Calldata of the transaction: "); |
| 187 | + console.logBytes(_upgradeCalldata); |
| 188 | + } |
| 189 | +
|
| 190 | + function getAdminAddress(address proxy) internal view returns (address) { |
| 191 | + address CHEATCODE_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; |
| 192 | + Vm vm = Vm(CHEATCODE_ADDRESS); |
| 193 | +
|
| 194 | + bytes32 adminSlot = vm.load(proxy, ERC1967Utils.ADMIN_SLOT); |
| 195 | + return address(uint160(uint256(adminSlot))); |
| 196 | + } |
| 197 | +} |
| 198 | +
|
| 199 | +``` |
| 200 | + |
| 201 | +then fill in the missing parts (between `<>` brackets), putting the path to the new contract code and the name of it. |
| 202 | + |
| 203 | +> [!IMPORTANT] |
| 204 | +> Remember to fill the missing parts (between `<>` brackets) in the script, putting the path to the new contract code and the name of it where needed. |
| 205 | +
|
| 206 | +Go into the `config.mainnet.json` file inside the `script-config` directory and fill in the following values: |
| 207 | + |
| 208 | +``` |
| 209 | +{ |
| 210 | + "foundation": "", |
| 211 | + "contractProxy": "" |
| 212 | + } |
| 213 | +
|
| 214 | +``` |
| 215 | + |
| 216 | +- `foundation` is the address of the foundation safe. |
| 217 | +- `contractProxy` is the address of the contract proxy to upgrade. |
| 218 | + |
| 219 | +## Run the deployment script |
| 220 | + |
| 221 | +Run the script with |
| 222 | + |
| 223 | +``` |
| 224 | +cd script && \ |
| 225 | + forge script <name_of_the_script.s.sol> \ |
| 226 | + --sig "run(string)" \ |
| 227 | + mainnet \ |
| 228 | + --private-key <private_key> \ |
| 229 | + --rpc-url <mainnet_rpc_url> \ |
| 230 | + --broadcast \ |
| 231 | + --verify \ |
| 232 | + --etherscan-api-key <etherscan_api_key> |
| 233 | +``` |
| 234 | + |
| 235 | +After running this script, it will show a message like this: |
| 236 | + |
| 237 | +``` |
| 238 | +Proxy Admin to call: 0xf447FD34D97317759777E242fF64cEAe9C58Bf9A |
| 239 | +Calldata of the transaction: |
| 240 | +0x9623609d0000000000000000000000000234947ce63d1a5e731e5700b911fb32ec54c3c3000000000000000000000000f7ac74dbc77e1afda093598c912a6b082dabc31a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000046c2eb35000000000000000000000000000000000000000000000000000000000 |
| 241 | +``` |
| 242 | + |
| 243 | +Go into the foundation safe, create a new transaction calling the proxy admin address shown in the message with the message's calldata. Done. |
0 commit comments