Skip to content

Commit 898958e

Browse files
fix: Address audit findings for Allowlist contract
- Add two-step process enforcement for weight decrease (Issue #1) - Introduce decreasePending flag to track valid decrease requests - Prevent bypassing the intended authorization flow - Add zero address validation (Issue #3) - Validate walletRegistry in initialize() - Validate stakingProvider in addStakingProvider() - Add zero weight validation (Issue #5) - Prevent adding staking providers with zero weight - Avoid potential duplicate additions - Restrict seize function access (Issue #8) - Only allow WalletRegistry to call seize() - Prevent event spam from unauthorized callers - Add comprehensive test coverage for all security fixes
1 parent 8281c38 commit 898958e

File tree

2 files changed

+70
-6
lines changed

2 files changed

+70
-6
lines changed

solidity/ecdsa/contracts/Allowlist.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ contract Allowlist is Ownable2StepUpgradeable {
3131
struct StakingProviderInfo {
3232
uint96 weight;
3333
uint96 pendingNewWeight;
34+
bool decreasePending;
3435
}
3536

3637
/// @notice Mapping between the staking provider address and a struct
@@ -59,13 +60,20 @@ contract Allowlist is Ownable2StepUpgradeable {
5960
error StakingProviderUnknown();
6061
error RequestedWeightNotBelowCurrentWeight();
6162
error NotWalletRegistry();
63+
error NoDecreasePending();
64+
error ZeroAddress();
65+
error ZeroWeight();
6266

6367
/// @custom:oz-upgrades-unsafe-allow constructor
6468
constructor() {
6569
_disableInitializers();
6670
}
6771

6872
function initialize(address _walletRegistry) external initializer {
73+
if (_walletRegistry == address(0)) {
74+
revert ZeroAddress();
75+
}
76+
6977
__Ownable2Step_init();
7078

7179
walletRegistry = WalletRegistry(_walletRegistry);
@@ -80,6 +88,14 @@ contract Allowlist is Ownable2StepUpgradeable {
8088
external
8189
onlyOwner
8290
{
91+
if (stakingProvider == address(0)) {
92+
revert ZeroAddress();
93+
}
94+
95+
if (weight == 0) {
96+
revert ZeroWeight();
97+
}
98+
8399
StakingProviderInfo storage info = stakingProviders[stakingProvider];
84100

85101
if (info.weight != 0) {
@@ -124,6 +140,7 @@ contract Allowlist is Ownable2StepUpgradeable {
124140
emit WeightDecreaseRequested(stakingProvider, currentWeight, newWeight);
125141

126142
info.pendingNewWeight = newWeight;
143+
info.decreasePending = true;
127144
walletRegistry.authorizationDecreaseRequested(
128145
stakingProvider,
129146
currentWeight,
@@ -151,10 +168,15 @@ contract Allowlist is Ownable2StepUpgradeable {
151168
revert StakingProviderUnknown();
152169
}
153170

171+
if (!info.decreasePending) {
172+
revert NoDecreasePending();
173+
}
174+
154175
emit WeightDecreaseFinalized(stakingProvider, currentWeight, newWeight);
155176

156177
info.weight = newWeight;
157178
info.pendingNewWeight = 0;
179+
info.decreasePending = false;
158180
return newWeight;
159181
}
160182

@@ -181,6 +203,10 @@ contract Allowlist is Ownable2StepUpgradeable {
181203
address notifier,
182204
address[] memory _stakingProviders
183205
) external {
206+
if (msg.sender != address(walletRegistry)) {
207+
revert NotWalletRegistry();
208+
}
209+
184210
emit MaliciousBehaviorIdentified(notifier, _stakingProviders);
185211
}
186212

solidity/ecdsa/test/Allowlist.test.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-unused-expressions */
2-
import { ethers, helpers } from "hardhat"
2+
import { ethers } from "hardhat"
33
import { smock } from "@defi-wonderland/smock"
44
import { expect } from "chai"
55

66
import type { FakeContract } from "@defi-wonderland/smock"
7-
import type { ContractTransaction } from "ethers"
87
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
98
import type { Allowlist, WalletRegistry } from "../typechain"
109

@@ -51,6 +50,15 @@ describe("Allowlist", () => {
5150
allowlist.initialize(walletRegistry.address)
5251
).to.be.revertedWith("Initializable: contract is already initialized")
5352
})
53+
54+
it("should revert if initialized with zero address", async () => {
55+
const AllowlistFactory = await ethers.getContractFactory("Allowlist")
56+
const newAllowlist = await AllowlistFactory.deploy()
57+
58+
await expect(newAllowlist.initialize(ZERO_ADDRESS)).to.be.revertedWith(
59+
"ZeroAddress"
60+
)
61+
})
5462
})
5563

5664
describe("addStakingProvider", () => {
@@ -100,6 +108,22 @@ describe("Allowlist", () => {
100108
.addStakingProvider(stakingProvider1.address, weight)
101109
).to.be.reverted
102110
})
111+
112+
it("should revert if staking provider is zero address", async () => {
113+
const weight = ethers.utils.parseEther("40000")
114+
115+
await expect(
116+
allowlist.connect(governance).addStakingProvider(ZERO_ADDRESS, weight)
117+
).to.be.revertedWith("ZeroAddress")
118+
})
119+
120+
it("should revert if weight is zero", async () => {
121+
await expect(
122+
allowlist
123+
.connect(governance)
124+
.addStakingProvider(stakingProvider1.address, 0)
125+
).to.be.revertedWith("ZeroWeight")
126+
})
103127
})
104128

105129
context("when called by non-owner", () => {
@@ -278,6 +302,20 @@ describe("Allowlist", () => {
278302
.approveAuthorizationDecrease(stakingProvider2.address)
279303
).to.be.reverted
280304
})
305+
306+
it("should revert if no decrease was requested (bypass protection)", async () => {
307+
// Add a staking provider but don't request decrease
308+
await allowlist
309+
.connect(governance)
310+
.addStakingProvider(stakingProvider2.address, initialWeight)
311+
312+
// Trying to approve decrease without requesting it first should fail
313+
await expect(
314+
allowlist
315+
.connect(walletRegistry.wallet)
316+
.approveAuthorizationDecrease(stakingProvider2.address)
317+
).to.be.revertedWith("NoDecreasePending")
318+
})
281319
})
282320

283321
context("when called by non-WalletRegistry", () => {
@@ -330,14 +368,14 @@ describe("Allowlist", () => {
330368
})
331369

332370
describe("seize", () => {
333-
it("should emit MaliciousBehaviorIdentified event without seizing tokens", async () => {
371+
it("should emit MaliciousBehaviorIdentified event when called by WalletRegistry", async () => {
334372
const stakingProviders = [
335373
stakingProvider1.address,
336374
stakingProvider2.address,
337375
]
338376

339377
await expect(
340-
allowlist.seize(
378+
allowlist.connect(walletRegistry.wallet).seize(
341379
ethers.utils.parseEther("1000"), // amount (ignored)
342380
100, // rewardMultiplier (ignored)
343381
thirdParty.address, // notifier
@@ -348,7 +386,7 @@ describe("Allowlist", () => {
348386
.withArgs(thirdParty.address, stakingProviders)
349387
})
350388

351-
it("should be callable by anyone", async () => {
389+
it("should revert when called by non-WalletRegistry", async () => {
352390
const stakingProviders = [stakingProvider1.address]
353391

354392
await expect(
@@ -360,7 +398,7 @@ describe("Allowlist", () => {
360398
governance.address,
361399
stakingProviders
362400
)
363-
).to.not.be.reverted
401+
).to.be.revertedWith("NotWalletRegistry")
364402
})
365403
})
366404

0 commit comments

Comments
 (0)