Clones.test.js 6.5 KB

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