|
| 1 | +// SPDX-License-Identifier: UNLICENSED |
| 2 | +pragma solidity ^0.8.13; |
| 3 | + |
| 4 | +import {Script, console} from "forge-std/Script.sol"; |
| 5 | +import {PythLazer} from "../src/PythLazer.sol"; |
| 6 | +import {ICreateX} from "createx/ICreateX.sol"; |
| 7 | +import {CreateX} from "createx/CreateX.sol"; |
| 8 | +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; |
| 9 | + |
| 10 | +// This script deploys the PythLazer proxy and implementation contract using |
| 11 | +// CreateX's contract factory to a deterministic address. Having deterministic |
| 12 | +// addresses make it easier for users to access our contracts and also helps in |
| 13 | +// making this deployment script idempotent without maintaining any state. |
| 14 | +// |
| 15 | +// CreateX enables us to deploy the contract deterministically to the same |
| 16 | +// address on any EVM chain using various methods. We use the deployer address |
| 17 | +// in salt to protect the deployment addresses from being redeployed by other |
| 18 | +// wallets so the addresses we use be fully deterministic. We use `create2` to |
| 19 | +// deploy the implementation contracts (to have a single address per |
| 20 | +// implementation) and `create3` to deploy the proxies (to avoid changing |
| 21 | +// addresses if our proxy contract changes, which might sound impossible, but |
| 22 | +// can easily happen when you change the optimisation or the solc version!). |
| 23 | +// Below you will find more explanation on what these methods. |
| 24 | +// |
| 25 | +// a `create` opcode (which typical deployment uses) on the EVM would deploy a |
| 26 | +// contract to an address that is a hash of the caller address, and the tx |
| 27 | +// nonce. Nonce is the tx number and can't be set and therefore it's not easily |
| 28 | +// possible to achieve deterministic deployments (unless we somehow make sure |
| 29 | +// the deployer never sends transactions prior deployment). That's why there is |
| 30 | +// a `create2` opcode that allows us to deploy contracts to an address that is |
| 31 | +// a hash of the caller address, a salt, and the contract bytecode. The salt is |
| 32 | +// a random number that we can control and therefore we can deploy contracts |
| 33 | +// deterministically. The problem with `create2` is that it is the bytecode |
| 34 | +// affects the address and therefore we can't deploy different contracts to the |
| 35 | +// same address. That's where the idea of `create3` comes in. `create3` is a |
| 36 | +// wrapper around create2 and create that achieves it. It deploys a minimal |
| 37 | +// fixed-code proxy first using create2 (which will be in a deterministic |
| 38 | +// address) and then we call the proxy using the new contract code. The proxy |
| 39 | +// then calls `create` to deploy the new contract and since it's the first |
| 40 | +// transaction of the proxy the nonce will be 0 and the address will be |
| 41 | +// deterministic. |
| 42 | +// |
| 43 | +// Maybe you are wondering why factory contracts are needed. We need them to |
| 44 | +// call create2 and since the caller address matters we need a factory contract |
| 45 | +// at a fixed address and that's what CreateX is for. CreateX has a single |
| 46 | +// signed transaction to deploy it in any network (and hopefully the key that |
| 47 | +// it uses is destroyed!). |
| 48 | +contract PythLazerDeployScript is Script { |
| 49 | + // The address of the wallet calling this script. This is used to protect |
| 50 | + // the deployment addresses from being redeployed by other wallets. |
| 51 | + address constant deployer = 0x78357316239040e19fC823372cC179ca75e64b81; |
| 52 | + |
| 53 | + // CreateX is a Contract Factory that provides multiple deployment solutions that |
| 54 | + // we use for deterministic deployments of our contract. It is universally deployed |
| 55 | + // at this address and can be deployed if it is not already deployed. |
| 56 | + ICreateX constant createX = |
| 57 | + ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); |
| 58 | + |
| 59 | + function contractDeployed(address addr) public view returns (bool) { |
| 60 | + return addr.code.length > 0; |
| 61 | + } |
| 62 | + |
| 63 | + function setUp() public view { |
| 64 | + if (!contractDeployed(address(createX))) { |
| 65 | + revert( |
| 66 | + "CreateX not deployed. Deploy it following the instructions in https://github.com/pcaversaccio/createx" |
| 67 | + ); |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + // Generate a salt that is safeguarded against redeployments by other |
| 72 | + // deployers. CreateX has a safeguard mechanism that based on the following |
| 73 | + // salt format creates a guardedSalt that no one else can get to. |
| 74 | + // |
| 75 | + // @param seed: A random number to be used as the salt for deployment |
| 76 | + function generateSalt( |
| 77 | + bytes11 seed |
| 78 | + ) public pure returns (bytes32 salt, bytes32 guardedSalt) { |
| 79 | + salt = bytes32( |
| 80 | + abi.encodePacked( |
| 81 | + deployer, // Deployment protection by our deployer |
| 82 | + uint8(0), // No crosschain replay protection |
| 83 | + seed |
| 84 | + ) |
| 85 | + ); |
| 86 | + |
| 87 | + // Mimic CreateX's guardedSalt mechanism only for our type of salt. Couldn't use CreateX's |
| 88 | + // _guard function because it is internal and inheriting it results in conflict with Script. |
| 89 | + guardedSalt = keccak256( |
| 90 | + abi.encode(bytes32(uint256(uint160(deployer))), salt) |
| 91 | + ); |
| 92 | + } |
| 93 | + |
| 94 | + // Deploy the implementation of the contract. This script is idempotent and |
| 95 | + // will return if the implementation is already deployed. |
| 96 | + // |
| 97 | + // @param seed: A random number to be used as the salt for deployment |
| 98 | + function deployImplementation(bytes11 seed) public returns (address addr) { |
| 99 | + (bytes32 salt, bytes32 guardedSalt) = generateSalt(seed); |
| 100 | + address implAddr = createX.computeCreate2Address({ |
| 101 | + salt: guardedSalt, |
| 102 | + initCodeHash: keccak256( |
| 103 | + abi.encodePacked(type(PythLazer).creationCode) |
| 104 | + ) |
| 105 | + }); |
| 106 | + |
| 107 | + if (contractDeployed(implAddr)) { |
| 108 | + console.log("Implementation already deployed at: %s", implAddr); |
| 109 | + return implAddr; |
| 110 | + } |
| 111 | + |
| 112 | + console.log("Deploying implementation... %s", implAddr); |
| 113 | + |
| 114 | + vm.startBroadcast(); |
| 115 | + addr = createX.deployCreate2({ |
| 116 | + salt: salt, |
| 117 | + initCode: abi.encodePacked(type(PythLazer).creationCode) |
| 118 | + }); |
| 119 | + vm.stopBroadcast(); |
| 120 | + |
| 121 | + console.log("Deployed implementation to: %s", addr); |
| 122 | + |
| 123 | + require( |
| 124 | + addr == implAddr, |
| 125 | + "Implementation address mismatch due to mismatched deployer." |
| 126 | + ); |
| 127 | + |
| 128 | + return addr; |
| 129 | + } |
| 130 | + |
| 131 | + function deployProxy( |
| 132 | + bytes11 seed, |
| 133 | + address impl |
| 134 | + ) public returns (address addr) { |
| 135 | + (bytes32 salt, bytes32 guardedSalt) = generateSalt(seed); |
| 136 | + address proxyAddr = createX.computeCreate3Address({salt: guardedSalt}); |
| 137 | + |
| 138 | + if (contractDeployed(proxyAddr)) { |
| 139 | + console.log("Proxy already deployed at: %s", proxyAddr); |
| 140 | + return proxyAddr; |
| 141 | + } |
| 142 | + |
| 143 | + console.log("Deploying proxy... %s", proxyAddr); |
| 144 | + |
| 145 | + // Set the top authority to the deployer for the time being |
| 146 | + address topAuthority = deployer; |
| 147 | + |
| 148 | + vm.startBroadcast(); |
| 149 | + addr = createX.deployCreate3({ |
| 150 | + salt: salt, |
| 151 | + initCode: abi.encodePacked( |
| 152 | + type(ERC1967Proxy).creationCode, |
| 153 | + abi.encode( |
| 154 | + impl, |
| 155 | + abi.encodeWithSignature("initialize(address)", topAuthority) |
| 156 | + ) |
| 157 | + ) |
| 158 | + }); |
| 159 | + vm.stopBroadcast(); |
| 160 | + |
| 161 | + console.log("Deployed proxy to: %s", addr); |
| 162 | + |
| 163 | + require( |
| 164 | + addr == proxyAddr, |
| 165 | + "Proxy address mismatch due to mismatched deployer." |
| 166 | + ); |
| 167 | + |
| 168 | + return addr; |
| 169 | + } |
| 170 | + |
| 171 | + function run() public { |
| 172 | + address impl = deployImplementation("lazer:impl"); |
| 173 | + deployProxy("lazer:proxy", impl); |
| 174 | + } |
| 175 | +} |
0 commit comments