Skip to content

Commit d27d468

Browse files
feat: Add rate limit admin to remote UpgradeableBurnMintTokenPool (#14)
* feat: enable remote rateLimitAdmin * fix: remove unnecessary modifer * chore: reorder state variables * fix: add and correct comments --------- Co-authored-by: miguelmtzinf <[email protected]>
1 parent 55a0b59 commit d27d468

File tree

6 files changed

+219
-17
lines changed

6 files changed

+219
-17
lines changed

.github/actions/setup-nodejs/action.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ description: Setup pnpm for contracts
77
runs:
88
using: composite
99
steps:
10-
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd # v2.2.4
10+
- uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0
1111
with:
12-
version: ^7.0.0
12+
version: ^9.0.0
1313

14-
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
14+
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
1515
with:
16-
node-version: "16"
16+
node-version: "20"
1717
cache: "pnpm"
1818
cache-dependency-path: "contracts/pnpm-lock.yaml"
1919

contracts/src/v0.8/ccip/pools/GHO/UpgradeableBurnMintTokenPool.sol

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol";
88

99
import {UpgradeableTokenPool} from "./UpgradeableTokenPool.sol";
1010
import {UpgradeableBurnMintTokenPoolAbstract} from "./UpgradeableBurnMintTokenPoolAbstract.sol";
11+
import {RateLimiter} from "../../libraries/RateLimiter.sol";
1112

1213
import {IRouter} from "../../interfaces/IRouter.sol";
1314

@@ -17,9 +18,16 @@ import {IRouter} from "../../interfaces/IRouter.sol";
1718
/// @dev Contract adaptations:
1819
/// - Implementation of Initializable to allow upgrades
1920
/// - Move of allowlist and router definition to initialization stage
21+
/// - Inclusion of rate limit admin who may configure rate limits in addition to owner
2022
contract UpgradeableBurnMintTokenPool is Initializable, UpgradeableBurnMintTokenPoolAbstract, ITypeAndVersion {
23+
error Unauthorized(address caller);
24+
2125
string public constant override typeAndVersion = "BurnMintTokenPool 1.4.0";
2226

27+
/// @notice The address of the rate limiter admin.
28+
/// @dev Can be address(0) if none is configured.
29+
address internal s_rateLimitAdmin;
30+
2331
/// @dev Constructor
2432
/// @param token The bridgeable token that is managed by this pool.
2533
/// @param armProxy The address of the arm proxy
@@ -49,6 +57,34 @@ contract UpgradeableBurnMintTokenPool is Initializable, UpgradeableBurnMintToken
4957
}
5058
}
5159

60+
/// @notice Sets the rate limiter admin address.
61+
/// @dev Only callable by the owner.
62+
/// @param rateLimitAdmin The new rate limiter admin address.
63+
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner {
64+
s_rateLimitAdmin = rateLimitAdmin;
65+
}
66+
67+
/// @notice Gets the rate limiter admin address.
68+
function getRateLimitAdmin() external view returns (address) {
69+
return s_rateLimitAdmin;
70+
}
71+
72+
/// @notice Sets the chain rate limiter config.
73+
/// @dev Only callable by the owner or the rate limiter admin. NOTE: overwrites the normal
74+
/// onlyAdmin check in the base implementation to also allow the rate limiter admin.
75+
/// @param remoteChainSelector The remote chain selector for which the rate limits apply.
76+
/// @param outboundConfig The new outbound rate limiter config.
77+
/// @param inboundConfig The new inbound rate limiter config.
78+
function setChainRateLimiterConfig(
79+
uint64 remoteChainSelector,
80+
RateLimiter.Config memory outboundConfig,
81+
RateLimiter.Config memory inboundConfig
82+
) external override {
83+
if (msg.sender != s_rateLimitAdmin && msg.sender != owner()) revert Unauthorized(msg.sender);
84+
85+
_setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);
86+
}
87+
5288
/// @inheritdoc UpgradeableBurnMintTokenPoolAbstract
5389
function _burn(uint256 amount) internal virtual override {
5490
IBurnMintERC20(address(i_token)).burn(amount);

contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ contract UpgradeableLockReleaseTokenPool is Initializable, UpgradeableTokenPool,
248248
emit LiquidityRemoved(msg.sender, amount);
249249
}
250250

251-
/// @notice Sets the rate limiter admin address.
251+
/// @notice Sets the chain rate limiter config.
252252
/// @dev Only callable by the owner or the rate limiter admin. NOTE: overwrites the normal
253253
/// onlyAdmin check in the base implementation to also allow the rate limiter admin.
254254
/// @param remoteChainSelector The remote chain selector for which the rate limits apply.

contracts/src/v0.8/ccip/pools/GHO/diffs/UpgradeableBurnMintTokenPool_diff.md

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
```diff
22
diff --git a/src/v0.8/ccip/pools/BurnMintTokenPool.sol b/src/v0.8/ccip/pools/GHO/UpgradeableBurnMintTokenPool.sol
3-
index 9af0f22f4c..58be87812f 100644
3+
index 9af0f22f4c..a46ff915e5 100644
44
--- a/src/v0.8/ccip/pools/BurnMintTokenPool.sol
55
+++ b/src/v0.8/ccip/pools/GHO/UpgradeableBurnMintTokenPool.sol
6-
@@ -1,28 +1,55 @@
6+
@@ -1,28 +1,90 @@
77
// SPDX-License-Identifier: BUSL-1.1
88
-pragma solidity 0.8.19;
99
+pragma solidity ^0.8.0;
@@ -16,15 +16,10 @@ index 9af0f22f4c..58be87812f 100644
1616
-import {BurnMintTokenPoolAbstract} from "./BurnMintTokenPoolAbstract.sol";
1717
+import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol";
1818
+import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol";
19-
20-
-/// @notice This pool mints and burns a 3rd-party token.
21-
-/// @dev Pool whitelisting mode is set in the constructor and cannot be modified later.
22-
-/// It either accepts any address as originalSender, or only accepts whitelisted originalSender.
23-
-/// The only way to change whitelisting mode is to deploy a new pool.
24-
-/// If that is expected, please make sure the token's burner/minter roles are adjustable.
25-
-contract BurnMintTokenPool is BurnMintTokenPoolAbstract, ITypeAndVersion {
19+
+
2620
+import {UpgradeableTokenPool} from "./UpgradeableTokenPool.sol";
2721
+import {UpgradeableBurnMintTokenPoolAbstract} from "./UpgradeableBurnMintTokenPoolAbstract.sol";
22+
+import {RateLimiter} from "../../libraries/RateLimiter.sol";
2823
+
2924
+import {IRouter} from "../../interfaces/IRouter.sol";
3025
+
@@ -35,8 +30,20 @@ index 9af0f22f4c..58be87812f 100644
3530
+/// - Implementation of Initializable to allow upgrades
3631
+/// - Move of allowlist and router definition to initialization stage
3732
+contract UpgradeableBurnMintTokenPool is Initializable, UpgradeableBurnMintTokenPoolAbstract, ITypeAndVersion {
33+
+ error Unauthorized(address caller);
34+
35+
-/// @notice This pool mints and burns a 3rd-party token.
36+
-/// @dev Pool whitelisting mode is set in the constructor and cannot be modified later.
37+
-/// It either accepts any address as originalSender, or only accepts whitelisted originalSender.
38+
-/// The only way to change whitelisting mode is to deploy a new pool.
39+
-/// If that is expected, please make sure the token's burner/minter roles are adjustable.
40+
-contract BurnMintTokenPool is BurnMintTokenPoolAbstract, ITypeAndVersion {
3841
string public constant override typeAndVersion = "BurnMintTokenPool 1.4.0";
3942

43+
+ /// @notice The address of the rate limiter admin.
44+
+ /// @dev Can be address(0) if none is configured.
45+
+ address internal s_rateLimitAdmin;
46+
+
4047
+ /// @dev Constructor
4148
+ /// @param token The bridgeable token that is managed by this pool.
4249
+ /// @param armProxy The address of the arm proxy
@@ -71,6 +78,34 @@ index 9af0f22f4c..58be87812f 100644
7178
+ }
7279
+ }
7380
+
81+
+ /// @notice Sets the rate limiter admin address.
82+
+ /// @dev Only callable by the owner.
83+
+ /// @param rateLimitAdmin The new rate limiter admin address.
84+
+ function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner {
85+
+ s_rateLimitAdmin = rateLimitAdmin;
86+
+ }
87+
+
88+
+ /// @notice Gets the rate limiter admin address.
89+
+ function getRateLimitAdmin() external view returns (address) {
90+
+ return s_rateLimitAdmin;
91+
+ }
92+
+
93+
+ /// @notice Sets the rate limiter admin address.
94+
+ /// @dev Only callable by the owner or the rate limiter admin. NOTE: overwrites the normal
95+
+ /// onlyAdmin check in the base implementation to also allow the rate limiter admin.
96+
+ /// @param remoteChainSelector The remote chain selector for which the rate limits apply.
97+
+ /// @param outboundConfig The new outbound rate limiter config.
98+
+ /// @param inboundConfig The new inbound rate limiter config.
99+
+ function setChainRateLimiterConfig(
100+
+ uint64 remoteChainSelector,
101+
+ RateLimiter.Config memory outboundConfig,
102+
+ RateLimiter.Config memory inboundConfig
103+
+ ) external override {
104+
+ if (msg.sender != s_rateLimitAdmin && msg.sender != owner()) revert Unauthorized(msg.sender);
105+
+
106+
+ _setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);
107+
+ }
108+
+
74109
+ /// @inheritdoc UpgradeableBurnMintTokenPoolAbstract
75110
function _burn(uint256 amount) internal virtual override {
76111
IBurnMintERC20(address(i_token)).burn(amount);

contracts/src/v0.8/ccip/test/BaseTest.t.sol

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {StdUtils} from "forge-std/StdUtils.sol";
88
import {MockARM} from "./mocks/MockARM.sol";
99
import {StructFactory} from "./StructFactory.sol";
1010

11-
1211
contract BaseTest is Test, StructFactory {
1312
bool private s_baseTestInitialized;
1413

@@ -30,6 +29,4 @@ contract BaseTest is Test, StructFactory {
3029

3130
s_mockARM = new MockARM();
3231
}
33-
34-
3532
}

contracts/src/v0.8/ccip/test/pools/GHO/GhoTokenPoolRemote.t.sol

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,137 @@ contract GhoTokenPoolEthereum_upgradeability is GhoTokenPoolRemoteSetup {
242242
assertEq(_getProxyAdminAddress(address(s_pool)), PROXY_ADMIN, "Unauthorized admin change");
243243
}
244244
}
245+
246+
contract GhoTokenPoolRemote_setChainRateLimiterConfig is GhoTokenPoolRemoteSetup {
247+
event ConfigChanged(RateLimiter.Config);
248+
event ChainConfigured(
249+
uint64 chainSelector,
250+
RateLimiter.Config outboundRateLimiterConfig,
251+
RateLimiter.Config inboundRateLimiterConfig
252+
);
253+
254+
uint64 internal s_remoteChainSelector;
255+
256+
function setUp() public virtual override {
257+
GhoTokenPoolRemoteSetup.setUp();
258+
UpgradeableTokenPool.ChainUpdate[] memory chainUpdates = new UpgradeableTokenPool.ChainUpdate[](1);
259+
s_remoteChainSelector = 123124;
260+
chainUpdates[0] = UpgradeableTokenPool.ChainUpdate({
261+
remoteChainSelector: s_remoteChainSelector,
262+
allowed: true,
263+
outboundRateLimiterConfig: getOutboundRateLimiterConfig(),
264+
inboundRateLimiterConfig: getInboundRateLimiterConfig()
265+
});
266+
changePrank(AAVE_DAO);
267+
s_pool.applyChainUpdates(chainUpdates);
268+
changePrank(OWNER);
269+
}
270+
271+
function testFuzz_SetChainRateLimiterConfigSuccess(uint128 capacity, uint128 rate, uint32 newTime) public {
272+
// Cap the lower bound to 4 so 4/2 is still >= 2
273+
vm.assume(capacity >= 4);
274+
// Cap the lower bound to 2 so 2/2 is still >= 1
275+
rate = uint128(bound(rate, 2, capacity - 2));
276+
// Bucket updates only work on increasing time
277+
newTime = uint32(bound(newTime, block.timestamp + 1, type(uint32).max));
278+
vm.warp(newTime);
279+
280+
uint256 oldOutboundTokens = s_pool.getCurrentOutboundRateLimiterState(s_remoteChainSelector).tokens;
281+
uint256 oldInboundTokens = s_pool.getCurrentInboundRateLimiterState(s_remoteChainSelector).tokens;
282+
283+
RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({isEnabled: true, capacity: capacity, rate: rate});
284+
RateLimiter.Config memory newInboundConfig = RateLimiter.Config({
285+
isEnabled: true,
286+
capacity: capacity / 2,
287+
rate: rate / 2
288+
});
289+
290+
vm.expectEmit();
291+
emit ConfigChanged(newOutboundConfig);
292+
vm.expectEmit();
293+
emit ConfigChanged(newInboundConfig);
294+
vm.expectEmit();
295+
emit ChainConfigured(s_remoteChainSelector, newOutboundConfig, newInboundConfig);
296+
297+
changePrank(AAVE_DAO);
298+
s_pool.setChainRateLimiterConfig(s_remoteChainSelector, newOutboundConfig, newInboundConfig);
299+
300+
uint256 expectedTokens = RateLimiter._min(newOutboundConfig.capacity, oldOutboundTokens);
301+
302+
RateLimiter.TokenBucket memory bucket = s_pool.getCurrentOutboundRateLimiterState(s_remoteChainSelector);
303+
assertEq(bucket.capacity, newOutboundConfig.capacity);
304+
assertEq(bucket.rate, newOutboundConfig.rate);
305+
assertEq(bucket.tokens, expectedTokens);
306+
assertEq(bucket.lastUpdated, newTime);
307+
308+
expectedTokens = RateLimiter._min(newInboundConfig.capacity, oldInboundTokens);
309+
310+
bucket = s_pool.getCurrentInboundRateLimiterState(s_remoteChainSelector);
311+
assertEq(bucket.capacity, newInboundConfig.capacity);
312+
assertEq(bucket.rate, newInboundConfig.rate);
313+
assertEq(bucket.tokens, expectedTokens);
314+
assertEq(bucket.lastUpdated, newTime);
315+
}
316+
317+
function testOnlyOwnerOrRateLimitAdminSuccess() public {
318+
address rateLimiterAdmin = address(28973509103597907);
319+
320+
changePrank(AAVE_DAO);
321+
s_pool.setRateLimitAdmin(rateLimiterAdmin);
322+
323+
changePrank(rateLimiterAdmin);
324+
325+
s_pool.setChainRateLimiterConfig(
326+
s_remoteChainSelector,
327+
getOutboundRateLimiterConfig(),
328+
getInboundRateLimiterConfig()
329+
);
330+
331+
changePrank(AAVE_DAO);
332+
333+
s_pool.setChainRateLimiterConfig(
334+
s_remoteChainSelector,
335+
getOutboundRateLimiterConfig(),
336+
getInboundRateLimiterConfig()
337+
);
338+
}
339+
340+
// Reverts
341+
342+
function testOnlyOwnerReverts() public {
343+
changePrank(STRANGER);
344+
345+
vm.expectRevert(abi.encodeWithSelector(UpgradeableBurnMintTokenPool.Unauthorized.selector, STRANGER));
346+
s_pool.setChainRateLimiterConfig(
347+
s_remoteChainSelector,
348+
getOutboundRateLimiterConfig(),
349+
getInboundRateLimiterConfig()
350+
);
351+
}
352+
353+
function testNonExistentChainReverts() public {
354+
uint64 wrongChainSelector = 9084102894;
355+
356+
vm.expectRevert(abi.encodeWithSelector(UpgradeableTokenPool.NonExistentChain.selector, wrongChainSelector));
357+
changePrank(AAVE_DAO);
358+
s_pool.setChainRateLimiterConfig(wrongChainSelector, getOutboundRateLimiterConfig(), getInboundRateLimiterConfig());
359+
}
360+
}
361+
362+
contract GhoTokenPoolRemote_setRateLimitAdmin is GhoTokenPoolRemoteSetup {
363+
function testSetRateLimitAdminSuccess() public {
364+
assertEq(address(0), s_pool.getRateLimitAdmin());
365+
changePrank(AAVE_DAO);
366+
s_pool.setRateLimitAdmin(OWNER);
367+
assertEq(OWNER, s_pool.getRateLimitAdmin());
368+
}
369+
370+
// Reverts
371+
372+
function testSetRateLimitAdminReverts() public {
373+
vm.startPrank(STRANGER);
374+
375+
vm.expectRevert("Only callable by owner");
376+
s_pool.setRateLimitAdmin(STRANGER);
377+
}
378+
}

0 commit comments

Comments
 (0)