Преглед изворни кода

Migrate `AccessManager` tests to ethers (#4710)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Ernesto García пре 1 година
родитељ
комит
cf6ff90b6d

+ 1 - 1
contracts/mocks/AuthorityMock.sol

@@ -52,7 +52,7 @@ contract AuthorityNoResponse {
     function canCall(address /* caller */, address /* target */, bytes4 /* selector */) external view {}
 }
 
-contract AuthoritiyObserveIsConsuming {
+contract AuthorityObserveIsConsuming {
     event ConsumeScheduledOpCalled(address caller, bytes data, bytes4 isConsuming);
 
     function canCall(

+ 6 - 0
test/access/Ownable.test.js

@@ -13,6 +13,12 @@ describe('Ownable', function () {
     Object.assign(this, await loadFixture(fixture));
   });
 
+  it('emits ownership transfer events during construction', async function () {
+    await expect(await this.ownable.deploymentTransaction())
+      .to.emit(this.ownable, 'OwnershipTransferred')
+      .withArgs(ethers.ZeroAddress, this.owner.address);
+  });
+
   it('rejects zero address for initialOwner', async function () {
     await expect(ethers.deployContract('$Ownable', [ethers.ZeroAddress]))
       .to.be.revertedWithCustomError({ interface: this.ownable.interface }, 'OwnableInvalidOwner')

+ 76 - 73
test/access/manager/AccessManaged.test.js

@@ -1,142 +1,145 @@
-const { expectEvent, time, expectRevert } = require('@openzeppelin/test-helpers');
-const { selector } = require('../../helpers/methods');
-const { expectRevertCustomError } = require('../../helpers/customError');
-const {
-  time: { setNextBlockTimestamp },
-} = require('@nomicfoundation/hardhat-network-helpers');
+const { bigint: time } = require('../../helpers/time');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 const { impersonate } = require('../../helpers/account');
+const { ethers } = require('hardhat');
 
-const AccessManaged = artifacts.require('$AccessManagedTarget');
-const AccessManager = artifacts.require('$AccessManager');
+async function fixture() {
+  const [admin, roleMember, other] = await ethers.getSigners();
 
-const AuthoritiyObserveIsConsuming = artifacts.require('$AuthoritiyObserveIsConsuming');
+  const authority = await ethers.deployContract('$AccessManager', [admin]);
+  const managed = await ethers.deployContract('$AccessManagedTarget', [authority]);
 
-contract('AccessManaged', function (accounts) {
-  const [admin, roleMember, other] = accounts;
+  const anotherAuthority = await ethers.deployContract('$AccessManager', [admin]);
+  const authorityObserveIsConsuming = await ethers.deployContract('$AuthorityObserveIsConsuming');
 
+  await impersonate(authority.target);
+  const authorityAsSigner = await ethers.getSigner(authority.target);
+
+  return {
+    roleMember,
+    other,
+    authorityAsSigner,
+    authority,
+    managed,
+    authorityObserveIsConsuming,
+    anotherAuthority,
+  };
+}
+
+describe('AccessManaged', function () {
   beforeEach(async function () {
-    this.authority = await AccessManager.new(admin);
-    this.managed = await AccessManaged.new(this.authority.address);
+    Object.assign(this, await loadFixture(fixture));
   });
 
   it('sets authority and emits AuthorityUpdated event during construction', async function () {
-    await expectEvent.inConstruction(this.managed, 'AuthorityUpdated', {
-      authority: this.authority.address,
-    });
-    expect(await this.managed.authority()).to.eq(this.authority.address);
+    await expect(await this.managed.deploymentTransaction())
+      .to.emit(this.managed, 'AuthorityUpdated')
+      .withArgs(this.authority.target);
   });
 
   describe('restricted modifier', function () {
-    const method = 'fnRestricted()';
-
     beforeEach(async function () {
-      this.selector = selector(method);
-      this.role = web3.utils.toBN(42);
-      await this.authority.$_setTargetFunctionRole(this.managed.address, this.selector, this.role);
-      await this.authority.$_grantRole(this.role, roleMember, 0, 0);
+      this.selector = this.managed.fnRestricted.getFragment().selector;
+      this.role = 42n;
+      await this.authority.$_setTargetFunctionRole(this.managed, this.selector, this.role);
+      await this.authority.$_grantRole(this.role, this.roleMember, 0, 0);
     });
 
     it('succeeds when role is granted without execution delay', async function () {
-      await this.managed.methods[method]({ from: roleMember });
+      await this.managed.connect(this.roleMember)[this.selector]();
     });
 
     it('reverts when role is not granted', async function () {
-      await expectRevertCustomError(this.managed.methods[method]({ from: other }), 'AccessManagedUnauthorized', [
-        other,
-      ]);
+      await expect(this.managed.connect(this.other)[this.selector]())
+        .to.be.revertedWithCustomError(this.managed, 'AccessManagedUnauthorized')
+        .withArgs(this.other.address);
     });
 
     it('panics in short calldata', async function () {
       // We avoid adding the `restricted` modifier to the fallback function because other tests may depend on it
       // being accessible without restrictions. We check for the internal `_checkCanCall` instead.
-      await expectRevert.unspecified(this.managed.$_checkCanCall(other, '0x1234'));
+      await expect(this.managed.$_checkCanCall(this.roleMember, '0x1234')).to.be.reverted;
     });
 
     describe('when role is granted with execution delay', function () {
       beforeEach(async function () {
-        const executionDelay = web3.utils.toBN(911);
-        await this.authority.$_grantRole(this.role, roleMember, 0, executionDelay);
+        const executionDelay = 911n;
+        await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay);
       });
 
       it('reverts if the operation is not scheduled', async function () {
-        const calldata = this.managed.contract.methods[method]().encodeABI();
-        const opId = await this.authority.hashOperation(roleMember, this.managed.address, calldata);
+        const fn = this.managed.interface.getFunction(this.selector);
+        const calldata = this.managed.interface.encodeFunctionData(fn, []);
+        const opId = await this.authority.hashOperation(this.roleMember, this.managed, calldata);
 
-        await expectRevertCustomError(this.managed.methods[method]({ from: roleMember }), 'AccessManagerNotScheduled', [
-          opId,
-        ]);
+        await expect(this.managed.connect(this.roleMember)[this.selector]())
+          .to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled')
+          .withArgs(opId);
       });
 
       it('succeeds if the operation is scheduled', async function () {
         // Arguments
         const delay = time.duration.hours(12);
-        const calldata = this.managed.contract.methods[method]().encodeABI();
+        const fn = this.managed.interface.getFunction(this.selector);
+        const calldata = this.managed.interface.encodeFunctionData(fn, []);
 
         // Schedule
-        const timestamp = await time.latest();
-        const scheduledAt = timestamp.addn(1);
-        const when = scheduledAt.add(delay);
-        await setNextBlockTimestamp(scheduledAt);
-        await this.authority.schedule(this.managed.address, calldata, when, {
-          from: roleMember,
-        });
+        const timestamp = await time.clock.timestamp();
+        const scheduledAt = timestamp + 1n;
+        const when = scheduledAt + delay;
+        await time.forward.timestamp(scheduledAt, false);
+        await this.authority.connect(this.roleMember).schedule(this.managed, calldata, when);
 
         // Set execution date
-        await setNextBlockTimestamp(when);
+        await time.forward.timestamp(when, false);
 
         // Shouldn't revert
-        await this.managed.methods[method]({ from: roleMember });
+        await this.managed.connect(this.roleMember)[this.selector]();
       });
     });
   });
 
   describe('setAuthority', function () {
-    beforeEach(async function () {
-      this.newAuthority = await AccessManager.new(admin);
-    });
-
     it('reverts if the caller is not the authority', async function () {
-      await expectRevertCustomError(this.managed.setAuthority(other, { from: other }), 'AccessManagedUnauthorized', [
-        other,
-      ]);
+      await expect(this.managed.connect(this.other).setAuthority(this.other))
+        .to.be.revertedWithCustomError(this.managed, 'AccessManagedUnauthorized')
+        .withArgs(this.other.address);
     });
 
     it('reverts if the new authority is not a valid authority', async function () {
-      await impersonate(this.authority.address);
-      await expectRevertCustomError(
-        this.managed.setAuthority(other, { from: this.authority.address }),
-        'AccessManagedInvalidAuthority',
-        [other],
-      );
+      await expect(this.managed.connect(this.authorityAsSigner).setAuthority(this.other))
+        .to.be.revertedWithCustomError(this.managed, 'AccessManagedInvalidAuthority')
+        .withArgs(this.other.address);
     });
 
     it('sets authority and emits AuthorityUpdated event', async function () {
-      await impersonate(this.authority.address);
-      const { receipt } = await this.managed.setAuthority(this.newAuthority.address, { from: this.authority.address });
-      await expectEvent(receipt, 'AuthorityUpdated', {
-        authority: this.newAuthority.address,
-      });
-      expect(await this.managed.authority()).to.eq(this.newAuthority.address);
+      await expect(this.managed.connect(this.authorityAsSigner).setAuthority(this.anotherAuthority))
+        .to.emit(this.managed, 'AuthorityUpdated')
+        .withArgs(this.anotherAuthority.target);
+
+      expect(await this.managed.authority()).to.equal(this.anotherAuthority.target);
     });
   });
 
   describe('isConsumingScheduledOp', function () {
     beforeEach(async function () {
-      this.authority = await AuthoritiyObserveIsConsuming.new();
-      this.managed = await AccessManaged.new(this.authority.address);
+      await this.managed.connect(this.authorityAsSigner).setAuthority(this.authorityObserveIsConsuming);
     });
 
     it('returns bytes4(0) when not consuming operation', async function () {
-      expect(await this.managed.isConsumingScheduledOp()).to.eq('0x00000000');
+      expect(await this.managed.isConsumingScheduledOp()).to.equal('0x00000000');
     });
 
     it('returns isConsumingScheduledOp selector when consuming operation', async function () {
-      const receipt = await this.managed.fnRestricted({ from: other });
-      await expectEvent.inTransaction(receipt.tx, this.authority, 'ConsumeScheduledOpCalled', {
-        caller: other,
-        data: this.managed.contract.methods.fnRestricted().encodeABI(),
-        isConsuming: selector('isConsumingScheduledOp()'),
-      });
+      const isConsumingScheduledOp = this.managed.interface.getFunction('isConsumingScheduledOp()');
+      const fnRestricted = this.managed.fnRestricted.getFragment();
+      await expect(this.managed.connect(this.other).fnRestricted())
+        .to.emit(this.authorityObserveIsConsuming, 'ConsumeScheduledOpCalled')
+        .withArgs(
+          this.other.address,
+          this.managed.interface.encodeFunctionData(fnRestricted, []),
+          isConsumingScheduledOp.selector,
+        );
     });
   });
 });

+ 56 - 69
test/access/manager/AccessManager.behavior.js

@@ -1,5 +1,3 @@
-const { mine } = require('@nomicfoundation/hardhat-network-helpers');
-const { expectRevertCustomError } = require('../../helpers/customError');
 const {
   LIKE_COMMON_IS_EXECUTING,
   LIKE_COMMON_GET_ACCESS,
@@ -18,13 +16,9 @@ const {
  */
 function shouldBehaveLikeDelayedAdminOperation() {
   const getAccessPath = LIKE_COMMON_GET_ACCESS;
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
-    beforeEach('consume previously set grant delay', async function () {
-      // Consume previously set delay
-      await mine();
-    });
-    testAsDelayedOperation();
-  };
+  testAsDelayedOperation.mineDelay = true;
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay =
+    testAsDelayedOperation;
   getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
     beforeEach('set execution delay', async function () {
       this.scheduleIn = this.executionDelay; // For testAsDelayedOperation
@@ -42,14 +36,12 @@ function shouldBehaveLikeDelayedAdminOperation() {
       testAsHasRole({
         publicRoleIsRequired() {
           it('reverts as AccessManagerUnauthorizedAccount', async function () {
-            await expectRevertCustomError(
-              web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-              'AccessManagerUnauthorizedAccount',
-              [
-                this.caller,
+            await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+              .to.be.revertedWithCustomError(this.target, 'AccessManagerUnauthorizedAccount')
+              .withArgs(
+                this.caller.address,
                 this.roles.ADMIN.id, // Although PUBLIC is required, target function role doesn't apply to admin ops
-              ],
-            );
+              );
           });
         },
         specificRoleIsRequired: getAccessPath,
@@ -63,19 +55,20 @@ function shouldBehaveLikeDelayedAdminOperation() {
  */
 function shouldBehaveLikeNotDelayedAdminOperation() {
   const getAccessPath = LIKE_COMMON_GET_ACCESS;
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
-    beforeEach('set execution delay', async function () {
-      await mine();
-      this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
-    });
-    testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
-  };
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
-    beforeEach('set execution delay', async function () {
-      this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
-    });
-    testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
-  };
+
+  function testScheduleOperation(mineDelay) {
+    return function self() {
+      self.mineDelay = mineDelay;
+      beforeEach('set execution delay', async function () {
+        this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
+      });
+      testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
+    };
+  }
+
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay =
+    testScheduleOperation(true);
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = testScheduleOperation(false);
 
   beforeEach('set target as manager', function () {
     this.target = this.manager;
@@ -87,11 +80,12 @@ function shouldBehaveLikeNotDelayedAdminOperation() {
       testAsHasRole({
         publicRoleIsRequired() {
           it('reverts as AccessManagerUnauthorizedAccount', async function () {
-            await expectRevertCustomError(
-              web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-              'AccessManagerUnauthorizedAccount',
-              [this.caller, this.roles.ADMIN.id], // Although PUBLIC_ROLE is required, admin ops are not subject to target function roles
-            );
+            await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+              .to.be.revertedWithCustomError(this.target, 'AccessManagerUnauthorizedAccount')
+              .withArgs(
+                this.caller.address,
+                this.roles.ADMIN.id, // Although PUBLIC_ROLE is required, admin ops are not subject to target function roles
+              );
           });
         },
         specificRoleIsRequired: getAccessPath,
@@ -105,19 +99,17 @@ function shouldBehaveLikeNotDelayedAdminOperation() {
  */
 function shouldBehaveLikeRoleAdminOperation(roleAdmin) {
   const getAccessPath = LIKE_COMMON_GET_ACCESS;
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
-    beforeEach('set operation delay', async function () {
-      await mine();
-      this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
-    });
-    testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
-  };
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
+
+  function afterGrantDelay() {
+    afterGrantDelay.mineDelay = true;
     beforeEach('set execution delay', async function () {
       this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
     });
     testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
-  };
+  }
+
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = afterGrantDelay;
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = afterGrantDelay;
 
   beforeEach('set target as manager', function () {
     this.target = this.manager;
@@ -129,11 +121,9 @@ function shouldBehaveLikeRoleAdminOperation(roleAdmin) {
       testAsHasRole({
         publicRoleIsRequired() {
           it('reverts as AccessManagerUnauthorizedAccount', async function () {
-            await expectRevertCustomError(
-              web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-              'AccessManagerUnauthorizedAccount',
-              [this.caller, roleAdmin], // Role admin ops require the role's admin
-            );
+            await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+              .to.be.revertedWithCustomError(this.target, 'AccessManagerUnauthorizedAccount')
+              .withArgs(this.caller.address, roleAdmin);
           });
         },
         specificRoleIsRequired: getAccessPath,
@@ -150,11 +140,9 @@ function shouldBehaveLikeRoleAdminOperation(roleAdmin) {
 function shouldBehaveLikeAManagedRestrictedOperation() {
   function revertUnauthorized() {
     it('reverts as AccessManagedUnauthorized', async function () {
-      await expectRevertCustomError(
-        web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-        'AccessManagedUnauthorized',
-        [this.caller],
-      );
+      await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+        .to.be.revertedWithCustomError(this.target, 'AccessManagedUnauthorized')
+        .withArgs(this.caller.address);
     });
   }
 
@@ -166,20 +154,19 @@ function shouldBehaveLikeAManagedRestrictedOperation() {
     revertUnauthorized;
   getAccessPath.requiredRoleIsNotGranted = revertUnauthorized;
 
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
-    beforeEach('consume previously set grant delay', async function () {
-      // Consume previously set delay
-      await mine();
-      this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
-    });
-    testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
-  };
-  getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
-    beforeEach('consume previously set grant delay', async function () {
-      this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
-    });
-    testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
-  };
+  function testScheduleOperation(mineDelay) {
+    return function self() {
+      self.mineDelay = mineDelay;
+      beforeEach('sets execution delay', async function () {
+        this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
+      });
+      testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE);
+    };
+  }
+
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay =
+    testScheduleOperation(true);
+  getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = testScheduleOperation(false);
 
   const isExecutingPath = LIKE_COMMON_IS_EXECUTING;
   isExecutingPath.notExecuting = revertUnauthorized;
@@ -191,11 +178,11 @@ function shouldBehaveLikeAManagedRestrictedOperation() {
       callerIsNotTheManager: {
         publicRoleIsRequired() {
           it('succeeds called directly', async function () {
-            await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
+            await this.caller.sendTransaction({ to: this.target, data: this.calldata });
           });
 
           it('succeeds via execute', async function () {
-            await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
+            await this.manager.connect(this.caller).execute(this.target, this.calldata);
           });
         },
         specificRoleIsRequired: getAccessPath,

+ 79 - 86
test/access/manager/AccessManager.predicate.js

@@ -1,27 +1,22 @@
 const { setStorageAt } = require('@nomicfoundation/hardhat-network-helpers');
-const {
-  time: { setNextBlockTimestamp },
-} = require('@nomicfoundation/hardhat-network-helpers');
-const { time } = require('@openzeppelin/test-helpers');
-const { EXECUTION_ID_STORAGE_SLOT, EXPIRATION, scheduleOperation } = require('../../helpers/access-manager');
+const { EXECUTION_ID_STORAGE_SLOT, EXPIRATION, prepareOperation } = require('../../helpers/access-manager');
 const { impersonate } = require('../../helpers/account');
-const { expectRevertCustomError } = require('../../helpers/customError');
+const { bigint: time } = require('../../helpers/time');
+const { ethers } = require('hardhat');
 
 // ============ COMMON PREDICATES ============
 
 const LIKE_COMMON_IS_EXECUTING = {
   executing() {
     it('succeeds', async function () {
-      await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
+      await this.caller.sendTransaction({ to: this.target, data: this.calldata });
     });
   },
   notExecuting() {
     it('reverts as AccessManagerUnauthorizedAccount', async function () {
-      await expectRevertCustomError(
-        web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-        'AccessManagerUnauthorizedAccount',
-        [this.caller, this.role.id],
-      );
+      await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+        .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount')
+        .withArgs(this.caller.address, this.role.id);
     });
   },
 };
@@ -32,11 +27,9 @@ const LIKE_COMMON_GET_ACCESS = {
       callerHasAnExecutionDelay: {
         beforeGrantDelay() {
           it('reverts as AccessManagerUnauthorizedAccount', async function () {
-            await expectRevertCustomError(
-              web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-              'AccessManagerUnauthorizedAccount',
-              [this.caller, this.role.id],
-            );
+            await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+              .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount')
+              .withArgs(this.caller.address, this.role.id);
           });
         },
         afterGrantDelay: undefined, // Diverges if there's an operation delay or not
@@ -44,20 +37,18 @@ const LIKE_COMMON_GET_ACCESS = {
       callerHasNoExecutionDelay: {
         beforeGrantDelay() {
           it('reverts as AccessManagerUnauthorizedAccount', async function () {
-            await expectRevertCustomError(
-              web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-              'AccessManagerUnauthorizedAccount',
-              [this.caller, this.role.id],
-            );
+            await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+              .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount')
+              .withArgs(this.caller.address, this.role.id);
           });
         },
         afterGrantDelay() {
           it('succeeds called directly', async function () {
-            await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
+            await this.caller.sendTransaction({ to: this.target, data: this.calldata });
           });
 
           it('succeeds via execute', async function () {
-            await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
+            await this.manager.connect(this.caller).execute(this.target, this.calldata);
           });
         },
       },
@@ -66,22 +57,20 @@ const LIKE_COMMON_GET_ACCESS = {
       callerHasAnExecutionDelay: undefined, // Diverges if there's an operation to schedule or not
       callerHasNoExecutionDelay() {
         it('succeeds called directly', async function () {
-          await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
+          await this.caller.sendTransaction({ to: this.target, data: this.calldata });
         });
 
         it('succeeds via execute', async function () {
-          await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
+          await this.manager.connect(this.caller).execute(this.target, this.calldata);
         });
       },
     },
   },
   requiredRoleIsNotGranted() {
     it('reverts as AccessManagerUnauthorizedAccount', async function () {
-      await expectRevertCustomError(
-        web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-        'AccessManagerUnauthorizedAccount',
-        [this.caller, this.role.id],
-      );
+      await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+        .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount')
+        .withArgs(this.caller.address, this.role.id);
     });
   },
 };
@@ -90,39 +79,33 @@ const LIKE_COMMON_SCHEDULABLE = {
   scheduled: {
     before() {
       it('reverts as AccessManagerNotReady', async function () {
-        await expectRevertCustomError(
-          web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-          'AccessManagerNotReady',
-          [this.operationId],
-        );
+        await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+          .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotReady')
+          .withArgs(this.operationId);
       });
     },
     after() {
       it('succeeds called directly', async function () {
-        await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
+        await this.caller.sendTransaction({ to: this.target, data: this.calldata });
       });
 
       it('succeeds via execute', async function () {
-        await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
+        await this.manager.connect(this.caller).execute(this.target, this.calldata);
       });
     },
     expired() {
       it('reverts as AccessManagerExpired', async function () {
-        await expectRevertCustomError(
-          web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-          'AccessManagerExpired',
-          [this.operationId],
-        );
+        await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+          .to.be.revertedWithCustomError(this.manager, 'AccessManagerExpired')
+          .withArgs(this.operationId);
       });
     },
   },
   notScheduled() {
     it('reverts as AccessManagerNotScheduled', async function () {
-      await expectRevertCustomError(
-        web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
-        'AccessManagerNotScheduled',
-        [this.operationId],
-      );
+      await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata }))
+        .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled')
+        .withArgs(this.operationId);
     });
   },
 };
@@ -135,7 +118,7 @@ const LIKE_COMMON_SCHEDULABLE = {
 function testAsClosable({ closed, open }) {
   describe('when the manager is closed', function () {
     beforeEach('close', async function () {
-      await this.manager.$_setTargetClosed(this.target.address, true);
+      await this.manager.$_setTargetClosed(this.target, true);
     });
 
     closed();
@@ -143,7 +126,7 @@ function testAsClosable({ closed, open }) {
 
   describe('when the manager is open', function () {
     beforeEach('open', async function () {
-      await this.manager.$_setTargetClosed(this.target.address, false);
+      await this.manager.$_setTargetClosed(this.target, false);
     });
 
     open();
@@ -157,13 +140,13 @@ function testAsClosable({ closed, open }) {
  */
 function testAsDelay(type, { before, after }) {
   beforeEach('define timestamp when delay takes effect', async function () {
-    const timestamp = await time.latest();
-    this.delayEffect = timestamp.add(this.delay);
+    const timestamp = await time.clock.timestamp();
+    this.delayEffect = timestamp + this.delay;
   });
 
   describe(`when ${type} delay has not taken effect yet`, function () {
     beforeEach(`set next block timestamp before ${type} takes effect`, async function () {
-      await setNextBlockTimestamp(this.delayEffect.subn(1));
+      await time.forward.timestamp(this.delayEffect - 1n, !!before.mineDelay);
     });
 
     before();
@@ -171,7 +154,7 @@ function testAsDelay(type, { before, after }) {
 
   describe(`when ${type} delay has taken effect`, function () {
     beforeEach(`set next block timestamp when ${type} takes effect`, async function () {
-      await setNextBlockTimestamp(this.delayEffect);
+      await time.forward.timestamp(this.delayEffect, !!after.mineDelay);
     });
 
     after();
@@ -186,21 +169,25 @@ function testAsDelay(type, { before, after }) {
 function testAsSchedulableOperation({ scheduled: { before, after, expired }, notScheduled }) {
   describe('when operation is scheduled', function () {
     beforeEach('schedule operation', async function () {
-      await impersonate(this.caller); // May be a contract
-      const { operationId } = await scheduleOperation(this.manager, {
+      if (this.caller.target) {
+        await impersonate(this.caller.target);
+        this.caller = await ethers.getSigner(this.caller.target);
+      }
+      const { operationId, schedule } = await prepareOperation(this.manager, {
         caller: this.caller,
-        target: this.target.address,
+        target: this.target,
         calldata: this.calldata,
         delay: this.scheduleIn,
       });
+      await schedule();
       this.operationId = operationId;
     });
 
     describe('when operation is not ready for execution', function () {
       beforeEach('set next block time before operation is ready', async function () {
-        this.scheduledAt = await time.latest();
+        this.scheduledAt = await time.clock.timestamp();
         const schedule = await this.manager.getSchedule(this.operationId);
-        await setNextBlockTimestamp(schedule.subn(1));
+        await time.forward.timestamp(schedule - 1n, !!before.mineDelay);
       });
 
       before();
@@ -208,9 +195,9 @@ function testAsSchedulableOperation({ scheduled: { before, after, expired }, not
 
     describe('when operation is ready for execution', function () {
       beforeEach('set next block time when operation is ready for execution', async function () {
-        this.scheduledAt = await time.latest();
+        this.scheduledAt = await time.clock.timestamp();
         const schedule = await this.manager.getSchedule(this.operationId);
-        await setNextBlockTimestamp(schedule);
+        await time.forward.timestamp(schedule, !!after.mineDelay);
       });
 
       after();
@@ -218,9 +205,9 @@ function testAsSchedulableOperation({ scheduled: { before, after, expired }, not
 
     describe('when operation has expired', function () {
       beforeEach('set next block time when operation expired', async function () {
-        this.scheduledAt = await time.latest();
+        this.scheduledAt = await time.clock.timestamp();
         const schedule = await this.manager.getSchedule(this.operationId);
-        await setNextBlockTimestamp(schedule.add(EXPIRATION));
+        await time.forward.timestamp(schedule + EXPIRATION, !!expired.mineDelay);
       });
 
       expired();
@@ -229,10 +216,10 @@ function testAsSchedulableOperation({ scheduled: { before, after, expired }, not
 
   describe('when operation is not scheduled', function () {
     beforeEach('set expected operationId', async function () {
-      this.operationId = await this.manager.hashOperation(this.caller, this.target.address, this.calldata);
+      this.operationId = await this.manager.hashOperation(this.caller, this.target, this.calldata);
 
       // Assert operation is not scheduled
-      expect(await this.manager.getSchedule(this.operationId)).to.be.bignumber.equal(web3.utils.toBN(0));
+      expect(await this.manager.getSchedule(this.operationId)).to.equal(0n);
     });
 
     notScheduled();
@@ -245,16 +232,22 @@ function testAsSchedulableOperation({ scheduled: { before, after, expired }, not
 function testAsRestrictedOperation({ callerIsTheManager: { executing, notExecuting }, callerIsNotTheManager }) {
   describe('when the call comes from the manager (msg.sender == manager)', function () {
     beforeEach('define caller as manager', async function () {
-      this.caller = this.manager.address;
-      await impersonate(this.caller);
+      this.caller = this.manager;
+      if (this.caller.target) {
+        await impersonate(this.caller.target);
+        this.caller = await ethers.getSigner(this.caller.target);
+      }
     });
 
     describe('when _executionId is in storage for target and selector', function () {
       beforeEach('set _executionId flag from calldata and target', async function () {
-        const executionId = web3.utils.keccak256(
-          web3.eth.abi.encodeParameters(['address', 'bytes4'], [this.target.address, this.calldata.substring(0, 10)]),
+        const executionId = ethers.keccak256(
+          ethers.AbiCoder.defaultAbiCoder().encode(
+            ['address', 'bytes4'],
+            [this.target.target, this.calldata.substring(0, 10)],
+          ),
         );
-        await setStorageAt(this.manager.address, EXECUTION_ID_STORAGE_SLOT, executionId);
+        await setStorageAt(this.manager.target, EXECUTION_ID_STORAGE_SLOT, executionId);
       });
 
       executing();
@@ -279,8 +272,8 @@ function testAsDelayedOperation() {
   describe('with operation delay', function () {
     describe('when operation delay is greater than execution delay', function () {
       beforeEach('set operation delay', async function () {
-        this.operationDelay = this.executionDelay.add(time.duration.hours(1));
-        await this.manager.$_setTargetAdminDelay(this.target.address, this.operationDelay);
+        this.operationDelay = this.executionDelay + time.duration.hours(1);
+        await this.manager.$_setTargetAdminDelay(this.target, this.operationDelay);
         this.scheduleIn = this.operationDelay; // For testAsSchedulableOperation
       });
 
@@ -289,8 +282,8 @@ function testAsDelayedOperation() {
 
     describe('when operation delay is shorter than execution delay', function () {
       beforeEach('set operation delay', async function () {
-        this.operationDelay = this.executionDelay.sub(time.duration.hours(1));
-        await this.manager.$_setTargetAdminDelay(this.target.address, this.operationDelay);
+        this.operationDelay = this.executionDelay - time.duration.hours(1);
+        await this.manager.$_setTargetAdminDelay(this.target, this.operationDelay);
         this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
       });
 
@@ -300,8 +293,8 @@ function testAsDelayedOperation() {
 
   describe('without operation delay', function () {
     beforeEach('set operation delay', async function () {
-      this.operationDelay = web3.utils.toBN(0);
-      await this.manager.$_setTargetAdminDelay(this.target.address, this.operationDelay);
+      this.operationDelay = 0n;
+      await this.manager.$_setTargetAdminDelay(this.target, this.operationDelay);
       this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation
     });
 
@@ -344,9 +337,9 @@ function testAsHasRole({ publicRoleIsRequired, specificRoleIsRequired }) {
   describe('when the function requires the caller to be granted with the PUBLIC_ROLE', function () {
     beforeEach('set target function role as PUBLIC_ROLE', async function () {
       this.role = this.roles.PUBLIC;
-      await this.manager.$_setTargetFunctionRole(this.target.address, this.calldata.substring(0, 10), this.role.id, {
-        from: this.roles.ADMIN.members[0],
-      });
+      await this.manager
+        .connect(this.roles.ADMIN.members[0])
+        .$_setTargetFunctionRole(this.target, this.calldata.substring(0, 10), this.role.id);
     });
 
     publicRoleIsRequired();
@@ -354,9 +347,9 @@ function testAsHasRole({ publicRoleIsRequired, specificRoleIsRequired }) {
 
   describe('when the function requires the caller to be granted with a role other than PUBLIC_ROLE', function () {
     beforeEach('set target function role as PUBLIC_ROLE', async function () {
-      await this.manager.$_setTargetFunctionRole(this.target.address, this.calldata.substring(0, 10), this.role.id, {
-        from: this.roles.ADMIN.members[0],
-      });
+      await this.manager
+        .connect(this.roles.ADMIN.members[0])
+        .$_setTargetFunctionRole(this.target, this.calldata.substring(0, 10), this.role.id);
     });
 
     testAsGetAccess(specificRoleIsRequired);
@@ -400,7 +393,7 @@ function testAsGetAccess({
 
       describe('when caller has no execution delay', function () {
         beforeEach('set role and delay', async function () {
-          this.executionDelay = web3.utils.toBN(0);
+          this.executionDelay = 0n;
           await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay);
         });
 
@@ -410,7 +403,7 @@ function testAsGetAccess({
 
     describe('when role granting is not delayed', function () {
       beforeEach('define delay', function () {
-        this.grantDelay = web3.utils.toBN(0);
+        this.grantDelay = 0n;
       });
 
       describe('when caller has an execution delay', function () {
@@ -424,7 +417,7 @@ function testAsGetAccess({
 
       describe('when caller has no execution delay', function () {
         beforeEach('set role and delay', async function () {
-          this.executionDelay = web3.utils.toBN(0);
+          this.executionDelay = 0n;
           await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay);
         });
 
@@ -439,7 +432,7 @@ function testAsGetAccess({
     // Although this is highly unlikely, we check for it here to avoid false positives.
     beforeEach('assert role is unset', async function () {
       const { since } = await this.manager.getAccess(this.role.id, this.caller);
-      expect(since).to.be.bignumber.equal(web3.utils.toBN(0));
+      expect(since).to.equal(0n);
     });
 
     requiredRoleIsNotGranted();

Разлика између датотеке није приказан због своје велике величине
+ 250 - 300
test/access/manager/AccessManager.test.js


+ 41 - 28
test/access/manager/AuthorityUtils.test.js

@@ -1,68 +1,81 @@
-require('@openzeppelin/test-helpers');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { ethers } = require('hardhat');
 
-const AuthorityUtils = artifacts.require('$AuthorityUtils');
-const NotAuthorityMock = artifacts.require('NotAuthorityMock');
-const AuthorityNoDelayMock = artifacts.require('AuthorityNoDelayMock');
-const AuthorityDelayMock = artifacts.require('AuthorityDelayMock');
-const AuthorityNoResponse = artifacts.require('AuthorityNoResponse');
+async function fixture() {
+  const [user, other] = await ethers.getSigners();
 
-contract('AuthorityUtils', function (accounts) {
-  const [user, other] = accounts;
+  const mock = await ethers.deployContract('$AuthorityUtils');
+  const notAuthorityMock = await ethers.deployContract('NotAuthorityMock');
+  const authorityNoDelayMock = await ethers.deployContract('AuthorityNoDelayMock');
+  const authorityDelayMock = await ethers.deployContract('AuthorityDelayMock');
+  const authorityNoResponse = await ethers.deployContract('AuthorityNoResponse');
 
+  return {
+    user,
+    other,
+    mock,
+    notAuthorityMock,
+    authorityNoDelayMock,
+    authorityDelayMock,
+    authorityNoResponse,
+  };
+}
+
+describe('AuthorityUtils', function () {
   beforeEach(async function () {
-    this.mock = await AuthorityUtils.new();
+    Object.assign(this, await loadFixture(fixture));
   });
 
   describe('canCallWithDelay', function () {
     describe('when authority does not have a canCall function', function () {
       beforeEach(async function () {
-        this.authority = await NotAuthorityMock.new();
+        this.authority = this.notAuthorityMock;
       });
 
       it('returns (immediate = 0, delay = 0)', async function () {
         const { immediate, delay } = await this.mock.$canCallWithDelay(
-          this.authority.address,
-          user,
-          other,
+          this.authority,
+          this.user,
+          this.other,
           '0x12345678',
         );
         expect(immediate).to.equal(false);
-        expect(delay).to.be.bignumber.equal('0');
+        expect(delay).to.be.equal(0n);
       });
     });
 
     describe('when authority has no delay', function () {
       beforeEach(async function () {
-        this.authority = await AuthorityNoDelayMock.new();
+        this.authority = this.authorityNoDelayMock;
         this.immediate = true;
         await this.authority._setImmediate(this.immediate);
       });
 
       it('returns (immediate, delay = 0)', async function () {
         const { immediate, delay } = await this.mock.$canCallWithDelay(
-          this.authority.address,
-          user,
-          other,
+          this.authority,
+          this.user,
+          this.other,
           '0x12345678',
         );
         expect(immediate).to.equal(this.immediate);
-        expect(delay).to.be.bignumber.equal('0');
+        expect(delay).to.be.equal(0n);
       });
     });
 
     describe('when authority replies with a delay', function () {
       beforeEach(async function () {
-        this.authority = await AuthorityDelayMock.new();
+        this.authority = this.authorityDelayMock;
       });
 
       for (const immediate of [true, false]) {
-        for (const delay of ['0', '42']) {
+        for (const delay of [0n, 42n]) {
           it(`returns (immediate=${immediate}, delay=${delay})`, async function () {
             await this.authority._setImmediate(immediate);
             await this.authority._setDelay(delay);
-            const result = await this.mock.$canCallWithDelay(this.authority.address, user, other, '0x12345678');
+            const result = await this.mock.$canCallWithDelay(this.authority, this.user, this.other, '0x12345678');
             expect(result.immediate).to.equal(immediate);
-            expect(result.delay).to.be.bignumber.equal(delay);
+            expect(result.delay).to.be.equal(delay);
           });
         }
       }
@@ -70,18 +83,18 @@ contract('AuthorityUtils', function (accounts) {
 
     describe('when authority replies with empty data', function () {
       beforeEach(async function () {
-        this.authority = await AuthorityNoResponse.new();
+        this.authority = this.authorityNoResponse;
       });
 
       it('returns (immediate = 0, delay = 0)', async function () {
         const { immediate, delay } = await this.mock.$canCallWithDelay(
-          this.authority.address,
-          user,
-          other,
+          this.authority,
+          this.user,
+          this.other,
           '0x12345678',
         );
         expect(immediate).to.equal(false);
-        expect(delay).to.be.bignumber.equal('0');
+        expect(delay).to.be.equal(0n);
       });
     });
   });

+ 23 - 19
test/helpers/access-manager.js

@@ -1,23 +1,23 @@
-const { time } = require('@openzeppelin/test-helpers');
-const { MAX_UINT64 } = require('./constants');
-const { namespaceSlot } = require('./namespaced-storage');
 const {
-  time: { setNextBlockTimestamp },
-} = require('@nomicfoundation/hardhat-network-helpers');
+  bigint: { MAX_UINT64 },
+} = require('./constants');
+const { namespaceSlot } = require('./namespaced-storage');
+const { bigint: time } = require('./time');
+const { keccak256, AbiCoder } = require('ethers');
 
 function buildBaseRoles() {
   const roles = {
     ADMIN: {
-      id: web3.utils.toBN(0),
+      id: 0n,
     },
     SOME_ADMIN: {
-      id: web3.utils.toBN(17),
+      id: 17n,
     },
     SOME_GUARDIAN: {
-      id: web3.utils.toBN(35),
+      id: 35n,
     },
     SOME: {
-      id: web3.utils.toBN(42),
+      id: 42n,
     },
     PUBLIC: {
       id: MAX_UINT64,
@@ -53,23 +53,27 @@ const CONSUMING_SCHEDULE_STORAGE_SLOT = namespaceSlot('AccessManaged', 0n);
 /**
  * @requires this.{manager, caller, target, calldata}
  */
-async function scheduleOperation(manager, { caller, target, calldata, delay }) {
-  const timestamp = await time.latest();
-  const scheduledAt = timestamp.addn(1);
-  await setNextBlockTimestamp(scheduledAt); // Fix next block timestamp for predictability
-  const { receipt } = await manager.schedule(target, calldata, scheduledAt.add(delay), {
-    from: caller,
-  });
+async function prepareOperation(manager, { caller, target, calldata, delay }) {
+  const timestamp = await time.clock.timestamp();
+  const scheduledAt = timestamp + 1n;
+  await time.forward.timestamp(scheduledAt, false); // Fix next block timestamp for predictability
 
   return {
-    receipt,
+    schedule: () => manager.connect(caller).schedule(target, calldata, scheduledAt + delay),
     scheduledAt,
     operationId: hashOperation(caller, target, calldata),
   };
 }
 
+const lazyGetAddress = addressable => addressable.address ?? addressable.target ?? addressable;
+
 const hashOperation = (caller, target, data) =>
-  web3.utils.keccak256(web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [caller, target, data]));
+  keccak256(
+    AbiCoder.defaultAbiCoder().encode(
+      ['address', 'address', 'bytes'],
+      [lazyGetAddress(caller), lazyGetAddress(target), data],
+    ),
+  );
 
 module.exports = {
   buildBaseRoles,
@@ -78,6 +82,6 @@ module.exports = {
   EXPIRATION,
   EXECUTION_ID_STORAGE_SLOT,
   CONSUMING_SCHEDULE_STORAGE_SLOT,
-  scheduleOperation,
+  prepareOperation,
   hashOperation,
 };

+ 9 - 2
test/helpers/constants.js

@@ -1,5 +1,12 @@
+// TODO: deprecate the old version in favor of this one
+const bigint = {
+  MAX_UINT48: 2n ** 48n - 1n,
+  MAX_UINT64: 2n ** 64n - 1n,
+};
+
 // TODO: remove toString() when bigint are supported
 module.exports = {
-  MAX_UINT48: (2n ** 48n - 1n).toString(),
-  MAX_UINT64: (2n ** 64n - 1n).toString(),
+  MAX_UINT48: bigint.MAX_UINT48.toString(),
+  MAX_UINT64: bigint.MAX_UINT64.toString(),
+  bigint,
 };

+ 5 - 7
test/helpers/namespaced-storage.js

@@ -1,16 +1,14 @@
+const { keccak256, id, toBeHex, MaxUint256 } = require('ethers');
 const { artifacts } = require('hardhat');
 
 function namespaceId(contractName) {
   return `openzeppelin.storage.${contractName}`;
 }
 
-function namespaceLocation(id) {
-  const hashIdBN = web3.utils.toBN(web3.utils.keccak256(id)).subn(1); // keccak256(id) - 1
-  const hashIdHex = web3.utils.padLeft(web3.utils.numberToHex(hashIdBN), 64);
-
-  const mask = BigInt(web3.utils.padLeft('0x00', 64, 'f')); // ~0xff
-
-  return BigInt(web3.utils.keccak256(hashIdHex)) & mask;
+function namespaceLocation(value) {
+  const hashIdBN = BigInt(id(value)) - 1n; // keccak256(id) - 1
+  const mask = MaxUint256 - 0xffn; // ~0xff
+  return BigInt(keccak256(toBeHex(hashIdBN, 32))) & mask;
 }
 
 function namespaceSlot(contractName, offset) {

Неке датотеке нису приказане због велике количине промена