123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- const { ethers } = require('hardhat');
- const { expect } = require('chai');
- const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
- const { generators } = require('../helpers/random');
- const shouldBehaveLikeClone = require('./Clones.behaviour');
- const cloneInitCode = (instance, args = undefined) =>
- args
- ? ethers.concat([
- '0x61',
- ethers.toBeHex(0x2d + ethers.getBytes(args).length, 2),
- '0x3d81600a3d39f3363d3d373d3d3d363d73',
- instance.target ?? instance.address ?? instance,
- '0x5af43d82803e903d91602b57fd5bf3',
- args,
- ])
- : ethers.concat([
- '0x3d602d80600a3d3981f3363d3d373d3d3d363d73',
- instance.target ?? instance.address ?? instance,
- '0x5af43d82803e903d91602b57fd5bf3',
- ]);
- async function fixture() {
- const [deployer] = await ethers.getSigners();
- const factory = await ethers.deployContract('$Clones');
- const implementation = await ethers.deployContract('DummyImplementation');
- const newClone =
- args =>
- async (opts = {}) => {
- const clone = await (
- args
- ? factory.$cloneWithImmutableArgs.staticCall(implementation, args)
- : factory.$clone.staticCall(implementation)
- ).then(address => implementation.attach(address));
- const tx = await (args
- ? opts.deployValue
- ? factory.$cloneWithImmutableArgs(implementation, args, ethers.Typed.uint256(opts.deployValue))
- : factory.$cloneWithImmutableArgs(implementation, args)
- : opts.deployValue
- ? factory.$clone(implementation, ethers.Typed.uint256(opts.deployValue))
- : factory.$clone(implementation));
- if (opts.initData || opts.initValue) {
- await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' });
- }
- return Object.assign(clone, { deploymentTransaction: () => tx });
- };
- const newCloneDeterministic =
- args =>
- async (opts = {}) => {
- const salt = opts.salt ?? ethers.randomBytes(32);
- const clone = await (
- args
- ? factory.$cloneDeterministicWithImmutableArgs.staticCall(implementation, args, salt)
- : factory.$cloneDeterministic.staticCall(implementation, salt)
- ).then(address => implementation.attach(address));
- const tx = await (args
- ? opts.deployValue
- ? factory.$cloneDeterministicWithImmutableArgs(
- implementation,
- args,
- salt,
- ethers.Typed.uint256(opts.deployValue),
- )
- : factory.$cloneDeterministicWithImmutableArgs(implementation, args, salt)
- : opts.deployValue
- ? factory.$cloneDeterministic(implementation, salt, ethers.Typed.uint256(opts.deployValue))
- : factory.$cloneDeterministic(implementation, salt));
- if (opts.initData || opts.initValue) {
- await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' });
- }
- return Object.assign(clone, { deploymentTransaction: () => tx });
- };
- return { deployer, factory, implementation, newClone, newCloneDeterministic };
- }
- describe('Clones', function () {
- beforeEach(async function () {
- Object.assign(this, await loadFixture(fixture));
- });
- for (const args of [undefined, '0x', '0x11223344']) {
- describe(args ? `with immutable args: ${args}` : 'without immutable args', function () {
- describe('clone', function () {
- beforeEach(async function () {
- this.createClone = this.newClone(args);
- });
- shouldBehaveLikeClone();
- it('get immutable arguments', async function () {
- const instance = await this.createClone();
- expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x');
- });
- });
- describe('cloneDeterministic', function () {
- beforeEach(async function () {
- this.createClone = this.newCloneDeterministic(args);
- });
- shouldBehaveLikeClone();
- it('get immutable arguments', async function () {
- const instance = await this.createClone();
- expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x');
- });
- it('revert if address already used', async function () {
- const salt = ethers.randomBytes(32);
- const deployClone = () =>
- args
- ? this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt)
- : this.factory.$cloneDeterministic(this.implementation, salt);
- // deploy once
- await expect(deployClone()).to.not.be.reverted;
- // deploy twice
- await expect(deployClone()).to.be.revertedWithCustomError(this.factory, 'FailedDeployment');
- });
- it('address prediction', async function () {
- const salt = ethers.randomBytes(32);
- const expected = ethers.getCreate2Address(
- this.factory.target,
- salt,
- ethers.keccak256(cloneInitCode(this.implementation, args)),
- );
- if (args) {
- const predicted = await this.factory.$predictDeterministicAddressWithImmutableArgs(
- this.implementation,
- args,
- salt,
- );
- expect(predicted).to.equal(expected);
- await expect(this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt))
- .to.emit(this.factory, 'return$cloneDeterministicWithImmutableArgs_address_bytes_bytes32')
- .withArgs(predicted);
- } else {
- const predicted = await this.factory.$predictDeterministicAddress(this.implementation, salt);
- expect(predicted).to.equal(expected);
- await expect(this.factory.$cloneDeterministic(this.implementation, salt))
- .to.emit(this.factory, 'return$cloneDeterministic_address_bytes32')
- .withArgs(predicted);
- }
- });
- });
- });
- }
- it('EIP-170 limit on immutable args', async function () {
- // EIP-170 limits the contract code size to 0x6000
- // This limits the length of immutable args to 0x5fd3
- const args = generators.hexBytes(0x5fd4);
- const salt = ethers.randomBytes(32);
- await expect(
- this.factory.$predictDeterministicAddressWithImmutableArgs(this.implementation, args, salt),
- ).to.be.revertedWithCustomError(this.factory, 'CloneArgumentsTooLong');
- await expect(this.factory.$cloneWithImmutableArgs(this.implementation, args)).to.be.revertedWithCustomError(
- this.factory,
- 'CloneArgumentsTooLong',
- );
- });
- });
|