|
1 |
| -// const { ethers } = require('hardhat'); |
2 |
| -// const { expect } = require('chai'); |
3 |
| -// const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); |
4 |
| - |
5 |
| -// const { impersonate } = require('../../helpers/account'); |
6 |
| -// const time = require('../../helpers/time'); |
7 |
| - |
8 |
| -// async function fixture() { |
9 |
| -// const [admin, roleMember, other] = await ethers.getSigners(); |
10 |
| - |
11 |
| -// const authority = await ethers.deployContract('$AccessManager', [admin]); |
12 |
| -// const managed = await ethers.deployContract('$AccessManagedTarget', [authority]); |
13 |
| - |
14 |
| -// const anotherAuthority = await ethers.deployContract('$AccessManager', [admin]); |
15 |
| -// const authorityObserveIsConsuming = await ethers.deployContract('$AuthorityObserveIsConsuming'); |
16 |
| - |
17 |
| -// await impersonate(authority.target); |
18 |
| -// const authorityAsSigner = await ethers.getSigner(authority.target); |
19 |
| - |
20 |
| -// return { |
21 |
| -// roleMember, |
22 |
| -// other, |
23 |
| -// authorityAsSigner, |
24 |
| -// authority, |
25 |
| -// managed, |
26 |
| -// authorityObserveIsConsuming, |
27 |
| -// anotherAuthority, |
28 |
| -// }; |
29 |
| -// } |
30 |
| - |
31 |
| -// describe('AccessManaged', function () { |
32 |
| -// beforeEach(async function () { |
33 |
| -// Object.assign(this, await loadFixture(fixture)); |
34 |
| -// }); |
35 |
| - |
36 |
| -// it('sets authority and emits AuthorityUpdated event during construction', async function () { |
37 |
| -// await expect(this.managed.deploymentTransaction()) |
38 |
| -// .to.emit(this.managed, 'AuthorityUpdated') |
39 |
| -// .withArgs(this.authority); |
40 |
| -// }); |
41 |
| - |
42 |
| -// describe('restricted modifier', function () { |
43 |
| -// beforeEach(async function () { |
44 |
| -// this.selector = this.managed.fnRestricted.getFragment().selector; |
45 |
| -// this.role = 42n; |
46 |
| -// await this.authority.$_setTargetFunctionRole(this.managed, this.selector, this.role); |
47 |
| -// await this.authority.$_grantRole(this.role, this.roleMember, 0, 0); |
48 |
| -// }); |
49 |
| - |
50 |
| -// it('succeeds when role is granted without execution delay', async function () { |
51 |
| -// await this.managed.connect(this.roleMember)[this.selector](); |
52 |
| -// }); |
53 |
| - |
54 |
| -// it('reverts when role is not granted', async function () { |
55 |
| -// await expect(this.managed.connect(this.other)[this.selector]()) |
56 |
| -// .to.be.revertedWithCustomError(this.managed, 'AccessManagedUnauthorized') |
57 |
| -// .withArgs(this.other); |
58 |
| -// }); |
59 |
| - |
60 |
| -// it('panics in short calldata', async function () { |
61 |
| -// // We avoid adding the `restricted` modifier to the fallback function because other tests may depend on it |
62 |
| -// // being accessible without restrictions. We check for the internal `_checkCanCall` instead. |
63 |
| -// await expect(this.managed.$_checkCanCall(this.roleMember, '0x1234')).to.be.reverted; |
64 |
| -// }); |
65 |
| - |
66 |
| -// describe('when role is granted with execution delay', function () { |
67 |
| -// beforeEach(async function () { |
68 |
| -// const executionDelay = 911n; |
69 |
| -// await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay); |
70 |
| -// }); |
71 |
| - |
72 |
| -// it('reverts if the operation is not scheduled', async function () { |
73 |
| -// const fn = this.managed.interface.getFunction(this.selector); |
74 |
| -// const calldata = this.managed.interface.encodeFunctionData(fn, []); |
75 |
| -// const opId = await this.authority.hashOperation(this.roleMember, this.managed, calldata); |
76 |
| - |
77 |
| -// await expect(this.managed.connect(this.roleMember)[this.selector]()) |
78 |
| -// .to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled') |
79 |
| -// .withArgs(opId); |
80 |
| -// }); |
81 |
| - |
82 |
| -// it('succeeds if the operation is scheduled', async function () { |
83 |
| -// // Arguments |
84 |
| -// const delay = time.duration.hours(12); |
85 |
| -// const fn = this.managed.interface.getFunction(this.selector); |
86 |
| -// const calldata = this.managed.interface.encodeFunctionData(fn, []); |
87 |
| - |
88 |
| -// // Schedule |
89 |
| -// const scheduledAt = (await time.clock.timestamp()) + 1n; |
90 |
| -// const when = scheduledAt + delay; |
91 |
| -// await time.increaseTo.timestamp(scheduledAt, false); |
92 |
| -// await this.authority.connect(this.roleMember).schedule(this.managed, calldata, when); |
93 |
| - |
94 |
| -// // Set execution date |
95 |
| -// await time.increaseTo.timestamp(when, false); |
96 |
| - |
97 |
| -// // Shouldn't revert |
98 |
| -// await this.managed.connect(this.roleMember)[this.selector](); |
99 |
| -// }); |
100 |
| -// }); |
101 |
| -// }); |
102 |
| - |
103 |
| -// describe('setAuthority', function () { |
104 |
| -// it('reverts if the caller is not the authority', async function () { |
105 |
| -// await expect(this.managed.connect(this.other).setAuthority(this.other)) |
106 |
| -// .to.be.revertedWithCustomError(this.managed, 'AccessManagedUnauthorized') |
107 |
| -// .withArgs(this.other); |
108 |
| -// }); |
109 |
| - |
110 |
| -// it('reverts if the new authority is not a valid authority', async function () { |
111 |
| -// await expect(this.managed.connect(this.authorityAsSigner).setAuthority(this.other)) |
112 |
| -// .to.be.revertedWithCustomError(this.managed, 'AccessManagedInvalidAuthority') |
113 |
| -// .withArgs(this.other); |
114 |
| -// }); |
115 |
| - |
116 |
| -// it('sets authority and emits AuthorityUpdated event', async function () { |
117 |
| -// await expect(this.managed.connect(this.authorityAsSigner).setAuthority(this.anotherAuthority)) |
118 |
| -// .to.emit(this.managed, 'AuthorityUpdated') |
119 |
| -// .withArgs(this.anotherAuthority); |
120 |
| - |
121 |
| -// expect(await this.managed.authority()).to.equal(this.anotherAuthority); |
122 |
| -// }); |
123 |
| -// }); |
124 |
| - |
125 |
| -// describe('isConsumingScheduledOp', function () { |
126 |
| -// beforeEach(async function () { |
127 |
| -// await this.managed.connect(this.authorityAsSigner).setAuthority(this.authorityObserveIsConsuming); |
128 |
| -// }); |
129 |
| - |
130 |
| -// it('returns bytes4(0) when not consuming operation', async function () { |
131 |
| -// expect(await this.managed.isConsumingScheduledOp()).to.equal('0x00000000'); |
132 |
| -// }); |
133 |
| - |
134 |
| -// it('returns isConsumingScheduledOp selector when consuming operation', async function () { |
135 |
| -// const isConsumingScheduledOp = this.managed.interface.getFunction('isConsumingScheduledOp()'); |
136 |
| -// const fnRestricted = this.managed.fnRestricted.getFragment(); |
137 |
| -// await expect(this.managed.connect(this.other).fnRestricted()) |
138 |
| -// .to.emit(this.authorityObserveIsConsuming, 'ConsumeScheduledOpCalled') |
139 |
| -// .withArgs( |
140 |
| -// this.other, |
141 |
| -// this.managed.interface.encodeFunctionData(fnRestricted, []), |
142 |
| -// isConsumingScheduledOp.selector, |
143 |
| -// ); |
144 |
| -// }); |
145 |
| -// }); |
146 |
| -// }); |
| 1 | +const { ethers } = require('hardhat'); |
| 2 | +const { expect } = require('chai'); |
| 3 | +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); |
| 4 | +const { mapValues } = require('@openzeppelin/contracts/test/helpers/iterate'); |
| 5 | + |
| 6 | +// Mask helpers |
| 7 | +const toHexString = i => '0x' + i.toString(16).padStart(64, 0); |
| 8 | +const toMask = i => toHexString(1n << BigInt(i)); |
| 9 | +const combine = (...masks) => toHexString(masks.reduce((acc, m) => acc | BigInt(m), 0n)); |
| 10 | + |
| 11 | +const Roles = { admin: 0x00, public: 0xff }; |
| 12 | +const Masks = mapValues(Roles, toMask); |
| 13 | + |
| 14 | +async function fixture() { |
| 15 | + const [admin, user, target, other] = await ethers.getSigners(); |
| 16 | + |
| 17 | + const authority = await ethers.deployContract('$AccessManagerLight', [admin]); |
| 18 | + |
| 19 | + return { |
| 20 | + admin, |
| 21 | + user, |
| 22 | + target, |
| 23 | + other, |
| 24 | + authority, |
| 25 | + }; |
| 26 | +} |
| 27 | + |
| 28 | +describe('AccessManaged', function () { |
| 29 | + beforeEach(async function () { |
| 30 | + Object.assign(this, await loadFixture(fixture)); |
| 31 | + }); |
| 32 | + |
| 33 | + describe('Permission Manager', function () { |
| 34 | + const selector = ethers.hexlify(ethers.randomBytes(4)); |
| 35 | + const group = 17n; |
| 36 | + const adminGroup = 42n; |
| 37 | + const groups = [13n, 69n, 128n]; |
| 38 | + |
| 39 | + describe('canCall', function () { |
| 40 | + describe('simple case: one group', async function () { |
| 41 | + it('Requirements set and Permissions set', async function () { |
| 42 | + this.withRequirements = true; |
| 43 | + this.withPermission = true; |
| 44 | + }); |
| 45 | + |
| 46 | + it('Requirements set and Permissions not set', async function () { |
| 47 | + this.withRequirements = true; |
| 48 | + this.withPermission = false; |
| 49 | + }); |
| 50 | + |
| 51 | + it('Requirements not set and Permissions set', async function () { |
| 52 | + this.withRequirements = false; |
| 53 | + this.withPermission = true; |
| 54 | + }); |
| 55 | + |
| 56 | + it('Requirements not set and Permissions not set', async function () { |
| 57 | + this.withRequirements = false; |
| 58 | + this.withPermission = false; |
| 59 | + }); |
| 60 | + |
| 61 | + afterEach(async function () { |
| 62 | + if (this.withRequirements) { |
| 63 | + await this.authority.setRequirements(this.target, [selector], [group]); |
| 64 | + } |
| 65 | + if (this.withPermission) { |
| 66 | + await this.authority.addGroup(this.user, group); |
| 67 | + } |
| 68 | + await expect(this.authority.canCall(this.user, this.target, selector)).to.eventually.equal( |
| 69 | + this.withRequirements && this.withPermission, |
| 70 | + ); |
| 71 | + }); |
| 72 | + }); |
| 73 | + |
| 74 | + describe('complexe case: one of many groups', async function () { |
| 75 | + it('some intersection', async function () { |
| 76 | + this.userGroups = [32, 42, 94, 128]; // User has all these groups |
| 77 | + this.targetGroups = [17, 35, 42, 69, 91]; // Target accepts any of these groups |
| 78 | + }); |
| 79 | + |
| 80 | + it('no intersection', async function () { |
| 81 | + this.userGroups = [32, 50, 94, 128]; // User has all these groups |
| 82 | + this.targetGroups = [17, 35, 42, 69, 91]; // Target accepts any of these groups |
| 83 | + }); |
| 84 | + |
| 85 | + afterEach(async function () { |
| 86 | + // set permissions and requirements |
| 87 | + await Promise.all([ |
| 88 | + this.authority.setRequirements(this.target, [selector], this.targetGroups), |
| 89 | + ...this.userGroups.map(group => this.authority.addGroup(this.user, group)), |
| 90 | + ]); |
| 91 | + |
| 92 | + // check can call |
| 93 | + await expect(this.authority.canCall(this.user, this.target, selector)).to.eventually.equal( |
| 94 | + this.userGroups.some(g => this.targetGroups.includes(g)), |
| 95 | + ); |
| 96 | + }); |
| 97 | + }); |
| 98 | + }); |
| 99 | + |
| 100 | + describe('addGroup', function () { |
| 101 | + it('authorized', async function () { |
| 102 | + await expect(this.authority.connect(this.admin).addGroup(this.user, group)) |
| 103 | + .to.emit(this.authority, 'GroupAdded') |
| 104 | + .withArgs(this.user, group); |
| 105 | + }); |
| 106 | + |
| 107 | + it('restricted', async function () { |
| 108 | + await expect(this.authority.connect(this.other).addGroup(this.user, group)) |
| 109 | + .to.revertedWithCustomError(this.authority, 'MissingPermissions') |
| 110 | + .withArgs(this.other, Masks.public, Masks.admin); |
| 111 | + }); |
| 112 | + |
| 113 | + it('with role admin', async function () { |
| 114 | + await this.authority.connect(this.admin).addGroup(this.other, adminGroup); |
| 115 | + |
| 116 | + await expect(this.authority.connect(this.other).addGroup(this.user, group)) |
| 117 | + .to.revertedWithCustomError(this.authority, 'MissingPermissions') |
| 118 | + .withArgs(this.other, combine(Masks.public, toMask(adminGroup)), Masks.admin); |
| 119 | + |
| 120 | + await expect(this.authority.setGroupAdmins(group, [adminGroup])) |
| 121 | + .to.emit(this.authority, 'GroupAdmins') |
| 122 | + .withArgs(group, toMask(adminGroup)); |
| 123 | + |
| 124 | + await expect(this.authority.connect(this.other).addGroup(this.user, group)) |
| 125 | + .to.emit(this.authority, 'GroupAdded') |
| 126 | + .withArgs(this.user, group); |
| 127 | + }); |
| 128 | + |
| 129 | + it('effect', async function () { |
| 130 | + await expect(this.authority.getGroups(this.user)).to.eventually.equal(Masks.public); |
| 131 | + |
| 132 | + await expect(this.authority.connect(this.admin).addGroup(this.user, group)) |
| 133 | + .to.emit(this.authority, 'GroupAdded') |
| 134 | + .withArgs(this.user, group); |
| 135 | + |
| 136 | + await expect(this.authority.getGroups(this.user)).to.eventually.equal(combine(Masks.public, toMask(group))); |
| 137 | + }); |
| 138 | + }); |
| 139 | + |
| 140 | + describe('remGroup', function () { |
| 141 | + beforeEach(async function () { |
| 142 | + await this.authority.connect(this.admin).addGroup(this.user, group); |
| 143 | + }); |
| 144 | + |
| 145 | + it('authorized', async function () { |
| 146 | + await expect(this.authority.connect(this.admin).remGroup(this.user, group)) |
| 147 | + .to.emit(this.authority, 'GroupRemoved') |
| 148 | + .withArgs(this.user, group); |
| 149 | + }); |
| 150 | + |
| 151 | + it('restricted', async function () { |
| 152 | + await expect(this.authority.connect(this.other).remGroup(this.user, group)) |
| 153 | + .to.revertedWithCustomError(this.authority, 'MissingPermissions') |
| 154 | + .withArgs(this.other, Masks.public, Masks.admin); |
| 155 | + }); |
| 156 | + |
| 157 | + it('with role admin', async function () { |
| 158 | + await this.authority.connect(this.admin).addGroup(this.other, adminGroup); |
| 159 | + |
| 160 | + await expect(this.authority.connect(this.other).addGroup(this.user, group)) |
| 161 | + .to.revertedWithCustomError(this.authority, 'MissingPermissions') |
| 162 | + .withArgs(this.other, combine(Masks.public, toMask(adminGroup)), Masks.admin); |
| 163 | + |
| 164 | + await expect(this.authority.setGroupAdmins(group, [adminGroup])) |
| 165 | + .to.emit(this.authority, 'GroupAdmins') |
| 166 | + .withArgs(group, toMask(adminGroup)); |
| 167 | + |
| 168 | + await expect(this.authority.connect(this.other).remGroup(this.user, group)) |
| 169 | + .to.emit(this.authority, 'GroupRemoved') |
| 170 | + .withArgs(this.user, group); |
| 171 | + }); |
| 172 | + |
| 173 | + it('effect', async function () { |
| 174 | + await expect(this.authority.getGroups(this.user)).to.eventually.equal(combine(Masks.public, toMask(group))); |
| 175 | + |
| 176 | + await expect(this.authority.connect(this.admin).remGroup(this.user, group)) |
| 177 | + .to.emit(this.authority, 'GroupRemoved') |
| 178 | + .withArgs(this.user, group); |
| 179 | + |
| 180 | + await expect(this.authority.getGroups(this.user)).to.eventually.equal(Masks.public); |
| 181 | + }); |
| 182 | + }); |
| 183 | + |
| 184 | + describe('setGroupAdmins', function () { |
| 185 | + it('authorized', async function () { |
| 186 | + await expect(this.authority.connect(this.admin).setGroupAdmins(group, groups)) |
| 187 | + .to.emit(this.authority, 'GroupAdmins') |
| 188 | + .withArgs(group, combine(...groups.map(toMask))); |
| 189 | + }); |
| 190 | + |
| 191 | + it('restricted', async function () { |
| 192 | + await expect(this.authority.connect(this.other).setGroupAdmins(group, groups)) |
| 193 | + .to.revertedWithCustomError(this.authority, 'MissingPermissions') |
| 194 | + .withArgs(this.other, Masks.public, Masks.admin); |
| 195 | + }); |
| 196 | + |
| 197 | + it('effect', async function () { |
| 198 | + // Set some previous value |
| 199 | + await this.authority.connect(this.admin).setGroupAdmins(group, [group]); |
| 200 | + |
| 201 | + // Check previous value is set |
| 202 | + await expect(this.authority.getGroupAdmins(group)).to.eventually.equal(combine(Masks.admin, toMask(group))); |
| 203 | + |
| 204 | + // Set some new values |
| 205 | + await expect(this.authority.connect(this.admin).setGroupAdmins(group, groups)) |
| 206 | + .to.emit(this.authority, 'GroupAdmins') |
| 207 | + .withArgs(group, combine(...groups.map(toMask))); |
| 208 | + |
| 209 | + // Check the new values are set, and the previous is removed |
| 210 | + await expect(this.authority.getGroupAdmins(group)).to.eventually.equal( |
| 211 | + combine(Masks.admin, ...groups.map(toMask)), |
| 212 | + ); |
| 213 | + }); |
| 214 | + }); |
| 215 | + |
| 216 | + describe('setRequirements', function () { |
| 217 | + it('authorized', async function () { |
| 218 | + await expect(this.authority.connect(this.admin).setRequirements(this.target, [selector], groups)) |
| 219 | + .to.emit(this.authority, 'RequirementsSet') |
| 220 | + .withArgs(this.target, selector, combine(...groups.map(toMask))); |
| 221 | + }); |
| 222 | + |
| 223 | + it('restricted', async function () { |
| 224 | + await expect(this.authority.connect(this.other).setRequirements(this.target, [selector], groups)) |
| 225 | + .to.revertedWithCustomError(this.authority, 'MissingPermissions') |
| 226 | + .withArgs(this.other, Masks.public, Masks.admin); |
| 227 | + }); |
| 228 | + |
| 229 | + it('effect', async function () { |
| 230 | + // Set some previous value |
| 231 | + await this.authority.connect(this.admin).setRequirements(this.target, [selector], [group]); |
| 232 | + |
| 233 | + // Check previous value is set |
| 234 | + await expect(this.authority.getRequirements(this.target, selector)).to.eventually.equal( |
| 235 | + combine(Masks.admin, toMask(group)), |
| 236 | + ); |
| 237 | + |
| 238 | + // Set some new values |
| 239 | + await expect(this.authority.connect(this.admin).setRequirements(this.target, [selector], groups)) |
| 240 | + .to.emit(this.authority, 'RequirementsSet') |
| 241 | + .withArgs(this.target, selector, combine(...groups.map(toMask))); |
| 242 | + |
| 243 | + // Check the new values are set, and the previous is removed |
| 244 | + await expect(this.authority.getRequirements(this.target, selector)).to.eventually.equal( |
| 245 | + combine(Masks.admin, ...groups.map(toMask)), |
| 246 | + ); |
| 247 | + }); |
| 248 | + }); |
| 249 | + }); |
| 250 | +}); |
0 commit comments