PythLazerDeploy.s.sol 7.2 KB

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