Create2.test.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
  5. const { RevertType } = require('../helpers/enums');
  6. async function fixture() {
  7. const [deployer, other] = await ethers.getSigners();
  8. const factory = await ethers.deployContract('$Create2');
  9. // Bytecode for deploying a contract that includes a constructor.
  10. // We use a vesting wallet, with 3 constructor arguments.
  11. const constructorByteCode = await ethers
  12. .getContractFactory('VestingWallet')
  13. .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([other.address, 0n, 0n])]));
  14. // Bytecode for deploying a contract that has no constructor log.
  15. // Here we use the Create2 helper factory.
  16. const constructorLessBytecode = await ethers
  17. .getContractFactory('$Create2')
  18. .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([])]));
  19. const mockFactory = await ethers.getContractFactory('ConstructorMock');
  20. return { deployer, other, factory, constructorByteCode, constructorLessBytecode, mockFactory };
  21. }
  22. describe('Create2', function () {
  23. const salt = 'salt message';
  24. const saltHex = ethers.id(salt);
  25. beforeEach(async function () {
  26. Object.assign(this, await loadFixture(fixture));
  27. });
  28. describe('computeAddress', function () {
  29. it('computes the correct contract address', async function () {
  30. const onChainComputed = await this.factory.$computeAddress(saltHex, ethers.keccak256(this.constructorByteCode));
  31. const offChainComputed = ethers.getCreate2Address(
  32. this.factory.target,
  33. saltHex,
  34. ethers.keccak256(this.constructorByteCode),
  35. );
  36. expect(onChainComputed).to.equal(offChainComputed);
  37. });
  38. it('computes the correct contract address with deployer', async function () {
  39. const onChainComputed = await this.factory.$computeAddress(
  40. saltHex,
  41. ethers.keccak256(this.constructorByteCode),
  42. ethers.Typed.address(this.deployer),
  43. );
  44. const offChainComputed = ethers.getCreate2Address(
  45. this.deployer.address,
  46. saltHex,
  47. ethers.keccak256(this.constructorByteCode),
  48. );
  49. expect(onChainComputed).to.equal(offChainComputed);
  50. });
  51. });
  52. describe('deploy', function () {
  53. it('deploys a contract without constructor', async function () {
  54. const offChainComputed = ethers.getCreate2Address(
  55. this.factory.target,
  56. saltHex,
  57. ethers.keccak256(this.constructorLessBytecode),
  58. );
  59. await expect(this.factory.$deploy(0n, saltHex, this.constructorLessBytecode))
  60. .to.emit(this.factory, 'return$deploy')
  61. .withArgs(offChainComputed);
  62. expect(this.constructorLessBytecode).to.include((await ethers.provider.getCode(offChainComputed)).slice(2));
  63. });
  64. it('deploys a contract with constructor arguments', async function () {
  65. const offChainComputed = ethers.getCreate2Address(
  66. this.factory.target,
  67. saltHex,
  68. ethers.keccak256(this.constructorByteCode),
  69. );
  70. await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode))
  71. .to.emit(this.factory, 'return$deploy')
  72. .withArgs(offChainComputed);
  73. const instance = await ethers.getContractAt('VestingWallet', offChainComputed);
  74. expect(await instance.owner()).to.equal(this.other);
  75. });
  76. it('deploys a contract with funds deposited in the factory', async function () {
  77. const value = 10n;
  78. await this.deployer.sendTransaction({ to: this.factory, value });
  79. const offChainComputed = ethers.getCreate2Address(
  80. this.factory.target,
  81. saltHex,
  82. ethers.keccak256(this.constructorByteCode),
  83. );
  84. expect(await ethers.provider.getBalance(this.factory)).to.equal(value);
  85. expect(await ethers.provider.getBalance(offChainComputed)).to.equal(0n);
  86. await expect(this.factory.$deploy(value, saltHex, this.constructorByteCode))
  87. .to.emit(this.factory, 'return$deploy')
  88. .withArgs(offChainComputed);
  89. expect(await ethers.provider.getBalance(this.factory)).to.equal(0n);
  90. expect(await ethers.provider.getBalance(offChainComputed)).to.equal(value);
  91. });
  92. it('fails deploying a contract in an existent address', async function () {
  93. await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.emit(this.factory, 'return$deploy');
  94. await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.be.revertedWithCustomError(
  95. this.factory,
  96. 'FailedDeployment',
  97. );
  98. });
  99. it('fails deploying a contract if the bytecode length is zero', async function () {
  100. await expect(this.factory.$deploy(0n, saltHex, '0x')).to.be.revertedWithCustomError(
  101. this.factory,
  102. 'Create2EmptyBytecode',
  103. );
  104. });
  105. it('fails deploying a contract if factory contract does not have sufficient balance', async function () {
  106. await expect(this.factory.$deploy(1n, saltHex, this.constructorByteCode))
  107. .to.be.revertedWithCustomError(this.factory, 'InsufficientBalance')
  108. .withArgs(0n, 1n);
  109. });
  110. describe('reverts error thrown during contract creation', function () {
  111. it('bubbles up without message', async function () {
  112. await expect(
  113. this.factory.$deploy(
  114. 0n,
  115. saltHex,
  116. ethers.concat([
  117. this.mockFactory.bytecode,
  118. this.mockFactory.interface.encodeDeploy([RevertType.RevertWithoutMessage]),
  119. ]),
  120. ),
  121. ).to.be.revertedWithCustomError(this.factory, 'FailedDeployment');
  122. });
  123. it('bubbles up message', async function () {
  124. await expect(
  125. this.factory.$deploy(
  126. 0n,
  127. saltHex,
  128. ethers.concat([
  129. this.mockFactory.bytecode,
  130. this.mockFactory.interface.encodeDeploy([RevertType.RevertWithMessage]),
  131. ]),
  132. ),
  133. ).to.be.revertedWith('ConstructorMock: reverting');
  134. });
  135. it('bubbles up custom error', async function () {
  136. await expect(
  137. this.factory.$deploy(
  138. 0n,
  139. saltHex,
  140. ethers.concat([
  141. this.mockFactory.bytecode,
  142. this.mockFactory.interface.encodeDeploy([RevertType.RevertWithCustomError]),
  143. ]),
  144. ),
  145. ).to.be.revertedWithCustomError({ interface: this.mockFactory.interface }, 'CustomError');
  146. });
  147. it('bubbles up panic', async function () {
  148. await expect(
  149. this.factory.$deploy(
  150. 0n,
  151. saltHex,
  152. ethers.concat([this.mockFactory.bytecode, this.mockFactory.interface.encodeDeploy([RevertType.Panic])]),
  153. ),
  154. ).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO);
  155. });
  156. });
  157. });
  158. });