From 9ea45331fbe2a6c0df6a5258bff7f11d2066fbd3 Mon Sep 17 00:00:00 2001 From: telome <> Date: Fri, 4 Oct 2024 12:44:30 +0200 Subject: [PATCH] Add maxWithdraws to L2TokenBridge --- deploy/L2TokenGatewaySpell.sol | 12 +++++++++++- deploy/TokenGatewayInit.sol | 6 +++++- script/Init.s.sol | 7 ++++++- src/L2TokenGateway.sol | 8 ++++++++ test/Integration.t.sol | 14 +++++++++----- test/L2TokenGateway.t.sol | 18 +++++++++++++++++- 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/deploy/L2TokenGatewaySpell.sol b/deploy/L2TokenGatewaySpell.sol index a15cc6d..78f89cc 100644 --- a/deploy/L2TokenGatewaySpell.sol +++ b/deploy/L2TokenGatewaySpell.sol @@ -24,6 +24,7 @@ interface L2TokenGatewayLike { function deny(address) external; function close() external; function registerToken(address, address) external; + function setMaxWithdraw(address, uint256) external; } interface AuthLike { @@ -49,13 +50,21 @@ contract L2TokenGatewaySpell { unchecked { ++i; } } } + + function setMaxWithdraws(address[] memory l2Tokens, uint256[] memory maxWithdraws) public { + for (uint256 i; i < l2Tokens.length;) { + l2Gateway.setMaxWithdraw(l2Tokens[i], maxWithdraws[i]); + unchecked { ++i; } + } + } function init( address l2Gateway_, address counterpartGateway, address l2Router, address[] calldata l1Tokens, - address[] calldata l2Tokens + address[] calldata l2Tokens, + uint256[] calldata maxWithdraws ) external { // sanity checks require(address(l2Gateway) == l2Gateway_, "L2TokenGatewaySpell/l2-gateway-mismatch"); @@ -64,5 +73,6 @@ contract L2TokenGatewaySpell { require(l2Gateway.l2Router() == l2Router, "L2TokenGatewaySpell/l2-router-mismatch"); registerTokens(l1Tokens, l2Tokens); + setMaxWithdraws(l2Tokens, maxWithdraws); } } diff --git a/deploy/TokenGatewayInit.sol b/deploy/TokenGatewayInit.sol index 624260b..fa56039 100644 --- a/deploy/TokenGatewayInit.sol +++ b/deploy/TokenGatewayInit.sol @@ -60,6 +60,7 @@ struct GatewaysConfig { address inbox; address[] l1Tokens; address[] l2Tokens; + uint256[] maxWithdraws; MessageParams xchainMsg; } @@ -81,6 +82,7 @@ library TokenGatewayInit { require(l1Gateway.l1Router() == cfg.l1Router, "TokenGatewayInit/l1-router-mismatch"); require(l1Gateway.inbox() == cfg.inbox, "TokenGatewayInit/inbox-mismatch"); require(cfg.l1Tokens.length == cfg.l2Tokens.length, "TokenGatewayInit/token-arrays-mismatch"); + require(cfg.maxWithdraws.length == cfg.l2Tokens.length, "TokenGatewayInit/max-withdraws-length-mismatch"); uint256 l1CallValue = cfg.xchainMsg.maxSubmissionCost + cfg.xchainMsg.maxGas * cfg.xchainMsg.gasPriceBid; @@ -94,6 +96,7 @@ library TokenGatewayInit { (address l1Token, address l2Token) = (cfg.l1Tokens[i], cfg.l2Tokens[i]); require(l1Token != address(0), "TokenGatewayInit/invalid-l1-token"); require(l2Token != address(0), "TokenGatewayInit/invalid-l2-token"); + require(cfg.maxWithdraws[i] > 0, "TokenGatewayInit/max-withdraw-not-set"); require(l1Gateway.l1ToL2Token(l1Token) == address(0), "TokenGatewayInit/existing-l1-token"); l1Gateway.registerToken(l1Token, l2Token); @@ -107,7 +110,8 @@ library TokenGatewayInit { l1Gateway_, l1Router.counterpartGateway(), cfg.l1Tokens, - cfg.l2Tokens + cfg.l2Tokens, + cfg.maxWithdraws )), l1CallValue: l1CallValue, maxGas: cfg.xchainMsg.maxGas, diff --git a/script/Init.s.sol b/script/Init.s.sol index 62ae6f6..b8b1e14 100644 --- a/script/Init.s.sol +++ b/script/Init.s.sol @@ -55,6 +55,10 @@ contract Init is Script { cfg.inbox = deps.readAddress(".inbox"); cfg.l1Tokens = deps.readAddressArray(".l1Tokens"); cfg.l2Tokens = deps.readAddressArray(".l2Tokens"); + cfg.maxWithdraws = new uint256[](cfg.l2Tokens.length); + for (uint256 i; i < cfg.maxWithdraws.length; ++i) { + cfg.maxWithdraws[i] = 10_000_000 ether; + } bytes memory initCalldata = abi.encodeCall(L2GovernanceRelay.relay, ( deps.readAddress(".l2GatewaySpell"), @@ -63,7 +67,8 @@ contract Init is Script { l1Gateway, deps.readAddress(".l2Router"), cfg.l1Tokens, - cfg.l2Tokens + cfg.l2Tokens, + cfg.maxWithdraws )) )); cfg.xchainMsg = MessageParams({ diff --git a/src/L2TokenGateway.sol b/src/L2TokenGateway.sol index 16be772..87c3267 100644 --- a/src/L2TokenGateway.sol +++ b/src/L2TokenGateway.sol @@ -32,6 +32,7 @@ contract L2TokenGateway is ITokenGateway, ICustomGateway, L2ArbitrumMessenger { mapping(address => uint256) public wards; mapping(address => address) public l1ToL2Token; + mapping(address => uint256) public maxWithdraws; uint256 public isOpen = 1; // --- immutables --- @@ -44,6 +45,7 @@ contract L2TokenGateway is ITokenGateway, ICustomGateway, L2ArbitrumMessenger { event Rely(address indexed usr); event Deny(address indexed usr); event Closed(); + event MaxWithdrawSet(address indexed l2Token, uint256 maxWithdraw); event DepositFinalized( address indexed l1Token, address indexed _from, @@ -109,6 +111,11 @@ contract L2TokenGateway is ITokenGateway, ICustomGateway, L2ArbitrumMessenger { emit TokenSet(l1Token, l2Token); } + function setMaxWithdraw(address l2Token, uint256 maxWithdraw) external auth { + maxWithdraws[l2Token] = maxWithdraw; + emit MaxWithdrawSet(l2Token, maxWithdraw); + } + // --- outbound transfers --- function outboundTransfer( @@ -139,6 +146,7 @@ contract L2TokenGateway is ITokenGateway, ICustomGateway, L2ArbitrumMessenger { require(isOpen == 1, "L2TokenGateway/closed"); address l2Token = l1ToL2Token[l1Token]; require(l2Token != address(0), "L2TokenGateway/invalid-token"); + require(amount <= maxWithdraws[l2Token], "L2TokenGateway/amount-too-large"); address from; bytes memory extraData = data; (from, extraData) = msg.sender == l2Router ? abi.decode(extraData, (address, bytes)) : (msg.sender, extraData); diff --git a/test/Integration.t.sol b/test/Integration.t.sol index d40841c..bcffe6a 100644 --- a/test/Integration.t.sol +++ b/test/Integration.t.sol @@ -123,17 +123,20 @@ contract IntegrationTest is DssTest { l1Tokens[0] = address(l1Token); address[] memory l2Tokens = new address[](1); l2Tokens[0] = address(l2Token); + uint256[] memory maxWithdraws = new uint256[](1); + maxWithdraws[0] = 10_000_000 ether; MessageParams memory xchainMsg = MessageParams({ gasPriceBid: 0.1 gwei, maxGas: 300_000, maxSubmissionCost: 0.01 ether }); GatewaysConfig memory cfg = GatewaysConfig({ - l1Router: L1_ROUTER, - inbox: INBOX, - l1Tokens: l1Tokens, - l2Tokens: l2Tokens, - xchainMsg: xchainMsg + l1Router: L1_ROUTER, + inbox: INBOX, + l1Tokens: l1Tokens, + l2Tokens: l2Tokens, + maxWithdraws: maxWithdraws, + xchainMsg: xchainMsg }); l1Domain.selectFork(); @@ -150,6 +153,7 @@ contract IntegrationTest is DssTest { // test L2 side of initGateways assertEq(l2Gateway.l1ToL2Token(address(l1Token)), address(l2Token)); + assertEq(l2Gateway.maxWithdraws(address(l2Token)), 10_000_000 ether); assertEq(l2Token.wards(address(l2Gateway)), 1); // Register L1 & L2 gateways in L1 & L2 routers diff --git a/test/L2TokenGateway.t.sol b/test/L2TokenGateway.t.sol index 491785e..1d5162e 100644 --- a/test/L2TokenGateway.t.sol +++ b/test/L2TokenGateway.t.sol @@ -27,6 +27,7 @@ import { AddressAliasHelper } from "src/arbitrum/AddressAliasHelper.sol"; contract L2TokenGatewayTest is DssTest { event TokenSet(address indexed l1Address, address indexed l2Address); + event MaxWithdrawSet(address indexed l2Token, uint256 maxWithdraw); event Closed(); event DepositFinalized( address indexed l1Token, @@ -57,6 +58,7 @@ contract L2TokenGatewayTest is DssTest { l2Token.rely(address(gateway)); l2Token.deny(address(this)); gateway.registerToken(l1Token, address(l2Token)); + gateway.setMaxWithdraw(address(l2Token), 1_000_000 ether); vm.etch(ARB_SYS_ADDRESS, address(new ArbSysMock()).code); } @@ -80,7 +82,8 @@ contract L2TokenGatewayTest is DssTest { checkModifier(address(gateway), string(abi.encodePacked("L2TokenGateway", "/not-authorized")), [ gateway.close.selector, - gateway.registerToken.selector + gateway.registerToken.selector, + gateway.setMaxWithdraw.selector ]); } @@ -96,6 +99,16 @@ contract L2TokenGatewayTest is DssTest { assertEq(gateway.calculateL2TokenAddress(address(11)), address(22)); } + function testSetmaxWithdraw() public { + assertEq(gateway.maxWithdraws(address(22)), 0); + + vm.expectEmit(true, true, true, true); + emit MaxWithdrawSet(address(22), 123); + gateway.setMaxWithdraw(address(22), 123); + + assertEq(gateway.maxWithdraws(address(22)), 123); + } + function testClose() public { assertEq(gateway.isOpen(), 1); @@ -123,6 +136,9 @@ contract L2TokenGatewayTest is DssTest { vm.expectRevert("L2TokenGateway/invalid-token"); gateway.outboundTransfer(address(0xbad), address(0xb0b), 100 ether, 0, 0, ""); + + vm.expectRevert("L2TokenGateway/amount-too-large"); + gateway.outboundTransfer(l1Token, address(0xb0b), 1_000_000 ether + 1, 0, 0, ""); vm.expectRevert("L2TokenGateway/extra-data-not-allowed"); gateway.outboundTransfer(l1Token, address(0xb0b), 100 ether, 0, 0, "bad");