PythLazerDeploy.s.sol 8.0 KB

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