Skip to content

Commit 3590d12

Browse files
authored
feat(validium): disallow arbitrary from address in ERC20 gateway (#177)
1 parent db16a98 commit 3590d12

5 files changed

Lines changed: 78 additions & 37 deletions

File tree

foundry.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"lib/ds-test": {
3+
"rev": "e282159d5170298eb2455a6c05280ab5a73a4ef0"
4+
},
5+
"lib/forge-std": {
6+
"rev": "978ac6fadb62f5f0b723c996f64be52eddba6801"
7+
},
8+
"lib/solmate": {
9+
"rev": "c892309933b25c03d32b1b0d674df7ae292ba925"
10+
}
11+
}

src/test/validium/FastWithdrawVault.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ contract FastWithdrawVaultTest is ValidiumTestBase {
268268
address(messenger),
269269
address(template),
270270
address(factory),
271-
address(rollup)
271+
address(rollup),
272+
address(1) // wethGateway - placeholder for tests
272273
)
273274
)
274275
);

src/test/validium/L1ERC20GatewayValidium.t.sol

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol"
1414
import {IL1ERC20GatewayValidium} from "../../validium/IL1ERC20GatewayValidium.sol";
1515
import {IL2ERC20GatewayValidium} from "../../validium/IL2ERC20GatewayValidium.sol";
1616
import {L1ERC20GatewayValidium} from "../../validium/L1ERC20GatewayValidium.sol";
17+
import {L1WETHGatewayValidium} from "../../validium/L1WETHGatewayValidium.sol";
1718
import {ScrollChainValidium} from "../../validium/ScrollChainValidium.sol";
1819

1920
import {TransferReentrantToken} from "../mocks/tokens/TransferReentrantToken.sol";
@@ -46,6 +47,7 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
4647
ScrollStandardERC20 private template;
4748
ScrollStandardERC20Factory private factory;
4849
L2StandardERC20Gateway private counterpartGateway;
50+
L1WETHGatewayValidium private wethGateway = L1WETHGatewayValidium(address(1)); // placeholder for tests
4951

5052
MockERC20 private l1Token;
5153
MockERC20 private l2Token;
@@ -120,23 +122,43 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
120122
_deposit(address(this), amount, recipient, gasLimit);
121123
}
122124

123-
function testDepositERC20WithSender(
124-
address sender,
125+
function testDepositERC20WrongKey(
125126
uint256 amount,
126127
bytes memory recipient,
127128
uint256 gasLimit
128129
) public {
129-
_deposit(sender, amount, recipient, gasLimit);
130+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
131+
hevm.expectRevert(ScrollChainValidium.ErrorUnknownEncryptionKey.selector);
132+
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId + 1);
130133
}
131134

132-
function testDepositERC20WrongKey(
135+
function testDepositERC20WithRealSenderUnauthorized(
136+
address attacker,
137+
address victim,
133138
uint256 amount,
134139
bytes memory recipient,
135140
uint256 gasLimit
136141
) public {
142+
hevm.assume(attacker != address(0));
143+
hevm.assume(attacker != address(wethGateway));
144+
145+
amount = bound(amount, 1, l1Token.balanceOf(address(this)));
146+
gasLimit = bound(gasLimit, defaultGasLimit / 2, defaultGasLimit);
137147
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
138-
hevm.expectRevert(ScrollChainValidium.ErrorUnknownEncryptionKey.selector);
139-
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId + 1);
148+
149+
// Transfer tokens to attacker
150+
l1Token.transfer(attacker, amount);
151+
152+
// Attacker approves gateway
153+
hevm.startPrank(attacker);
154+
l1Token.approve(address(gateway), amount);
155+
156+
// Attacker tries to call depositERC20 with victim as _realSender
157+
// This should revert with ErrorCallerNotWethGateway
158+
hevm.expectRevert(L1ERC20GatewayValidium.ErrorCallerNotWethGateway.selector);
159+
gateway.depositERC20(address(l1Token), victim, recipient, amount, gasLimit, keyId);
160+
161+
hevm.stopPrank();
140162
}
141163

142164
function testDepositReentrantToken(uint256 amount) public {
@@ -407,12 +429,11 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
407429
if (amount == 0) {
408430
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
409431
hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector);
410-
if (from == address(this)) {
411-
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId);
412-
} else {
413-
gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit, keyId);
414-
}
432+
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId);
415433
} else {
434+
// Note: from parameter is only used in event expectations, not in actual calls
435+
// The depositERC20 function always uses msg.sender for actual deposits
436+
416437
// emit QueueTransaction from L1MessageQueueV2
417438
{
418439
hevm.expectEmit(true, true, false, true);
@@ -434,11 +455,7 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
434455
uint256 feeVaultBalance = address(feeVault).balance;
435456
assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0);
436457
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
437-
if (from == address(this)) {
438-
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId);
439-
} else {
440-
gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit, keyId);
441-
}
458+
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId);
442459
assertEq(amount + gatewayBalance, l1Token.balanceOf(address(gateway)));
443460
assertEq(feeVaultBalance, address(feeVault).balance);
444461
assertGt(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0);
@@ -456,7 +473,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
456473
address(messenger),
457474
address(template),
458475
address(factory),
459-
address(rollup)
476+
address(rollup),
477+
address(wethGateway)
460478
)
461479
)
462480
);

src/test/validium/L1WETHGatewayValidium.t.sol

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,24 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase {
6464
counterpartGateway = new L2StandardERC20Gateway(address(1), address(1), address(1), address(factory));
6565

6666
// Deploy L1 contracts
67-
gateway = _deployGateway(address(l1Messenger));
67+
gateway = L1ERC20GatewayValidium(_deployProxy(address(0)));
6868
wethGateway = new L1WETHGatewayValidium(address(weth), address(gateway));
6969

70+
// Upgrade gateway implementation with actual wethGateway address
71+
admin.upgrade(
72+
ITransparentUpgradeableProxy(address(gateway)),
73+
address(
74+
new L1ERC20GatewayValidium(
75+
address(counterpartGateway),
76+
address(l1Messenger),
77+
address(template),
78+
address(factory),
79+
address(rollup),
80+
address(wethGateway)
81+
)
82+
)
83+
);
84+
7085
// Initialize L1 contracts
7186
gateway.initialize();
7287

@@ -146,21 +161,4 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase {
146161
assertGt(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0);
147162
}
148163
}
149-
150-
function _deployGateway(address messenger) internal returns (L1ERC20GatewayValidium _gateway) {
151-
_gateway = L1ERC20GatewayValidium(_deployProxy(address(0)));
152-
153-
admin.upgrade(
154-
ITransparentUpgradeableProxy(address(_gateway)),
155-
address(
156-
new L1ERC20GatewayValidium(
157-
address(counterpartGateway),
158-
address(messenger),
159-
address(template),
160-
address(factory),
161-
address(rollup)
162-
)
163-
)
164-
);
165-
}
166164
}

src/validium/L1ERC20GatewayValidium.sol

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
3434
/// @dev Error thrown when amount is zero.
3535
error ErrorAmountIsZero();
3636

37+
/// @dev Error thrown when caller is not the WETH gateway.
38+
error ErrorCallerNotWethGateway();
39+
3740
/*************
3841
* Constants *
3942
*************/
@@ -47,6 +50,9 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
4750
/// @notice The address of ScrollChainValidium contract in L2.
4851
address public immutable scrollChainValidium;
4952

53+
/// @notice The address of L1WETHGatewayValidium contract.
54+
address public immutable wethGateway;
55+
5056
/*************
5157
* Variables *
5258
*************/
@@ -67,18 +73,22 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
6773
/// @param _messenger The address of `L1ScrollMessenger` contract in L1.
6874
/// @param _l2TokenImplementation The address of `ScrollStandardERC20` implementation in L2.
6975
/// @param _l2TokenFactory The address of `ScrollStandardERC20Factory` contract in L2.
76+
/// @param _scrollChainValidium The address of `ScrollChainValidium` contract in L2.
77+
/// @param _wethGateway The address of `L1WETHGatewayValidium` contract.
7078
constructor(
7179
address _counterpart,
7280
address _messenger,
7381
address _l2TokenImplementation,
7482
address _l2TokenFactory,
75-
address _scrollChainValidium
83+
address _scrollChainValidium,
84+
address _wethGateway
7685
) ScrollGatewayBase(_counterpart, address(0), _messenger) {
7786
_disableInitializers();
7887

7988
l2TokenImplementation = _l2TokenImplementation;
8089
l2TokenFactory = _l2TokenFactory;
8190
scrollChainValidium = _scrollChainValidium;
91+
wethGateway = _wethGateway;
8292
}
8393

8494
/// @notice Initialize the storage of L1ERC20GatewayValidium.
@@ -123,6 +133,9 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
123133
uint256 _gasLimit,
124134
uint256 _keyId
125135
) external payable override {
136+
// Only the WETH gateway can call this function to preserve the real sender
137+
if (_msgSender() != wethGateway) revert ErrorCallerNotWethGateway();
138+
126139
_deposit(_token, _realSender, _to, _amount, new bytes(0), _gasLimit, _keyId);
127140
}
128141

0 commit comments

Comments
 (0)