mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-04-23 03:00:50 -04:00
draft version of batch token bridge
This commit is contained in:
@@ -67,7 +67,7 @@ Commit a batch of transactions on layer 1.
|
||||
function committedBatches(uint256) external view returns (bytes32)
|
||||
```
|
||||
|
||||
Return the batch hash of a committed batch.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ Return the batch hash of a committed batch.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _0 | bytes32 | undefined |
|
||||
| _0 | bytes32 | The batch hash of a committed batch. |
|
||||
|
||||
### finalizeBatchWithProof
|
||||
|
||||
@@ -130,7 +130,7 @@ Finalize a committed batch (with blob) on layer 1.
|
||||
function finalizedStateRoots(uint256) external view returns (bytes32)
|
||||
```
|
||||
|
||||
Return the state root of a committed batch.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ Return the state root of a committed batch.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _0 | bytes32 | undefined |
|
||||
| _0 | bytes32 | The state root of a committed batch. |
|
||||
|
||||
### importGenesisBatch
|
||||
|
||||
@@ -160,8 +160,8 @@ Import layer 2 genesis block
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _batchHeader | bytes | undefined |
|
||||
| _stateRoot | bytes32 | undefined |
|
||||
| _batchHeader | bytes | The header of the genesis batch. |
|
||||
| _stateRoot | bytes32 | The state root of the genesis block. |
|
||||
|
||||
### initialize
|
||||
|
||||
@@ -187,7 +187,7 @@ Initialize the storage of ScrollChain.
|
||||
function isBatchFinalized(uint256 _batchIndex) external view returns (bool)
|
||||
```
|
||||
|
||||
Return whether the batch is finalized by batch index.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ Return whether the batch is finalized by batch index.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _0 | bool | undefined |
|
||||
| _0 | bool | Whether the batch is finalized by batch index. |
|
||||
|
||||
### isProver
|
||||
|
||||
@@ -253,7 +253,7 @@ Whether an account is a sequencer.
|
||||
function lastFinalizedBatchIndex() external view returns (uint256)
|
||||
```
|
||||
|
||||
The latest finalized batch index.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ The latest finalized batch index.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _0 | uint256 | undefined |
|
||||
| _0 | uint256 | The latest finalized batch index. |
|
||||
|
||||
### layer2ChainId
|
||||
|
||||
@@ -480,7 +480,7 @@ The address of RollupVerifier.
|
||||
function withdrawRoots(uint256) external view returns (bytes32)
|
||||
```
|
||||
|
||||
Return the message root of a committed batch.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -494,7 +494,7 @@ Return the message root of a committed batch.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _0 | bytes32 | undefined |
|
||||
| _0 | bytes32 | The message root of a committed batch. |
|
||||
|
||||
|
||||
|
||||
|
||||
358
contracts/src/batch-bridge/L1BatchBridgeGateway.sol
Normal file
358
contracts/src/batch-bridge/L1BatchBridgeGateway.sol
Normal file
@@ -0,0 +1,358 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity =0.8.24;
|
||||
|
||||
import {AccessControlEnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
|
||||
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
|
||||
|
||||
import {IL1ERC20Gateway} from "../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import {IL1GatewayRouter} from "../L1/gateways/IL1GatewayRouter.sol";
|
||||
import {IL1MessageQueue} from "../L1/rollup/IL1MessageQueue.sol";
|
||||
import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol";
|
||||
|
||||
import {L2BatchBridgeGateway} from "./L2BatchBridgeGateway.sol";
|
||||
|
||||
/// @title L1BatchBridgeGateway
|
||||
contract L1BatchBridgeGateway is AccessControlEnumerableUpgradeable, ReentrancyGuardUpgradeable {
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
event Deposit(address indexed token, uint256 indexed phase, uint256 amount, uint256 fee);
|
||||
|
||||
event BatchBridge(address indexed l1Token, address indexed l2Token, uint256 indexed phase);
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice The role for batch deposit keeper.
|
||||
bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
|
||||
|
||||
/// @notice The safe gas limit for batch bridge.
|
||||
uint256 private constant SAFE_BATCH_BRIDGE_GAS_LIMIT = 1000000;
|
||||
|
||||
/// @notice The address of corresponding `L2BatchBridgeGateway` contract.
|
||||
address public immutable counterpart;
|
||||
|
||||
/// @notice The address of `L1GatewayRouter` contract.
|
||||
address public immutable router;
|
||||
|
||||
/// @notice The address of `L1ScrollMessenger` contract.
|
||||
address public immutable messenger;
|
||||
|
||||
/// @notice The address of `L1MessageQueue` contract.
|
||||
address public immutable queue;
|
||||
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
|
||||
/// @notice The setting for batch token bridging.
|
||||
/// @dev Compiler will pack this into a single `bytes32`.
|
||||
/// @param feeAmountPerTx The amount of fee charged for each deposit.
|
||||
/// @param minAmountPerTx The minimum amount of token for each deposit.
|
||||
/// @param maxTxPerBatch The maximum number of deposit in each batch.
|
||||
/// @param maxDelayPerBatch The maximum number of seconds to wait in each batch.
|
||||
/// @param safeBridgeGasLimit The safe bridge gas limit for bridging token from L1 to L2.
|
||||
struct BatchSetting {
|
||||
uint96 feeAmountPerTx;
|
||||
uint96 minAmountPerTx;
|
||||
uint16 maxTxPerBatch;
|
||||
uint24 maxDelayPerBatch;
|
||||
uint24 safeBridgeGasLimit;
|
||||
}
|
||||
|
||||
/// @dev Compiler will pack this into two `bytes32`.
|
||||
/// @param amount The total amount of token to deposit in current phase.
|
||||
/// @param firstDepositTimestamp The timestamp of first deposit.
|
||||
/// @param numDeposits The total number of deposits in current phase.
|
||||
/// @param hash The hash of current phase.
|
||||
/// Suppose there are `n` deposits in current phase with `senders` and `amounts`. The hash is computed as
|
||||
/// ```text
|
||||
/// hash[0] = concat(token, phase_index)
|
||||
/// hash[i] = keccak(hash[i-1], concat(senders[i], amounts[i]))
|
||||
/// ```
|
||||
/// The type of `token` and `senders` is `address`, while The type of `phase_index` and `amounts[i]` is `uint96`.
|
||||
/// In current way, the hash of each phase should be different.
|
||||
struct PhaseState {
|
||||
uint256 amount;
|
||||
uint256 firstDepositTimestamp;
|
||||
uint256 numDeposits;
|
||||
bytes32 hash;
|
||||
}
|
||||
|
||||
/// @dev Compiler will pack this into a single `bytes32`.
|
||||
struct TokenState {
|
||||
uint128 pending;
|
||||
uint64 currentPhaseIndex;
|
||||
uint64 pendingPhaseIndex;
|
||||
}
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from token address to batch deposit setting.
|
||||
/// @dev The `address(0)` is used for ETH.
|
||||
mapping(address => BatchSetting) public settings;
|
||||
|
||||
/// @notice Mapping from token address to phase index to phase state.
|
||||
/// @dev The `address(0)` is used for ETH.
|
||||
mapping(address => mapping(uint256 => PhaseState)) public phases;
|
||||
|
||||
/// @notice Mapping from token address to token state.
|
||||
/// @dev The `address(0)` is used for ETH.
|
||||
mapping(address => TokenState) public tokens;
|
||||
|
||||
/// @notice The address of fee vault.
|
||||
address public feeVault;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _queue
|
||||
) {
|
||||
_disableInitializers();
|
||||
|
||||
counterpart = _counterpart;
|
||||
router = _router;
|
||||
messenger = _messenger;
|
||||
queue = _queue;
|
||||
}
|
||||
|
||||
function initialize(address _feeVault) external initializer {
|
||||
__Context_init(); // from ContextUpgradeable
|
||||
__ERC165_init(); // from ERC165Upgradeable
|
||||
__AccessControl_init(); // from AccessControlUpgradeable
|
||||
__AccessControlEnumerable_init(); // from AccessControlEnumerableUpgradeable
|
||||
__ReentrancyGuard_init(); // from ReentrancyGuardUpgradeable
|
||||
|
||||
feeVault = _feeVault;
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
/// @notice Deposit ETH.
|
||||
function depositETH() external payable {
|
||||
// no safe cast check here, since no one has so much ETH yet.
|
||||
_deposit(address(0), _msgSender(), uint96(msg.value));
|
||||
}
|
||||
|
||||
/// @notice Deposit ERC20 token.
|
||||
///
|
||||
/// @param token The address of token.
|
||||
/// @param amount The amount of token to deposit. We use type `uint96`, since it is enough for most of the major tokens.
|
||||
function depositERC20(address token, uint96 amount) external {
|
||||
if (token == address(0)) revert();
|
||||
|
||||
// common practice to handle fee on transfer token.
|
||||
uint256 beforeBalance = IERC20Upgradeable(token).balanceOf(address(this));
|
||||
IERC20Upgradeable(token).safeTransferFrom(_msgSender(), address(this), amount);
|
||||
amount = uint96(IERC20Upgradeable(token).balanceOf(address(this)) - beforeBalance);
|
||||
|
||||
_deposit(token, _msgSender(), amount);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Add or update the batch deposit setting for the given token.
|
||||
/// @param token The address of token to update.
|
||||
/// @param newSetting The new setting.
|
||||
function setTokenSetting(address token, BatchSetting memory newSetting) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
settings[token] = newSetting;
|
||||
}
|
||||
|
||||
/// @notice Initiate the batch bridge of current pending phase.
|
||||
/// @param token The address of the token.
|
||||
function batchBridge(address token) external payable onlyRole(KEEPER_ROLE) {
|
||||
BatchSetting memory cachedBatchSetting = settings[token];
|
||||
TokenState memory cachedTokenState = tokens[token];
|
||||
_tryFinalzeCurrentPhase(token, cachedBatchSetting, cachedTokenState);
|
||||
|
||||
// no phase to bridge
|
||||
if (cachedTokenState.currentPhaseIndex == cachedTokenState.pendingPhaseIndex) revert();
|
||||
|
||||
// check bridge fee
|
||||
uint256 depositFee = IL1MessageQueue(queue).estimateCrossDomainMessageFee(
|
||||
cachedBatchSetting.safeBridgeGasLimit
|
||||
);
|
||||
uint256 batchBridgeFee = IL1MessageQueue(queue).estimateCrossDomainMessageFee(SAFE_BATCH_BRIDGE_GAS_LIMIT);
|
||||
if (msg.value < depositFee + batchBridgeFee) revert();
|
||||
|
||||
// take accumulated fee
|
||||
uint256 accumulatedFee;
|
||||
if (token == address(0)) {
|
||||
accumulatedFee = address(this).balance - msg.value - cachedTokenState.pending;
|
||||
} else {
|
||||
accumulatedFee = IERC20Upgradeable(token).balanceOf(address(this)) - cachedTokenState.pending;
|
||||
}
|
||||
_transferToken(token, feeVault, accumulatedFee);
|
||||
|
||||
// deposit token to L2
|
||||
PhaseState memory cachedPhaseState = phases[token][cachedTokenState.pendingPhaseIndex];
|
||||
address l2Token;
|
||||
if (token == address(0)) {
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: cachedPhaseState.amount + depositFee}(
|
||||
counterpart,
|
||||
cachedPhaseState.amount,
|
||||
new bytes(0),
|
||||
cachedBatchSetting.safeBridgeGasLimit
|
||||
);
|
||||
} else {
|
||||
address gateway = IL1GatewayRouter(router).getERC20Gateway(token);
|
||||
l2Token = IL1ERC20Gateway(gateway).getL2ERC20Address(token);
|
||||
IERC20Upgradeable(token).safeApprove(gateway, 0);
|
||||
IERC20Upgradeable(token).safeApprove(gateway, cachedPhaseState.amount);
|
||||
IL1ERC20Gateway(gateway).depositERC20{value: depositFee}(
|
||||
token,
|
||||
cachedPhaseState.amount,
|
||||
cachedBatchSetting.safeBridgeGasLimit
|
||||
);
|
||||
}
|
||||
|
||||
// notify `L2BatchBridgeGateway`
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: batchBridgeFee}(
|
||||
counterpart,
|
||||
0,
|
||||
abi.encodeCall(
|
||||
L2BatchBridgeGateway.finalizeBatchBridge,
|
||||
(token, l2Token, cachedTokenState.pendingPhaseIndex, cachedPhaseState.hash)
|
||||
),
|
||||
SAFE_BATCH_BRIDGE_GAS_LIMIT
|
||||
);
|
||||
|
||||
emit BatchBridge(token, l2Token, cachedTokenState.pendingPhaseIndex);
|
||||
|
||||
// update token state
|
||||
unchecked {
|
||||
cachedTokenState.pending -= uint128(cachedPhaseState.amount);
|
||||
cachedTokenState.pendingPhaseIndex += 1;
|
||||
}
|
||||
tokens[token] = cachedTokenState;
|
||||
|
||||
// refund keeper fee
|
||||
if (msg.value > depositFee + batchBridgeFee) {
|
||||
unchecked {
|
||||
_transferToken(address(0), _msgSender(), msg.value - depositFee - batchBridgeFee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to deposit token.
|
||||
/// @param token The address of token to deposit.
|
||||
/// @param sender The address of token sender.
|
||||
/// @param amount The amount of token to deposit.
|
||||
function _deposit(
|
||||
address token,
|
||||
address sender,
|
||||
uint96 amount
|
||||
) internal nonReentrant {
|
||||
BatchSetting memory cachedBatchSetting = settings[token];
|
||||
TokenState memory cachedTokenState = tokens[token];
|
||||
_tryFinalzeCurrentPhase(token, cachedBatchSetting, cachedTokenState);
|
||||
|
||||
PhaseState memory cachedPhaseState = phases[token][cachedTokenState.currentPhaseIndex];
|
||||
if (amount < cachedBatchSetting.minAmountPerTx) revert();
|
||||
|
||||
// deduct fee and update cached state
|
||||
unchecked {
|
||||
amount -= cachedBatchSetting.feeAmountPerTx;
|
||||
cachedTokenState.pending += amount;
|
||||
cachedPhaseState.amount += amount;
|
||||
cachedPhaseState.numDeposits += 1;
|
||||
}
|
||||
|
||||
// compute the hash chain
|
||||
bytes32 node;
|
||||
assembly {
|
||||
node := add(shl(96, sender), amount)
|
||||
}
|
||||
if (cachedPhaseState.hash == bytes32(0)) {
|
||||
uint256 currentPhaseIndex = cachedTokenState.currentPhaseIndex;
|
||||
bytes32 initialNode;
|
||||
assembly {
|
||||
initialNode := add(shl(96, token), currentPhaseIndex)
|
||||
}
|
||||
// this is first tx in this phase
|
||||
cachedPhaseState.hash = _efficientHash(initialNode, node);
|
||||
cachedPhaseState.firstDepositTimestamp = block.timestamp;
|
||||
} else {
|
||||
cachedPhaseState.hash = _efficientHash(cachedPhaseState.hash, node);
|
||||
}
|
||||
|
||||
phases[token][cachedTokenState.currentPhaseIndex] = cachedPhaseState;
|
||||
tokens[token] = cachedTokenState;
|
||||
}
|
||||
|
||||
/// @dev Internal function to finalze current phase.
|
||||
/// This function may change the value of `cachedTokenState`, which can be used in later operation.
|
||||
/// @param token The address of token to finalze.
|
||||
/// @param cachedBatchSetting The cached batch setting in memory.
|
||||
/// @param cachedTokenState The cached token state in memory.
|
||||
function _tryFinalzeCurrentPhase(
|
||||
address token,
|
||||
BatchSetting memory cachedBatchSetting,
|
||||
TokenState memory cachedTokenState
|
||||
) internal view {
|
||||
if (cachedBatchSetting.maxTxPerBatch == 0) revert();
|
||||
PhaseState memory cachedPhaseState = phases[token][cachedTokenState.currentPhaseIndex];
|
||||
if (cachedPhaseState.numDeposits == 0) return;
|
||||
|
||||
// finalize current phase when `maxTxPerBatch` or `maxDelayPerBatch` reached.
|
||||
if (
|
||||
cachedPhaseState.numDeposits == cachedBatchSetting.maxTxPerBatch ||
|
||||
block.timestamp - cachedPhaseState.firstDepositTimestamp > cachedBatchSetting.maxDelayPerBatch
|
||||
) {
|
||||
cachedTokenState.currentPhaseIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Internal function to compute `keccak256(concat(a, b))`.
|
||||
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
mstore(0x00, a)
|
||||
mstore(0x20, b)
|
||||
value := keccak256(0x00, 0x40)
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Internal function to transfer token, including ETH.
|
||||
/// @param token The address of token.
|
||||
/// @param receiver The address of token receiver.
|
||||
/// @param amount The amount of token to transfer.
|
||||
function _transferToken(
|
||||
address token,
|
||||
address receiver,
|
||||
uint256 amount
|
||||
) private {
|
||||
if (token == address(0)) {
|
||||
(bool success, ) = receiver.call{value: amount}("");
|
||||
if (!success) revert();
|
||||
} else {
|
||||
IERC20Upgradeable(token).safeTransfer(receiver, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
189
contracts/src/batch-bridge/L2BatchBridgeGateway.sol
Normal file
189
contracts/src/batch-bridge/L2BatchBridgeGateway.sol
Normal file
@@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity =0.8.24;
|
||||
|
||||
import {AccessControlEnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
|
||||
import {IL2ScrollMessenger} from "../L2/IL2ScrollMessenger.sol";
|
||||
|
||||
/// @title L2BatchBridgeGateway
|
||||
contract L2BatchBridgeGateway is AccessControlEnumerableUpgradeable {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC20 token is updated.
|
||||
/// @param l2Token The address of corresponding ERC20 token in layer 2.
|
||||
/// @param oldL1Token The address of the old corresponding ERC20 token in layer 1.
|
||||
/// @param newL1Token The address of the new corresponding ERC20 token in layer 1.
|
||||
event UpdateTokenMapping(address indexed l2Token, address indexed oldL1Token, address indexed newL1Token);
|
||||
|
||||
event FinalizeBatchBridge(address indexed l1Token, address indexed l2Token, uint256 indexed phase);
|
||||
|
||||
event DistributeFailed(address l2Token, uint256 phase, address receiver, uint256 amount);
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice The role for batch deposit keeper.
|
||||
bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
|
||||
|
||||
/// @notice The safe gas limit for ETH transfer
|
||||
uint256 private constant SAFE_ETH_TRANSFER_GAS_LIMIT = 100000;
|
||||
|
||||
/// @notice The address of corresponding `L1BatchBridgeGateway` contract.
|
||||
address public immutable counterpart;
|
||||
|
||||
/// @notice The address of corresponding `L2ScrollMessenger` contract.
|
||||
address public immutable messenger;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from l2 token address to l1 token address.
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/// @notice Mapping from L2 token address to phase index to phase hash.
|
||||
mapping(address => mapping(uint256 => bytes32)) public phaseHashes;
|
||||
|
||||
mapping(address => uint256) public failedAmount;
|
||||
|
||||
mapping(bytes32 => bool) public isDistributed;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(address _counterpart, address _messenger) {
|
||||
_disableInitializers();
|
||||
|
||||
counterpart = _counterpart;
|
||||
messenger = _messenger;
|
||||
}
|
||||
|
||||
function initialize() external initializer {
|
||||
__Context_init(); // from ContextUpgradeable
|
||||
__ERC165_init(); // from ERC165Upgradeable
|
||||
__AccessControl_init(); // from AccessControlUpgradeable
|
||||
__AccessControlEnumerable_init(); // from AccessControlEnumerableUpgradeable
|
||||
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
receive() external payable {
|
||||
if (_msgSender() != messenger) revert();
|
||||
}
|
||||
|
||||
function finalizeBatchBridge(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
uint256 phase,
|
||||
bytes32 hash
|
||||
) external {
|
||||
if (tokenMapping[l2Token] != l1Token) revert();
|
||||
if (_msgSender() != messenger) revert();
|
||||
if (counterpart != IL2ScrollMessenger(messenger).xDomainMessageSender()) revert();
|
||||
|
||||
phaseHashes[l2Token][phase] = hash;
|
||||
|
||||
emit FinalizeBatchBridge(l1Token, l2Token, phase);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param l2Token The address of corresponding ERC20 token on layer 2.
|
||||
/// @param l1Token The address of ERC20 token on layer 1.
|
||||
function updateTokenMapping(address l2Token, address l1Token) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
address oldL1Token = tokenMapping[l2Token];
|
||||
tokenMapping[l2Token] = l1Token;
|
||||
|
||||
emit UpdateTokenMapping(l2Token, oldL1Token, l1Token);
|
||||
}
|
||||
|
||||
function withdrawFailedAmount(address token, address receiver) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
uint256 amount = failedAmount[token];
|
||||
if (amount == 0) revert();
|
||||
|
||||
_transferToken(token, receiver, amount);
|
||||
}
|
||||
|
||||
/// @notice Distribute deposited token to corresponding receivers.
|
||||
/// @param l2Token The address of L2 token.
|
||||
/// @param phase The phase to distribute.
|
||||
/// @param nodes The list of encoded L1 deposits.
|
||||
function distribute(
|
||||
address l2Token,
|
||||
uint256 phase,
|
||||
bytes32[] memory nodes
|
||||
) external onlyRole(KEEPER_ROLE) {
|
||||
address l1Token = tokenMapping[l2Token];
|
||||
bytes32 hash;
|
||||
assembly {
|
||||
hash := add(shl(96, l1Token), phase)
|
||||
}
|
||||
for (uint256 i = 0; i < nodes.length; i++) {
|
||||
hash = _efficientHash(hash, nodes[i]);
|
||||
}
|
||||
if (phaseHashes[l2Token][phase] != hash) revert();
|
||||
if (isDistributed[hash]) revert();
|
||||
|
||||
// do transfer and allow failure to avoid DDOS attack
|
||||
for (uint256 i = 0; i < nodes.length; i++) {
|
||||
address receiver = address(uint160(uint256(nodes[i]) >> 96));
|
||||
uint256 amount = uint256(nodes[i]) & 0xffffffffffffffffffffffff;
|
||||
if (!_transferToken(l2Token, receiver, amount)) {
|
||||
failedAmount[l2Token] += amount;
|
||||
|
||||
emit DistributeFailed(l2Token, phase, receiver, amount);
|
||||
}
|
||||
}
|
||||
|
||||
isDistributed[hash] = true;
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to compute `keccak256(concat(a, b))`.
|
||||
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
mstore(0x00, a)
|
||||
mstore(0x20, b)
|
||||
value := keccak256(0x00, 0x40)
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Internal function to transfer token, including ETH.
|
||||
/// @param token The address of token.
|
||||
/// @param receiver The address of token receiver.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @return success Whether the transfer is successful.
|
||||
function _transferToken(
|
||||
address token,
|
||||
address receiver,
|
||||
uint256 amount
|
||||
) private returns (bool success) {
|
||||
if (token == address(0)) {
|
||||
(success, ) = receiver.call{value: amount, gas: SAFE_ETH_TRANSFER_GAS_LIMIT}("");
|
||||
} else {
|
||||
try IERC20Upgradeable(token).transfer(receiver, amount) {
|
||||
success = true;
|
||||
} catch {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user