Create2.test.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. async function fixture() {
  5. const [deployer, other] = await ethers.getSigners();
  6. const factory = await ethers.deployContract('$Create2');
  7. // Bytecode for deploying a contract that includes a constructor.
  8. // We use a vesting wallet, with 3 constructor arguments.
  9. const constructorByteCode = await ethers
  10. .getContractFactory('VestingWallet')
  11. .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([other.address, 0n, 0n])]));
  12. // Bytecode for deploying a contract that has no constructor log.
  13. // Here we use the Create2 helper factory.
  14. const constructorLessBytecode = await ethers
  15. .getContractFactory('$Create2')
  16. .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])]));
  17. return { deployer, other, factory, constructorByteCode, constructorLessBytecode };
  18. }
  19. describe('Create2', function () {
  20. const salt = 'salt message';
  21. const saltHex = ethers.id(salt);
  22. beforeEach(async function () {
  23. Object.assign(this, await loadFixture(fixture));
  24. });
  25. describe('computeAddress', function () {
  26. it('computes the correct contract address', async function () {
  27. const onChainComputed = await this.factory.$computeAddress(saltHex, ethers.keccak256(this.constructorByteCode));
  28. const offChainComputed = ethers.getCreate2Address(
  29. this.factory.target,
  30. saltHex,
  31. ethers.keccak256(this.constructorByteCode),
  32. );
  33. expect(onChainComputed).to.equal(offChainComputed);
  34. });
  35. it('computes the correct contract address with deployer', async function () {
  36. const onChainComputed = await this.factory.$computeAddress(
  37. saltHex,
  38. ethers.keccak256(this.constructorByteCode),
  39. ethers.Typed.address(this.deployer),
  40. );
  41. const offChainComputed = ethers.getCreate2Address(
  42. this.deployer.address,
  43. saltHex,
  44. ethers.keccak256(this.constructorByteCode),
  45. );
  46. expect(onChainComputed).to.equal(offChainComputed);
  47. });
  48. });
  49. describe('deploy', function () {
  50. it('deploys a contract without constructor', async function () {
  51. const offChainComputed = ethers.getCreate2Address(
  52. this.factory.target,
  53. saltHex,
  54. ethers.keccak256(this.constructorLessBytecode),
  55. );
  56. await expect(this.factory.$deploy(0n, saltHex, this.constructorLessBytecode))
  57. .to.emit(this.factory, 'return$deploy')
  58. .withArgs(offChainComputed);
  59. expect(this.constructorLessBytecode).to.include((await ethers.provider.getCode(offChainComputed)).slice(2));
  60. });
  61. it('deploys a contract with constructor arguments', async function () {
  62. const offChainComputed = ethers.getCreate2Address(
  63. this.factory.target,
  64. saltHex,
  65. ethers.keccak256(this.constructorByteCode),
  66. );
  67. await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode))
  68. .to.emit(this.factory, 'return$deploy')
  69. .withArgs(offChainComputed);
  70. const instance = await ethers.getContractAt('VestingWallet', offChainComputed);
  71. expect(await instance.owner()).to.equal(this.other);
  72. });
  73. it('deploys a contract with funds deposited in the factory', async function () {
  74. const value = 10n;
  75. await this.deployer.sendTransaction({ to: this.factory, value });
  76. const offChainComputed = ethers.getCreate2Address(
  77. this.factory.target,
  78. saltHex,
  79. ethers.keccak256(this.constructorByteCode),
  80. );
  81. expect(await ethers.provider.getBalance(this.factory)).to.equal(value);
  82. expect(await ethers.provider.getBalance(offChainComputed)).to.equal(0n);
  83. await expect(this.factory.$deploy(value, saltHex, this.constructorByteCode))
  84. .to.emit(this.factory, 'return$deploy')
  85. .withArgs(offChainComputed);
  86. expect(await ethers.provider.getBalance(this.factory)).to.equal(0n);
  87. expect(await ethers.provider.getBalance(offChainComputed)).to.equal(value);
  88. });
  89. it('fails deploying a contract in an existent address', async function () {
  90. await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.emit(this.factory, 'return$deploy');
  91. await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.be.revertedWithCustomError(
  92. this.factory,
  93. 'FailedDeployment',
  94. );
  95. });
  96. it('fails deploying a contract if the bytecode length is zero', async function () {
  97. await expect(this.factory.$deploy(0n, saltHex, '0x')).to.be.revertedWithCustomError(
  98. this.factory,
  99. 'Create2EmptyBytecode',
  100. );
  101. });
  102. it('fails deploying a contract if factory contract does not have sufficient balance', async function () {
  103. await expect(this.factory.$deploy(1n, saltHex, this.constructorByteCode))
  104. .to.be.revertedWithCustomError(this.factory, 'InsufficientBalance')
  105. .withArgs(0n, 1n);
  106. });
  107. });
  108. });