Skip to content

Commit 94db957

Browse files
committed
allow user claim; allow claim and call
1 parent cbca22b commit 94db957

File tree

1 file changed

+173
-15
lines changed

1 file changed

+173
-15
lines changed

src/validium/FastWithdrawVault.sol

Lines changed: 173 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/crypt
1212
import {ECDSAUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
1313

1414
import {IL1ERC20Gateway} from "../L1/gateways/IL1ERC20Gateway.sol";
15+
import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol";
16+
import {IScrollChain} from "../L1/rollup/IScrollChain.sol";
1517
import {IWETH} from "../interfaces/IWETH.sol";
18+
import {IScrollGatewayCallback} from "../libraries/callbacks/IScrollGatewayCallback.sol";
19+
import {IScrollGateway} from "../libraries/gateway/IScrollGateway.sol";
20+
import {WithdrawTrieVerifier} from "../libraries/verifier/WithdrawTrieVerifier.sol";
1621

1722
/// @title FastWithdrawVault
1823
/// @notice The vault for fast withdrawals from L2 to L1.
@@ -24,7 +29,12 @@ import {IWETH} from "../interfaces/IWETH.sol";
2429
/// also sending the proper amount of tokens.
2530
/// 2. The sequencer signs the withdraw request and sends it to the vault.
2631
/// 3. The vault verifies the signature and the message hash, and then withdraws the tokens from L2 to L1.
27-
contract FastWithdrawVault is AccessControlUpgradeable, ReentrancyGuardUpgradeable, EIP712Upgradeable {
32+
contract FastWithdrawVault is
33+
AccessControlUpgradeable,
34+
ReentrancyGuardUpgradeable,
35+
EIP712Upgradeable,
36+
IScrollGatewayCallback
37+
{
2838
using SafeERC20Upgradeable for IERC20Upgradeable;
2939

3040
/**********
@@ -46,6 +56,15 @@ contract FastWithdrawVault is AccessControlUpgradeable, ReentrancyGuardUpgradeab
4656
/// @dev Thrown when the given withdraw message has already been processed.
4757
error ErrorWithdrawAlreadyProcessed();
4858

59+
/// @dev Thrown when the given message is not valid.
60+
error ErrorInvalidMessage();
61+
62+
/// @dev Thrown when the given batch is not finalized.
63+
error ErrorBatchNotFinalized();
64+
65+
/// @dev Thrown when the given proof is invalid.
66+
error ErrorInvalidProof();
67+
4968
/*************
5069
* Constants *
5170
*************/
@@ -68,6 +87,27 @@ contract FastWithdrawVault is AccessControlUpgradeable, ReentrancyGuardUpgradeab
6887
/// @notice The address of the `L1ERC20Gateway` contract.
6988
address public immutable gateway;
7089

90+
/// @notice The address of the `ScrollChain` contract.
91+
address public immutable rollup;
92+
93+
/***********
94+
* Structs *
95+
***********/
96+
97+
/// @notice The struct of the validium cross domain message.
98+
/// @param from The address of the sender of the message.
99+
/// @param to The address of the recipient of the message.
100+
/// @param value The msg.value passed to the message call.
101+
/// @param nonce The nonce of the message to avoid replay attack.
102+
/// @param message The content of the message.
103+
struct ValidiumCrossDomainMessage {
104+
address from;
105+
address to;
106+
uint256 value;
107+
uint256 nonce;
108+
bytes message;
109+
}
110+
71111
/*********************
72112
* Storage Variables *
73113
*********************/
@@ -108,6 +148,52 @@ contract FastWithdrawVault is AccessControlUpgradeable, ReentrancyGuardUpgradeab
108148

109149
receive() external payable {}
110150

151+
/// @notice Fast withdraw some tokens from L2 to L1 with proof from sequencer.
152+
/// @param message The content of the message.
153+
/// @param proof The proof used to verify the correctness of the transaction.
154+
function claimWithProof(ValidiumCrossDomainMessage calldata message, IL1ScrollMessenger.L2MessageProof memory proof)
155+
external
156+
nonReentrant
157+
{
158+
if (message.to != gateway) revert ErrorInvalidMessage();
159+
if (message.from != IScrollGateway(gateway).counterpart()) revert ErrorInvalidMessage();
160+
161+
bytes32 messageHash = keccak256(
162+
_encodeXDomainCalldata(message.from, message.to, message.value, message.nonce, message.message)
163+
);
164+
if (isWithdrawn[messageHash]) revert ErrorWithdrawAlreadyProcessed();
165+
isWithdrawn[messageHash] = true;
166+
167+
// verify proof
168+
{
169+
if (!IScrollChain(rollup).isBatchFinalized(proof.batchIndex)) {
170+
revert ErrorBatchNotFinalized();
171+
}
172+
bytes32 _messageRoot = IScrollChain(rollup).withdrawRoots(proof.batchIndex);
173+
if (!WithdrawTrieVerifier.verifyMerkleProof(_messageRoot, messageHash, message.nonce, proof.merkleProof)) {
174+
revert ErrorInvalidProof();
175+
}
176+
}
177+
178+
// decode actual validium sender from message.
179+
(address l1Token, address l2Token, address sender, address receiver, uint256 amount, ) = abi.decode(
180+
message.message[4:],
181+
(address, address, address, address, uint256, bytes)
182+
);
183+
if (IL1ERC20Gateway(gateway).getL2ERC20Address(l1Token) != l2Token) revert ErrorInvalidMessage();
184+
if (receiver != address(this)) revert ErrorInvalidMessage();
185+
186+
// transfer tokens to sender
187+
if (l1Token == weth) {
188+
IWETH(weth).withdraw(amount);
189+
AddressUpgradeable.sendValue(payable(sender), amount);
190+
} else {
191+
IERC20Upgradeable(l1Token).safeTransfer(sender, amount);
192+
}
193+
194+
emit Withdraw(l1Token, l2Token, sender, amount, messageHash);
195+
}
196+
111197
/// @notice Fast withdraw some tokens from L2 to L1 with signature from sequencer.
112198
/// @param l1Token The address of the L1 token.
113199
/// @param to The address of the recipient.
@@ -121,23 +207,34 @@ contract FastWithdrawVault is AccessControlUpgradeable, ReentrancyGuardUpgradeab
121207
bytes32 messageHash,
122208
bytes memory signature
123209
) external nonReentrant {
124-
address l2Token = IL1ERC20Gateway(gateway).getL2ERC20Address(l1Token);
125-
bytes32 structHash = keccak256(abi.encode(_WITHDRAW_TYPEHASH, l1Token, l2Token, to, amount, messageHash));
126-
if (isWithdrawn[structHash]) revert ErrorWithdrawAlreadyProcessed();
127-
isWithdrawn[structHash] = true;
210+
_claim(l1Token, to, amount, messageHash, signature);
211+
}
128212

129-
bytes32 hash = _hashTypedDataV4(structHash);
130-
address signer = ECDSAUpgradeable.recover(hash, signature);
131-
_checkRole(SEQUENCER_ROLE, signer);
213+
/// @notice Fast withdraw some tokens from L2 to L1 with signature from sequencer and call the target contract.
214+
/// @param l1Token The address of the L1 token.
215+
/// @param to The address of the recipient.
216+
/// @param amount The amount of tokens to withdraw.
217+
/// @param messageHash The hash of the message, which is the corresponding withdraw message hash in L2.
218+
/// @param signature The signature of the message from sequencer.
219+
/// @param data The data to call the target contract.
220+
function claimAndCall(
221+
address l1Token,
222+
address to,
223+
uint256 amount,
224+
bytes32 messageHash,
225+
bytes memory signature,
226+
address callbackTo,
227+
bytes memory data
228+
) external payable nonReentrant {
229+
_claim(l1Token, to, amount, messageHash, signature);
132230

133-
if (l1Token == weth) {
134-
IWETH(weth).withdraw(amount);
135-
AddressUpgradeable.sendValue(payable(to), amount);
136-
} else {
137-
IERC20Upgradeable(l1Token).safeTransfer(to, amount);
138-
}
231+
// @note callbackTo is the address of the target contract to call.
232+
IScrollGatewayCallback(callbackTo).onScrollGatewayCallback(data);
233+
}
139234

140-
emit Withdraw(l1Token, l2Token, to, amount, messageHash);
235+
/// @inheritdoc IScrollGatewayCallback
236+
function onScrollGatewayCallback(bytes calldata _data) external override {
237+
// noop
141238
}
142239

143240
/************************
@@ -155,4 +252,65 @@ contract FastWithdrawVault is AccessControlUpgradeable, ReentrancyGuardUpgradeab
155252
) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
156253
IERC20Upgradeable(token).safeTransfer(recipient, amount);
157254
}
255+
256+
/**********************
257+
* Internal Functions *
258+
**********************/
259+
260+
/// @dev Internal function to claim the fast withdraw.
261+
/// @param l1Token The address of the L1 token.
262+
/// @param to The address of the recipient.
263+
/// @param amount The amount of tokens to withdraw.
264+
/// @param messageHash The hash of the message, which is the corresponding withdraw message hash in L2.
265+
/// @param signature The signature of the message from sequencer.
266+
function _claim(
267+
address l1Token,
268+
address to,
269+
uint256 amount,
270+
bytes32 messageHash,
271+
bytes memory signature
272+
) internal {
273+
address l2Token = IL1ERC20Gateway(gateway).getL2ERC20Address(l1Token);
274+
bytes32 structHash = keccak256(abi.encode(_WITHDRAW_TYPEHASH, l1Token, l2Token, to, amount, messageHash));
275+
if (isWithdrawn[structHash]) revert ErrorWithdrawAlreadyProcessed();
276+
isWithdrawn[structHash] = true;
277+
278+
bytes32 hash = _hashTypedDataV4(structHash);
279+
address signer = ECDSAUpgradeable.recover(hash, signature);
280+
_checkRole(SEQUENCER_ROLE, signer);
281+
282+
if (l1Token == weth) {
283+
IWETH(weth).withdraw(amount);
284+
AddressUpgradeable.sendValue(payable(to), amount);
285+
} else {
286+
IERC20Upgradeable(l1Token).safeTransfer(to, amount);
287+
}
288+
289+
emit Withdraw(l1Token, l2Token, to, amount, messageHash);
290+
}
291+
292+
/// @dev Internal function to generate the correct cross domain calldata for a message.
293+
/// @param _sender Message sender address.
294+
/// @param _target Target contract address.
295+
/// @param _value The amount of ETH pass to the target.
296+
/// @param _messageNonce Nonce for the provided message.
297+
/// @param _message Message to send to the target.
298+
/// @return ABI encoded cross domain calldata.
299+
function _encodeXDomainCalldata(
300+
address _sender,
301+
address _target,
302+
uint256 _value,
303+
uint256 _messageNonce,
304+
bytes memory _message
305+
) internal pure returns (bytes memory) {
306+
return
307+
abi.encodeWithSignature(
308+
"relayMessage(address,address,uint256,uint256,bytes)",
309+
_sender,
310+
_target,
311+
_value,
312+
_messageNonce,
313+
_message
314+
);
315+
}
158316
}

0 commit comments

Comments
 (0)