Clones.test.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { generators } = require('../helpers/random');
  5. const shouldBehaveLikeClone = require('./Clones.behaviour');
  6. const cloneInitCode = (instance, args = undefined) =>
  7. args
  8. ? ethers.concat([
  9. '0x61',
  10. ethers.toBeHex(0x2d + ethers.getBytes(args).length, 2),
  11. '0x3d81600a3d39f3363d3d373d3d3d363d73',
  12. instance.target ?? instance.address ?? instance,
  13. '0x5af43d82803e903d91602b57fd5bf3',
  14. args,
  15. ])
  16. : ethers.concat([
  17. '0x3d602d80600a3d3981f3363d3d373d3d3d363d73',
  18. instance.target ?? instance.address ?? instance,
  19. '0x5af43d82803e903d91602b57fd5bf3',
  20. ]);
  21. async function fixture() {
  22. const [deployer] = await ethers.getSigners();
  23. const factory = await ethers.deployContract('$Clones');
  24. const implementation = await ethers.deployContract('DummyImplementation');
  25. const newClone =
  26. args =>
  27. async (opts = {}) => {
  28. const clone = await (
  29. args
  30. ? factory.$cloneWithImmutableArgs.staticCall(implementation, args)
  31. : factory.$clone.staticCall(implementation)
  32. ).then(address => implementation.attach(address));
  33. const tx = await (args
  34. ? opts.deployValue
  35. ? factory.$cloneWithImmutableArgs(implementation, args, ethers.Typed.uint256(opts.deployValue))
  36. : factory.$cloneWithImmutableArgs(implementation, args)
  37. : opts.deployValue
  38. ? factory.$clone(implementation, ethers.Typed.uint256(opts.deployValue))
  39. : factory.$clone(implementation));
  40. if (opts.initData || opts.initValue) {
  41. await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' });
  42. }
  43. return Object.assign(clone, { deploymentTransaction: () => tx });
  44. };
  45. const newCloneDeterministic =
  46. args =>
  47. async (opts = {}) => {
  48. const salt = opts.salt ?? ethers.randomBytes(32);
  49. const clone = await (
  50. args
  51. ? factory.$cloneDeterministicWithImmutableArgs.staticCall(implementation, args, salt)
  52. : factory.$cloneDeterministic.staticCall(implementation, salt)
  53. ).then(address => implementation.attach(address));
  54. const tx = await (args
  55. ? opts.deployValue
  56. ? factory.$cloneDeterministicWithImmutableArgs(
  57. implementation,
  58. args,
  59. salt,
  60. ethers.Typed.uint256(opts.deployValue),
  61. )
  62. : factory.$cloneDeterministicWithImmutableArgs(implementation, args, salt)
  63. : opts.deployValue
  64. ? factory.$cloneDeterministic(implementation, salt, ethers.Typed.uint256(opts.deployValue))
  65. : factory.$cloneDeterministic(implementation, salt));
  66. if (opts.initData || opts.initValue) {
  67. await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' });
  68. }
  69. return Object.assign(clone, { deploymentTransaction: () => tx });
  70. };
  71. return { deployer, factory, implementation, newClone, newCloneDeterministic };
  72. }
  73. describe('Clones', function () {
  74. beforeEach(async function () {
  75. Object.assign(this, await loadFixture(fixture));
  76. });
  77. for (const args of [undefined, '0x', '0x11223344']) {
  78. describe(args ? `with immutable args: ${args}` : 'without immutable args', function () {
  79. describe('clone', function () {
  80. beforeEach(async function () {
  81. this.createClone = this.newClone(args);
  82. });
  83. shouldBehaveLikeClone();
  84. it('get immutable arguments', async function () {
  85. const instance = await this.createClone();
  86. expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x');
  87. });
  88. });
  89. describe('cloneDeterministic', function () {
  90. beforeEach(async function () {
  91. this.createClone = this.newCloneDeterministic(args);
  92. });
  93. shouldBehaveLikeClone();
  94. it('get immutable arguments', async function () {
  95. const instance = await this.createClone();
  96. expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x');
  97. });
  98. it('revert if address already used', async function () {
  99. const salt = ethers.randomBytes(32);
  100. const deployClone = () =>
  101. args
  102. ? this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt)
  103. : this.factory.$cloneDeterministic(this.implementation, salt);
  104. // deploy once
  105. await expect(deployClone()).to.not.be.reverted;
  106. // deploy twice
  107. await expect(deployClone()).to.be.revertedWithCustomError(this.factory, 'FailedDeployment');
  108. });
  109. it('address prediction', async function () {
  110. const salt = ethers.randomBytes(32);
  111. const expected = ethers.getCreate2Address(
  112. this.factory.target,
  113. salt,
  114. ethers.keccak256(cloneInitCode(this.implementation, args)),
  115. );
  116. if (args) {
  117. const predicted = await this.factory.$predictDeterministicAddressWithImmutableArgs(
  118. this.implementation,
  119. args,
  120. salt,
  121. );
  122. expect(predicted).to.equal(expected);
  123. await expect(this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt))
  124. .to.emit(this.factory, 'return$cloneDeterministicWithImmutableArgs_address_bytes_bytes32')
  125. .withArgs(predicted);
  126. } else {
  127. const predicted = await this.factory.$predictDeterministicAddress(this.implementation, salt);
  128. expect(predicted).to.equal(expected);
  129. await expect(this.factory.$cloneDeterministic(this.implementation, salt))
  130. .to.emit(this.factory, 'return$cloneDeterministic_address_bytes32')
  131. .withArgs(predicted);
  132. }
  133. });
  134. });
  135. });
  136. }
  137. it('EIP-170 limit on immutable args', async function () {
  138. // EIP-170 limits the contract code size to 0x6000
  139. // This limits the length of immutable args to 0x5fd3
  140. const args = generators.hexBytes(0x5fd4);
  141. const salt = ethers.randomBytes(32);
  142. await expect(
  143. this.factory.$predictDeterministicAddressWithImmutableArgs(this.implementation, args, salt),
  144. ).to.be.revertedWithCustomError(this.factory, 'CloneArgumentsTooLong');
  145. await expect(this.factory.$cloneWithImmutableArgs(this.implementation, args)).to.be.revertedWithCustomError(
  146. this.factory,
  147. 'CloneArgumentsTooLong',
  148. );
  149. });
  150. });