Transaction Hash:
Block:
18431293 at Oct-26-2023 02:04:23 AM +UTC
Transaction Fee:
0.00319391546177282 ETH
$6.89
Gas Used:
135,502 Gas / 23.57098391 Gwei
Emitted Events:
| 248 |
Proxy.0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32( 0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32, 0x00000000000000000000000036bde71c97b33cc4729cf772ae268934f7ab70b2, 0x0000000000000000000000004200000000000000000000000000000000000007, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000020, 000000000000000000000000000000000000000000000000000000000000020d, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000006b8c400d764ad0b00010000000000000000000000000000000000, 0000000000000000000000fecf000000000000000000000000a45df1a388049f, b8d76e72d350d24e2c3f7aebd100000000000000000000000083f6244bd87662, 118d96d9a6d44f09dfff14b30e00000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000249f000000000000000000000000000000000000000, 000000000000000000000000c000000000000000000000000000000000000000, 000000000000000000000000c4cc29a30600000000000000000000000036a7fc, af41b24e488f4c5ecef2dfd06985442467000000000000000000000000000000, 000000000000000000000fe4954e3b7f31000000000000000000000000000000, 000000000000000000000fd03d84e1996f000000000000000000000000000000, 0000000000000000000000018b8dc6137a000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 249 |
Lib_ResolvedDelegateProxy.0xcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a( 0xcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a, 0x00000000000000000000000083f6244bd87662118d96d9a6d44f09dfff14b30e, 000000000000000000000000a45df1a388049fb8d76e72d350d24e2c3f7aebd1, 0000000000000000000000000000000000000000000000000000000000000080, 000100000000000000000000000000000000000000000000000000000000fecf, 00000000000000000000000000000000000000000000000000000000000249f0, 00000000000000000000000000000000000000000000000000000000000000c4, cc29a30600000000000000000000000036a7fcaf41b24e488f4c5ecef2dfd069, 85442467000000000000000000000000000000000000000000000000000fe495, 4e3b7f31000000000000000000000000000000000000000000000000000fd03d, 84e1996f0000000000000000000000000000000000000000000000000000018b, 8dc6137a00000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 250 |
Lib_ResolvedDelegateProxy.0x8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d546( 0x8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d546, 0x000000000000000000000000a45df1a388049fb8d76e72d350d24e2c3f7aebd1, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 251 |
L1_ETH_Bridge.TransferSentToL2( chainId=10, recipient=[Sender] 0x36a7fcaf41b24e488f4c5ecef2dfd06985442467, amount=4473454564441905, amountOutMin=4451087291619695, deadline=1698890650490, relayer=0x00000000...000000000, relayerFee=0 )
|
| 252 |
SocketGateway.0x74594da9e31ee4068e17809037db37db496702bf7d8d63afe6f97949277d1609( 0x74594da9e31ee4068e17809037db37db496702bf7d8d63afe6f97949277d1609, 000000000000000000000000000000000000000000000000000fe4954e3b7f31, 000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 000000000000000000000000000000000000000000000000000000000000000a, 837ed841e30438f54fb6b0097c30a5c4f64b47545c3df655bcd6e44bb8991e37, 00000000000000000000000036a7fcaf41b24e488f4c5ecef2dfd06985442467, 00000000000000000000000036a7fcaf41b24e488f4c5ecef2dfd06985442467, 00000000000000000000000000000000000000000000000000000000000008f1 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x25ace71c...4F7ab5fA1 | (Optimism: Proxy OVM L1 Cross Domain Messenger) | ||||
| 0x36A7FcaF...985442467 |
0.012868861675141905 Eth
Nonce: 1810
|
0.00520149164892718 Eth
Nonce: 1811
| 0.007667370026214725 | ||
|
0x77777A6C...0F660eC94
Miner
| (MEV Builder: 0x777...C94) | 2.969371756956759734 Eth | 2.969385307156759734 Eth | 0.0000135502 | |
| 0xb8901acB...02919727f | (Hop Protocol: Ethereum Bridge) | 12,502.318837600369886342 Eth | 12,502.323311054934328247 Eth | 0.004473454564441905 | |
| 0xbEb5Fc57...e41f106Ed | (Optimism: Portal) |
Execution Trace
ETH 0.004473454564441905
SocketGateway.00000014( )
ETH 0.004473454564441905
0x2197a1d9af24b4d6a64bff95b4c29fcd3ff28c30.d025dec0( )ETH 0.004473454564441905
L1_ETH_Bridge.sendToL2( chainId=10, recipient=0x36A7FcaF41b24e488f4C5eCEF2dFD06985442467, amount=4473454564441905, amountOutMin=4451087291619695, deadline=1698890650490, relayer=0x0000000000000000000000000000000000000000, relayerFee=0 )OptimismMessengerWrapper.sendCrossDomainMessage( _calldata=0xCC29A30600000000000000000000000036A7FCAF41B24E488F4C5ECEF2DFD06985442467000000000000000000000000000000000000000000000000000FE4954E3B7F31000000000000000000000000000000000000000000000000000FD03D84E1996F0000000000000000000000000000000000000000000000000000018B8DC6137A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 )Lib_ResolvedDelegateProxy.3dbb202b( )-
Lib_AddressManager.getAddress( _name=OVM_L1CrossDomainMessenger ) => ( 0x2150Bc3c64cbfDDbaC9815EF615D6AB8671bfe43 ) L1CrossDomainMessenger.sendMessage( _target=0x83f6244Bd87662118d96D9a6D44f09dffF14b30E, _message=0xCC29A30600000000000000000000000036A7FCAF41B24E488F4C5ECEF2DFD06985442467000000000000000000000000000000000000000000000000000FE4954E3B7F31000000000000000000000000000000000000000000000000000FD03D84E1996F0000000000000000000000000000000000000000000000000000018B8DC6137A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, _minGasLimit=150000 )-
Proxy.e9e05c42( )
-
-
File 1 of 7: SocketGateway
File 2 of 7: Proxy
File 3 of 7: Lib_ResolvedDelegateProxy
File 4 of 7: L1_ETH_Bridge
File 5 of 7: OptimismMessengerWrapper
File 6 of 7: Lib_AddressManager
File 7 of 7: L1CrossDomainMessenger
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\\x19\\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./interfaces/across.sol";
import "../BridgeImplBase.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {ACROSS} from "../../static/RouteIdentifiers.sol";
/**
* @title Across-Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via Across-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of AcrossImplementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract AcrossImpl is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable AcrossIdentifier = ACROSS;
/// @notice Function-selector for ERC20-token bridging on Across-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4 public immutable ACROSS_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(uint256,uint256,bytes32,address,address,uint32,uint64)"
)
);
/// @notice Function-selector for Native bridging on Across-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens
bytes4 public immutable ACROSS_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(uint256,uint256,bytes32,address,uint32,uint64)"
)
);
bytes4 public immutable ACROSS_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(uint256,address,uint32,uint64,bytes32))"
)
);
/// @notice spokePool Contract instance used to deposit ERC20 and Native on to Across-Bridge
/// @dev contract instance is to be initialized in the constructor using the spokePoolAddress passed as constructor argument
SpokePool public immutable spokePool;
address public immutable spokePoolAddress;
/// @notice address of WETH token to be initialised in constructor
address public immutable WETH;
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct AcrossBridgeDataNoToken {
uint256 toChainId;
address receiverAddress;
uint32 quoteTimestamp;
uint64 relayerFeePct;
bytes32 metadata;
}
struct AcrossBridgeData {
uint256 toChainId;
address receiverAddress;
address token;
uint32 quoteTimestamp;
uint64 relayerFeePct;
bytes32 metadata;
}
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure spokepool, weth-address are set properly for the chainId in which the contract is being deployed
constructor(
address _spokePool,
address _wethAddress,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
spokePool = SpokePool(_spokePool);
spokePoolAddress = _spokePool;
WETH = _wethAddress;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for AcrossBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
AcrossBridgeData memory acrossBridgeData = abi.decode(
bridgeData,
(AcrossBridgeData)
);
if (acrossBridgeData.token == NATIVE_TOKEN_ADDRESS) {
spokePool.deposit{value: amount}(
acrossBridgeData.receiverAddress,
WETH,
amount,
acrossBridgeData.toChainId,
acrossBridgeData.relayerFeePct,
acrossBridgeData.quoteTimestamp
);
} else {
spokePool.deposit(
acrossBridgeData.receiverAddress,
acrossBridgeData.token,
amount,
acrossBridgeData.toChainId,
acrossBridgeData.relayerFeePct,
acrossBridgeData.quoteTimestamp
);
}
emit SocketBridge(
amount,
acrossBridgeData.token,
acrossBridgeData.toChainId,
AcrossIdentifier,
msg.sender,
acrossBridgeData.receiverAddress,
acrossBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param acrossBridgeData encoded data for AcrossBridge
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
AcrossBridgeDataNoToken calldata acrossBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
spokePool.deposit{value: bridgeAmount}(
acrossBridgeData.receiverAddress,
WETH,
bridgeAmount,
acrossBridgeData.toChainId,
acrossBridgeData.relayerFeePct,
acrossBridgeData.quoteTimestamp
);
} else {
spokePool.deposit(
acrossBridgeData.receiverAddress,
token,
bridgeAmount,
acrossBridgeData.toChainId,
acrossBridgeData.relayerFeePct,
acrossBridgeData.quoteTimestamp
);
}
emit SocketBridge(
bridgeAmount,
token,
acrossBridgeData.toChainId,
AcrossIdentifier,
msg.sender,
acrossBridgeData.receiverAddress,
acrossBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Across-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount being bridged
* @param toChainId destination ChainId
* @param receiverAddress address of receiver of bridged tokens
* @param token address of token being bridged
* @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract
* @param relayerFeePct feePct that will be relayed by the Bridge to the relayer
*/
function bridgeERC20To(
uint256 amount,
uint256 toChainId,
bytes32 metadata,
address receiverAddress,
address token,
uint32 quoteTimestamp,
uint64 relayerFeePct
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
spokePool.deposit(
receiverAddress,
address(token),
amount,
toChainId,
relayerFeePct,
quoteTimestamp
);
emit SocketBridge(
amount,
token,
toChainId,
AcrossIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Across-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount being bridged
* @param toChainId destination ChainId
* @param receiverAddress address of receiver of bridged tokens
* @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract
* @param relayerFeePct feePct that will be relayed by the Bridge to the relayer
*/
function bridgeNativeTo(
uint256 amount,
uint256 toChainId,
bytes32 metadata,
address receiverAddress,
uint32 quoteTimestamp,
uint64 relayerFeePct
) external payable {
spokePool.deposit{value: amount}(
receiverAddress,
WETH,
amount,
toChainId,
relayerFeePct,
quoteTimestamp
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
toChainId,
AcrossIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @notice interface with functions to interact with SpokePool contract of Across-Bridge
interface SpokePool {
/**************************************
* DEPOSITOR FUNCTIONS *
**************************************/
/**
* @notice Called by user to bridge funds from origin to destination chain. Depositor will effectively lock
* tokens in this contract and receive a destination token on the destination chain. The origin => destination
* token mapping is stored on the L1 HubPool.
* @notice The caller must first approve this contract to spend amount of originToken.
* @notice The originToken => destinationChainId must be enabled.
* @notice This method is payable because the caller is able to deposit native token if the originToken is
* wrappedNativeToken and this function will handle wrapping the native token to wrappedNativeToken.
* @param recipient Address to receive funds at on destination chain.
* @param originToken Token to lock into this contract to initiate deposit.
* @param amount Amount of tokens to deposit. Will be amount of tokens to receive less fees.
* @param destinationChainId Denotes network where user will receive funds from SpokePool by a relayer.
* @param relayerFeePct % of deposit amount taken out to incentivize a fast relayer.
* @param quoteTimestamp Timestamp used by relayers to compute this deposit's realizedLPFeePct which is paid
* to LP pool on HubPool.
*/
function deposit(
address recipient,
address originToken,
uint256 amount,
uint256 destinationChainId,
uint64 relayerFeePct,
uint32 quoteTimestamp
) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {ANYSWAP} from "../../../static/RouteIdentifiers.sol";
/**
* @title Anyswap-V4-Route L1 Implementation
* @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation
* This is the L1 implementation, so this is used when transferring from l1 to supported l1s or L1.
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
/// @notice Interface to interact with AnyswapV4-Router Implementation
interface AnyswapV4Router {
function anySwapOutUnderlying(
address token,
address to,
uint256 amount,
uint256 toChainID
) external;
}
contract AnyswapImplL1 is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable AnyswapIdentifier = ANYSWAP;
/// @notice Function-selector for ERC20-token bridging on Anyswap-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4 public immutable ANYSWAP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(uint256,uint256,bytes32,address,address,address)"
)
);
bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))"
)
);
/// @notice AnSwapV4Router Contract instance used to deposit ERC20 on to Anyswap-Bridge
/// @dev contract instance is to be initialized in the constructor using the router-address passed as constructor argument
AnyswapV4Router public immutable router;
/**
* @notice Constructor sets the router address and socketGateway address.
* @dev anyswap 4 router is immutable. so no setter function required.
*/
constructor(
address _router,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
router = AnyswapV4Router(_router);
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct AnyswapBridgeDataNoToken {
/// @notice destination ChainId
uint256 toChainId;
/// @notice address of receiver of bridged tokens
address receiverAddress;
/// @notice address of wrapperToken, WrappedVersion of the token being bridged
address wrapperTokenAddress;
/// @notice socket offchain created hash
bytes32 metadata;
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct AnyswapBridgeData {
/// @notice destination ChainId
uint256 toChainId;
/// @notice address of receiver of bridged tokens
address receiverAddress;
/// @notice address of wrapperToken, WrappedVersion of the token being bridged
address wrapperTokenAddress;
/// @notice address of token being bridged
address token;
/// @notice socket offchain created hash
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for AnyswapBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
AnyswapBridgeData memory anyswapBridgeData = abi.decode(
bridgeData,
(AnyswapBridgeData)
);
ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);
router.anySwapOutUnderlying(
anyswapBridgeData.wrapperTokenAddress,
anyswapBridgeData.receiverAddress,
amount,
anyswapBridgeData.toChainId
);
emit SocketBridge(
amount,
anyswapBridgeData.token,
anyswapBridgeData.toChainId,
AnyswapIdentifier,
msg.sender,
anyswapBridgeData.receiverAddress,
anyswapBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param anyswapBridgeData encoded data for AnyswapBridge
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
AnyswapBridgeDataNoToken calldata anyswapBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
ERC20(token).safeApprove(address(router), bridgeAmount);
router.anySwapOutUnderlying(
anyswapBridgeData.wrapperTokenAddress,
anyswapBridgeData.receiverAddress,
bridgeAmount,
anyswapBridgeData.toChainId
);
emit SocketBridge(
bridgeAmount,
token,
anyswapBridgeData.toChainId,
AnyswapIdentifier,
msg.sender,
anyswapBridgeData.receiverAddress,
anyswapBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount being bridged
* @param toChainId destination ChainId
* @param receiverAddress address of receiver of bridged tokens
* @param token address of token being bridged
* @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged
*/
function bridgeERC20To(
uint256 amount,
uint256 toChainId,
bytes32 metadata,
address receiverAddress,
address token,
address wrapperTokenAddress
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(address(router), amount);
router.anySwapOutUnderlying(
wrapperTokenAddress,
receiverAddress,
amount,
toChainId
);
emit SocketBridge(
amount,
token,
toChainId,
AnyswapIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {ANYSWAP} from "../../../static/RouteIdentifiers.sol";
/**
* @title Anyswap-V4-Route L1 Implementation
* @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation
* This is the L2 implementation, so this is used when transferring from l2.
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
interface AnyswapV4Router {
function anySwapOutUnderlying(
address token,
address to,
uint256 amount,
uint256 toChainID
) external;
}
contract AnyswapL2Impl is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable AnyswapIdentifier = ANYSWAP;
/// @notice Function-selector for ERC20-token bridging on Anyswap-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4 public immutable ANYSWAP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(uint256,uint256,bytes32,address,address,address)"
)
);
bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))"
)
);
// polygon router multichain router v4
AnyswapV4Router public immutable router;
/**
* @notice Constructor sets the router address and socketGateway address.
* @dev anyswap v4 router is immutable. so no setter function required.
*/
constructor(
address _router,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
router = AnyswapV4Router(_router);
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct AnyswapBridgeDataNoToken {
/// @notice destination ChainId
uint256 toChainId;
/// @notice address of receiver of bridged tokens
address receiverAddress;
/// @notice address of wrapperToken, WrappedVersion of the token being bridged
address wrapperTokenAddress;
/// @notice socket offchain created hash
bytes32 metadata;
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct AnyswapBridgeData {
/// @notice destination ChainId
uint256 toChainId;
/// @notice address of receiver of bridged tokens
address receiverAddress;
/// @notice address of wrapperToken, WrappedVersion of the token being bridged
address wrapperTokenAddress;
/// @notice address of token being bridged
address token;
/// @notice socket offchain created hash
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for AnyswapBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
AnyswapBridgeData memory anyswapBridgeData = abi.decode(
bridgeData,
(AnyswapBridgeData)
);
ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);
router.anySwapOutUnderlying(
anyswapBridgeData.wrapperTokenAddress,
anyswapBridgeData.receiverAddress,
amount,
anyswapBridgeData.toChainId
);
emit SocketBridge(
amount,
anyswapBridgeData.token,
anyswapBridgeData.toChainId,
AnyswapIdentifier,
msg.sender,
anyswapBridgeData.receiverAddress,
anyswapBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param anyswapBridgeData encoded data for AnyswapBridge
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
AnyswapBridgeDataNoToken calldata anyswapBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
ERC20(token).safeApprove(address(router), bridgeAmount);
router.anySwapOutUnderlying(
anyswapBridgeData.wrapperTokenAddress,
anyswapBridgeData.receiverAddress,
bridgeAmount,
anyswapBridgeData.toChainId
);
emit SocketBridge(
bridgeAmount,
token,
anyswapBridgeData.toChainId,
AnyswapIdentifier,
msg.sender,
anyswapBridgeData.receiverAddress,
anyswapBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount being bridged
* @param toChainId destination ChainId
* @param receiverAddress address of receiver of bridged tokens
* @param token address of token being bridged
* @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged
*/
function bridgeERC20To(
uint256 amount,
uint256 toChainId,
bytes32 metadata,
address receiverAddress,
address token,
address wrapperTokenAddress
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(address(router), amount);
router.anySwapOutUnderlying(
wrapperTokenAddress,
receiverAddress,
amount,
toChainId
);
emit SocketBridge(
amount,
token,
toChainId,
AnyswapIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity >=0.8.0;
/**
* @title L1gatewayRouter for native-arbitrum
*/
interface L1GatewayRouter {
/**
* @notice outbound function to bridge ERC20 via NativeArbitrum-Bridge
* @param _token address of token being bridged via GatewayRouter
* @param _to recipient of the token on arbitrum chain
* @param _amount amount of ERC20 token being bridged
* @param _maxGas a depositParameter for bridging the token
* @param _gasPriceBid a depositParameter for bridging the token
* @param _data a depositParameter for bridging the token
* @return calldata returns the output of transactioncall made on gatewayRouter
*/
function outboundTransfer(
address _token,
address _to,
uint256 _amount,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes calldata _data
) external payable returns (bytes calldata);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {L1GatewayRouter} from "../interfaces/arbitrum.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {NATIVE_ARBITRUM} from "../../../static/RouteIdentifiers.sol";
/**
* @title Native Arbitrum-Route Implementation
* @notice Route implementation with functions to bridge ERC20 via NativeArbitrum-Bridge
* @notice Called via SocketGateway if the routeId in the request maps to the routeId of NativeArbitrum-Implementation
* @notice This is used when transferring from ethereum chain to arbitrum via their native bridge.
* @notice Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* @notice RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract NativeArbitrumImpl is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable NativeArbitrumIdentifier = NATIVE_ARBITRUM;
uint256 public constant DESTINATION_CHAIN_ID = 42161;
/// @notice Function-selector for ERC20-token bridging on NativeArbitrum
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4
public immutable NATIVE_ARBITRUM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(uint256,uint256,uint256,uint256,bytes32,address,address,address,bytes)"
)
);
bytes4 public immutable NATIVE_ARBITRUM_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(uint256,uint256,uint256,address,address,bytes32,bytes))"
)
);
/// @notice router address of NativeArbitrum Bridge
/// @notice GatewayRouter looks up ERC20Token's gateway, and finding that it's Standard ERC20 gateway (the L1ERC20Gateway contract).
address public immutable router;
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure router-address are set properly for the chainId in which the contract is being deployed
constructor(
address _router,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
router = _router;
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct NativeArbitrumBridgeDataNoToken {
uint256 value;
/// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum
uint256 maxGas;
/// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum
uint256 gasPriceBid;
/// @notice address of receiver of bridged tokens
address receiverAddress;
/// @notice address of Gateway which handles the token bridging for the token
/// @notice gatewayAddress is unique for each token
address gatewayAddress;
/// @notice socket offchain created hash
bytes32 metadata;
/// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum
bytes data;
}
struct NativeArbitrumBridgeData {
uint256 value;
/// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum
uint256 maxGas;
/// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum
uint256 gasPriceBid;
/// @notice address of receiver of bridged tokens
address receiverAddress;
/// @notice address of Gateway which handles the token bridging for the token
/// @notice gatewayAddress is unique for each token
address gatewayAddress;
/// @notice address of token being bridged
address token;
/// @notice socket offchain created hash
bytes32 metadata;
/// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum
bytes data;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for NativeArbitrumBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
NativeArbitrumBridgeData memory nativeArbitrumBridgeData = abi.decode(
bridgeData,
(NativeArbitrumBridgeData)
);
ERC20(nativeArbitrumBridgeData.token).safeApprove(
nativeArbitrumBridgeData.gatewayAddress,
amount
);
L1GatewayRouter(router).outboundTransfer{
value: nativeArbitrumBridgeData.value
}(
nativeArbitrumBridgeData.token,
nativeArbitrumBridgeData.receiverAddress,
amount,
nativeArbitrumBridgeData.maxGas,
nativeArbitrumBridgeData.gasPriceBid,
nativeArbitrumBridgeData.data
);
emit SocketBridge(
amount,
nativeArbitrumBridgeData.token,
DESTINATION_CHAIN_ID,
NativeArbitrumIdentifier,
msg.sender,
nativeArbitrumBridgeData.receiverAddress,
nativeArbitrumBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param nativeArbitrumBridgeData encoded data for NativeArbitrumBridge
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
NativeArbitrumBridgeDataNoToken calldata nativeArbitrumBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
ERC20(token).safeApprove(
nativeArbitrumBridgeData.gatewayAddress,
bridgeAmount
);
L1GatewayRouter(router).outboundTransfer{
value: nativeArbitrumBridgeData.value
}(
token,
nativeArbitrumBridgeData.receiverAddress,
bridgeAmount,
nativeArbitrumBridgeData.maxGas,
nativeArbitrumBridgeData.gasPriceBid,
nativeArbitrumBridgeData.data
);
emit SocketBridge(
bridgeAmount,
token,
DESTINATION_CHAIN_ID,
NativeArbitrumIdentifier,
msg.sender,
nativeArbitrumBridgeData.receiverAddress,
nativeArbitrumBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via NativeArbitrum-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount being bridged
* @param value value
* @param maxGas maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum
* @param gasPriceBid gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum
* @param receiverAddress address of receiver of bridged tokens
* @param token address of token being bridged
* @param gatewayAddress address of Gateway which handles the token bridging for the token, gatewayAddress is unique for each token
* @param data data is a depositParameter derived from erc20Bridger of nativeArbitrum
*/
function bridgeERC20To(
uint256 amount,
uint256 value,
uint256 maxGas,
uint256 gasPriceBid,
bytes32 metadata,
address receiverAddress,
address token,
address gatewayAddress,
bytes memory data
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(gatewayAddress, amount);
L1GatewayRouter(router).outboundTransfer{value: value}(
token,
receiverAddress,
amount,
maxGas,
gasPriceBid,
data
);
emit SocketBridge(
amount,
token,
DESTINATION_CHAIN_ID,
NativeArbitrumIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {ISocketGateway} from "../interfaces/ISocketGateway.sol";
import {ISocketRoute} from "../interfaces/ISocketRoute.sol";
import {OnlySocketGatewayOwner, OnlySocketDeployer} from "../errors/SocketErrors.sol";
/**
* @title Abstract Implementation Contract.
* @notice All Bridge Implementation will follow this interface.
*/
abstract contract BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
/// @notice Address used to identify if it is a native token transfer or not
address public immutable NATIVE_TOKEN_ADDRESS =
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
/// @notice immutable variable to store the socketGateway address
address public immutable socketGateway;
/// @notice immutable variable to store the socketGateway address
address public immutable socketDeployFactory;
/// @notice immutable variable with instance of SocketRoute to access route functions
ISocketRoute public immutable socketRoute;
/// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation
bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =
bytes4(keccak256("bridgeAfterSwap(uint256,bytes)"));
/****************************************
* EVENTS *
****************************************/
event SocketBridge(
uint256 amount,
address token,
uint256 toChainId,
bytes32 bridgeName,
address sender,
address receiver,
bytes32 metadata
);
/**
* @notice Construct the base for all BridgeImplementations.
* @param _socketGateway Socketgateway address, an immutable variable to set.
* @param _socketDeployFactory Socket Deploy Factory address, an immutable variable to set.
*/
constructor(address _socketGateway, address _socketDeployFactory) {
socketGateway = _socketGateway;
socketDeployFactory = _socketDeployFactory;
socketRoute = ISocketRoute(_socketGateway);
}
/****************************************
* MODIFIERS *
****************************************/
/// @notice Implementing contract needs to make use of the modifier where restricted access is to be used
modifier isSocketGatewayOwner() {
if (msg.sender != ISocketGateway(socketGateway).owner()) {
revert OnlySocketGatewayOwner();
}
_;
}
/// @notice Implementing contract needs to make use of the modifier where restricted access is to be used
modifier isSocketDeployFactory() {
if (msg.sender != socketDeployFactory) {
revert OnlySocketDeployer();
}
_;
}
/****************************************
* RESTRICTED FUNCTIONS *
****************************************/
/**
* @notice function to rescue the ERC20 tokens in the bridge Implementation contract
* @notice this is a function restricted to Owner of SocketGateway only
* @param token address of ERC20 token being rescued
* @param userAddress receipient address to which ERC20 tokens will be rescued to
* @param amount amount of ERC20 tokens being rescued
*/
function rescueFunds(
address token,
address userAddress,
uint256 amount
) external isSocketGatewayOwner {
ERC20(token).safeTransfer(userAddress, amount);
}
/**
* @notice function to rescue the native-balance in the bridge Implementation contract
* @notice this is a function restricted to Owner of SocketGateway only
* @param userAddress receipient address to which native-balance will be rescued to
* @param amount amount of native balance tokens being rescued
*/
function rescueEther(
address payable userAddress,
uint256 amount
) external isSocketGatewayOwner {
userAddress.transfer(amount);
}
function killme() external isSocketDeployFactory {
selfdestruct(payable(msg.sender));
}
/******************************
* VIRTUAL FUNCTIONS *
*****************************/
/**
* @notice function to bridge which is succeeding the swap function
* @notice this function is to be used only when bridging as a succeeding step
* @notice All bridge implementation contracts must implement this function
* @notice bridge-implementations will have a bridge specific struct with properties used in bridging
* @param bridgeData encoded value of properties in the bridgeData Struct
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../../libraries/Pb.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "./interfaces/cbridge.sol";
import "./interfaces/ICelerStorageWrapper.sol";
import {TransferIdExists, InvalidCelerRefund, CelerAlreadyRefunded, CelerRefundNotReady} from "../../errors/SocketErrors.sol";
import {BridgeImplBase} from "../BridgeImplBase.sol";
import {CBRIDGE} from "../../static/RouteIdentifiers.sol";
/**
* @title Celer-Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via Celer-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of CelerImplementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract CelerImpl is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable CBridgeIdentifier = CBRIDGE;
/// @notice Utility to perform operation on Buffer
using Pb for Pb.Buffer;
/// @notice Function-selector for ERC20-token bridging on Celer-Route
/// @dev This function selector is to be used while building transaction-data to bridge ERC20 tokens
bytes4 public immutable CELER_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(address,address,uint256,bytes32,uint64,uint64,uint32)"
)
);
/// @notice Function-selector for Native bridging on Celer-Route
/// @dev This function selector is to be used while building transaction-data to bridge Native tokens
bytes4 public immutable CELER_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(address,uint256,bytes32,uint64,uint64,uint32)"
)
);
bytes4 public immutable CELER_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(address,uint64,uint32,uint64,bytes32))"
)
);
/// @notice router Contract instance used to deposit ERC20 and Native on to Celer-Bridge
/// @dev contract instance is to be initialized in the constructor using the routerAddress passed as constructor argument
ICBridge public immutable router;
/// @notice celerStorageWrapper Contract instance used to store the transferId generated during ERC20 and Native bridge on to Celer-Bridge
/// @dev contract instance is to be initialized in the constructor using the celerStorageWrapperAddress passed as constructor argument
ICelerStorageWrapper public immutable celerStorageWrapper;
/// @notice WETH token address
address public immutable weth;
/// @notice chainId used during generation of transferId generated while bridging ERC20 and Native on to Celer-Bridge
/// @dev this is to be initialised in the constructor
uint64 public immutable chainId;
struct WithdrawMsg {
uint64 chainid; // tag: 1
uint64 seqnum; // tag: 2
address receiver; // tag: 3
address token; // tag: 4
uint256 amount; // tag: 5
bytes32 refid; // tag: 6
}
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure routerAddress, weth-address, celerStorageWrapperAddress are set properly for the chainId in which the contract is being deployed
constructor(
address _routerAddress,
address _weth,
address _celerStorageWrapperAddress,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
router = ICBridge(_routerAddress);
celerStorageWrapper = ICelerStorageWrapper(_celerStorageWrapperAddress);
weth = _weth;
chainId = uint64(block.chainid);
}
// Function to receive Ether. msg.data must be empty
receive() external payable {}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct CelerBridgeDataNoToken {
address receiverAddress;
uint64 toChainId;
uint32 maxSlippage;
uint64 nonce;
bytes32 metadata;
}
struct CelerBridgeData {
address token;
address receiverAddress;
uint64 toChainId;
uint32 maxSlippage;
uint64 nonce;
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in CelerBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for CelerBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
CelerBridgeData memory celerBridgeData = abi.decode(
bridgeData,
(CelerBridgeData)
);
if (celerBridgeData.token == NATIVE_TOKEN_ADDRESS) {
// transferId is generated using the request-params and nonce of the account
// transferId should be unique for each request and this is used while handling refund from celerBridge
bytes32 transferId = keccak256(
abi.encodePacked(
address(this),
celerBridgeData.receiverAddress,
weth,
amount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
chainId
)
);
// transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender
celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);
router.sendNative{value: amount}(
celerBridgeData.receiverAddress,
amount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
celerBridgeData.maxSlippage
);
} else {
// transferId is generated using the request-params and nonce of the account
// transferId should be unique for each request and this is used while handling refund from celerBridge
bytes32 transferId = keccak256(
abi.encodePacked(
address(this),
celerBridgeData.receiverAddress,
celerBridgeData.token,
amount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
chainId
)
);
// transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender
celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);
router.send(
celerBridgeData.receiverAddress,
celerBridgeData.token,
amount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
celerBridgeData.maxSlippage
);
}
emit SocketBridge(
amount,
celerBridgeData.token,
celerBridgeData.toChainId,
CBridgeIdentifier,
msg.sender,
celerBridgeData.receiverAddress,
celerBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in CelerBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param celerBridgeData encoded data for CelerBridgeData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
CelerBridgeDataNoToken calldata celerBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
// transferId is generated using the request-params and nonce of the account
// transferId should be unique for each request and this is used while handling refund from celerBridge
bytes32 transferId = keccak256(
abi.encodePacked(
address(this),
celerBridgeData.receiverAddress,
weth,
bridgeAmount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
chainId
)
);
// transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender
celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);
router.sendNative{value: bridgeAmount}(
celerBridgeData.receiverAddress,
bridgeAmount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
celerBridgeData.maxSlippage
);
} else {
// transferId is generated using the request-params and nonce of the account
// transferId should be unique for each request and this is used while handling refund from celerBridge
bytes32 transferId = keccak256(
abi.encodePacked(
address(this),
celerBridgeData.receiverAddress,
token,
bridgeAmount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
chainId
)
);
// transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender
celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);
router.send(
celerBridgeData.receiverAddress,
token,
bridgeAmount,
celerBridgeData.toChainId,
celerBridgeData.nonce,
celerBridgeData.maxSlippage
);
}
emit SocketBridge(
bridgeAmount,
token,
celerBridgeData.toChainId,
CBridgeIdentifier,
msg.sender,
celerBridgeData.receiverAddress,
celerBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Celer-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress address of recipient
* @param token address of token being bridged
* @param amount amount of token for bridging
* @param toChainId destination ChainId
* @param nonce nonce of the sender-account address
* @param maxSlippage maximum Slippage for the bridging
*/
function bridgeERC20To(
address receiverAddress,
address token,
uint256 amount,
bytes32 metadata,
uint64 toChainId,
uint64 nonce,
uint32 maxSlippage
) external payable {
/// @notice transferId is generated using the request-params and nonce of the account
/// @notice transferId should be unique for each request and this is used while handling refund from celerBridge
bytes32 transferId = keccak256(
abi.encodePacked(
address(this),
receiverAddress,
token,
amount,
toChainId,
nonce,
chainId
)
);
/// @notice stored in the CelerStorageWrapper contract
celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
router.send(
receiverAddress,
token,
amount,
toChainId,
nonce,
maxSlippage
);
emit SocketBridge(
amount,
token,
toChainId,
CBridgeIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Celer-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress address of recipient
* @param amount amount of token for bridging
* @param toChainId destination ChainId
* @param nonce nonce of the sender-account address
* @param maxSlippage maximum Slippage for the bridging
*/
function bridgeNativeTo(
address receiverAddress,
uint256 amount,
bytes32 metadata,
uint64 toChainId,
uint64 nonce,
uint32 maxSlippage
) external payable {
bytes32 transferId = keccak256(
abi.encodePacked(
address(this),
receiverAddress,
weth,
amount,
toChainId,
nonce,
chainId
)
);
celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);
router.sendNative{value: amount}(
receiverAddress,
amount,
toChainId,
nonce,
maxSlippage
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
toChainId,
CBridgeIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle refund from CelerBridge-Router
* @param _request request data generated offchain using the celer-SDK
* @param _sigs generated offchain using the celer-SDK
* @param _signers generated offchain using the celer-SDK
* @param _powers generated offchain using the celer-SDK
*/
function refundCelerUser(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external payable {
WithdrawMsg memory request = decWithdrawMsg(_request);
bytes32 transferId = keccak256(
abi.encodePacked(
request.chainid,
request.seqnum,
request.receiver,
request.token,
request.amount
)
);
uint256 _initialNativeBalance = address(this).balance;
uint256 _initialTokenBalance = ERC20(request.token).balanceOf(
address(this)
);
if (!router.withdraws(transferId)) {
router.withdraw(_request, _sigs, _signers, _powers);
}
if (request.receiver != socketGateway) {
revert InvalidCelerRefund();
}
address _receiver = celerStorageWrapper.getAddressFromTransferId(
request.refid
);
celerStorageWrapper.deleteTransferId(request.refid);
if (_receiver == address(0)) {
revert CelerAlreadyRefunded();
}
uint256 _nativeBalanceAfter = address(this).balance;
uint256 _tokenBalanceAfter = ERC20(request.token).balanceOf(
address(this)
);
if (_nativeBalanceAfter > _initialNativeBalance) {
if ((_nativeBalanceAfter - _initialNativeBalance) != request.amount)
revert CelerRefundNotReady();
payable(_receiver).transfer(request.amount);
return;
}
if (_tokenBalanceAfter > _initialTokenBalance) {
if ((_tokenBalanceAfter - _initialTokenBalance) != request.amount)
revert CelerRefundNotReady();
ERC20(request.token).safeTransfer(_receiver, request.amount);
return;
}
revert CelerRefundNotReady();
}
function decWithdrawMsg(
bytes memory raw
) internal pure returns (WithdrawMsg memory m) {
Pb.Buffer memory buf = Pb.fromBytes(raw);
uint256 tag;
Pb.WireType wire;
while (buf.hasMore()) {
(tag, wire) = buf.decKey();
if (false) {}
// solidity has no switch/case
else if (tag == 1) {
m.chainid = uint64(buf.decVarint());
} else if (tag == 2) {
m.seqnum = uint64(buf.decVarint());
} else if (tag == 3) {
m.receiver = Pb._address(buf.decBytes());
} else if (tag == 4) {
m.token = Pb._address(buf.decBytes());
} else if (tag == 5) {
m.amount = Pb._uint256(buf.decBytes());
} else if (tag == 6) {
m.refid = Pb._bytes32(buf.decBytes());
} else {
buf.skipValue(wire);
} // skip value of unknown tag
}
} // end decoder WithdrawMsg
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
import {OnlySocketGateway, TransferIdExists, TransferIdDoesnotExist} from "../../errors/SocketErrors.sol";
/**
* @title CelerStorageWrapper
* @notice handle storageMappings used while bridging ERC20 and native on CelerBridge
* @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway
* @author Socket dot tech.
*/
contract CelerStorageWrapper {
/// @notice Socketgateway-address to be set in the constructor of CelerStorageWrapper
address public immutable socketGateway;
/// @notice mapping to store the transferId generated during bridging on Celer to message-sender
mapping(bytes32 => address) private transferIdMapping;
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
constructor(address _socketGateway) {
socketGateway = _socketGateway;
}
/**
* @notice function to store the transferId and message-sender of a bridging activity
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in CelerBridgeData struct
* @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge
* @param transferIdAddress message sender who is making the bridging on CelerBridge
*/
function setAddressForTransferId(
bytes32 transferId,
address transferIdAddress
) external {
if (msg.sender != socketGateway) {
revert OnlySocketGateway();
}
if (transferIdMapping[transferId] != address(0)) {
revert TransferIdExists();
}
transferIdMapping[transferId] = transferIdAddress;
}
/**
* @notice function to delete the transferId when the celer bridge processes a refund.
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in CelerBridgeData struct
* @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge
*/
function deleteTransferId(bytes32 transferId) external {
if (msg.sender != socketGateway) {
revert OnlySocketGateway();
}
if (transferIdMapping[transferId] == address(0)) {
revert TransferIdDoesnotExist();
}
delete transferIdMapping[transferId];
}
/**
* @notice function to lookup the address mapped to the transferId
* @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge
* @return address of account mapped to transferId
*/
function getAddressFromTransferId(
bytes32 transferId
) external view returns (address) {
return transferIdMapping[transferId];
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
interface ICBridge {
function send(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChinId,
uint64 _nonce,
uint32 _maxSlippage
) external;
function sendNative(
address _receiver,
uint256 _amount,
uint64 _dstChinId,
uint64 _nonce,
uint32 _maxSlippage
) external payable;
function withdraws(bytes32 withdrawId) external view returns (bool);
function withdraw(
bytes calldata _wdmsg,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
/**
* @title Celer-StorageWrapper interface
* @notice Interface to handle storageMappings used while bridging ERC20 and native on CelerBridge
* @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway
* @author Socket dot tech.
*/
interface ICelerStorageWrapper {
/**
* @notice function to store the transferId and message-sender of a bridging activity
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in CelerBridgeData struct
* @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge
* @param transferIdAddress message sender who is making the bridging on CelerBridge
*/
function setAddressForTransferId(
bytes32 transferId,
address transferIdAddress
) external;
/**
* @notice function to store the transferId and message-sender of a bridging activity
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in CelerBridgeData struct
* @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge
*/
function deleteTransferId(bytes32 transferId) external;
/**
* @notice function to lookup the address mapped to the transferId
* @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge
* @return address of account mapped to transferId
*/
function getAddressFromTransferId(
bytes32 transferId
) external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title HopAMM
* @notice Interface to handle the token bridging to L2 chains.
*/
interface HopAMM {
/**
* @notice To send funds L2->L1 or L2->L2, call the swapAndSend on the L2 AMM Wrapper contract
* @param chainId chainId of the L2 contract
* @param recipient receiver address
* @param amount amount is the amount the user wants to send plus the Bonder fee
* @param bonderFee fees
* @param amountOutMin minimum amount
* @param deadline deadline for bridging
* @param destinationAmountOutMin minimum amount expected to be bridged on L2
* @param destinationDeadline destination time before which token is to be bridged on L2
*/
function swapAndSend(
uint256 chainId,
address recipient,
uint256 amount,
uint256 bonderFee,
uint256 amountOutMin,
uint256 deadline,
uint256 destinationAmountOutMin,
uint256 destinationDeadline
) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title L1Bridge Hop Interface
* @notice L1 Hop Bridge, Used to transfer from L1 to L2s.
*/
interface IHopL1Bridge {
/**
* @notice `amountOutMin` and `deadline` should be 0 when no swap is intended at the destination.
* @notice `amount` is the total amount the user wants to send including the relayer fee
* @dev Send tokens to a supported layer-2 to mint hToken and optionally swap the hToken in the
* AMM at the destination.
* @param chainId The chainId of the destination chain
* @param recipient The address receiving funds at the destination
* @param amount The amount being sent
* @param amountOutMin The minimum amount received after attempting to swap in the destination
* AMM market. 0 if no swap is intended.
* @param deadline The deadline for swapping in the destination AMM market. 0 if no
* swap is intended.
* @param relayer The address of the relayer at the destination.
* @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.
*/
function sendToL2(
uint256 chainId,
address recipient,
uint256 amount,
uint256 amountOutMin,
uint256 deadline,
address relayer,
uint256 relayerFee
) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "../interfaces/IHopL1Bridge.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {HOP} from "../../../static/RouteIdentifiers.sol";
/**
* @title Hop-L1 Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via Hop-Bridge from L1 to Supported L2s
* Called via SocketGateway if the routeId in the request maps to the routeId of HopImplementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract HopImplL1 is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable HopIdentifier = HOP;
/// @notice Function-selector for ERC20-token bridging on Hop-L1-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4 public immutable HOP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(address,address,address,address,uint256,uint256,uint256,uint256,(uint256,bytes32))"
)
);
/// @notice Function-selector for Native bridging on Hop-L1-Route
/// @dev This function selector is to be used while building transaction-data to bridge Native tokens
bytes4 public immutable HOP_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32)"
)
);
bytes4 public immutable HOP_L1_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(address,address,address,uint256,uint256,uint256,uint256,bytes32))"
)
);
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
constructor(
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct HopDataNoToken {
// The address receiving funds at the destination
address receiverAddress;
// address of the Hop-L1-Bridge to handle bridging the tokens
address l1bridgeAddr;
// relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.
address relayer;
// The chainId of the destination chain
uint256 toChainId;
// The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
uint256 amountOutMin;
// The amount distributed to the relayer at the destination. This is subtracted from the `amount`.
uint256 relayerFee;
// The deadline for swapping in the destination AMM market. 0 if no swap is intended.
uint256 deadline;
// socket offchain created hash
bytes32 metadata;
}
struct HopData {
/// @notice address of token being bridged
address token;
// The address receiving funds at the destination
address receiverAddress;
// address of the Hop-L1-Bridge to handle bridging the tokens
address l1bridgeAddr;
// relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.
address relayer;
// The chainId of the destination chain
uint256 toChainId;
// The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
uint256 amountOutMin;
// The amount distributed to the relayer at the destination. This is subtracted from the `amount`.
uint256 relayerFee;
// The deadline for swapping in the destination AMM market. 0 if no swap is intended.
uint256 deadline;
// socket offchain created hash
bytes32 metadata;
}
struct HopERC20Data {
uint256 deadline;
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in HopBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for Hop-L1-Bridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
HopData memory hopData = abi.decode(bridgeData, (HopData));
if (hopData.token == NATIVE_TOKEN_ADDRESS) {
IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: amount}(
hopData.toChainId,
hopData.receiverAddress,
amount,
hopData.amountOutMin,
hopData.deadline,
hopData.relayer,
hopData.relayerFee
);
} else {
ERC20(hopData.token).safeApprove(hopData.l1bridgeAddr, amount);
// perform bridging
IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(
hopData.toChainId,
hopData.receiverAddress,
amount,
hopData.amountOutMin,
hopData.deadline,
hopData.relayer,
hopData.relayerFee
);
}
emit SocketBridge(
amount,
hopData.token,
hopData.toChainId,
HopIdentifier,
msg.sender,
hopData.receiverAddress,
hopData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in HopBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param hopData encoded data for HopData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
HopDataNoToken calldata hopData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: bridgeAmount}(
hopData.toChainId,
hopData.receiverAddress,
bridgeAmount,
hopData.amountOutMin,
hopData.deadline,
hopData.relayer,
hopData.relayerFee
);
} else {
ERC20(token).safeApprove(hopData.l1bridgeAddr, bridgeAmount);
// perform bridging
IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(
hopData.toChainId,
hopData.receiverAddress,
bridgeAmount,
hopData.amountOutMin,
hopData.deadline,
hopData.relayer,
hopData.relayerFee
);
}
emit SocketBridge(
bridgeAmount,
token,
hopData.toChainId,
HopIdentifier,
msg.sender,
hopData.receiverAddress,
hopData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Hop-L1-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress The address receiving funds at the destination
* @param token token being bridged
* @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens
* @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.
* @param toChainId The chainId of the destination chain
* @param amount The amount being sent
* @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
* @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.
* @param hopData extra data needed to build the tx
*/
function bridgeERC20To(
address receiverAddress,
address token,
address l1bridgeAddr,
address relayer,
uint256 toChainId,
uint256 amount,
uint256 amountOutMin,
uint256 relayerFee,
HopERC20Data calldata hopData
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(l1bridgeAddr, amount);
// perform bridging
IHopL1Bridge(l1bridgeAddr).sendToL2(
toChainId,
receiverAddress,
amount,
amountOutMin,
hopData.deadline,
relayer,
relayerFee
);
emit SocketBridge(
amount,
token,
toChainId,
HopIdentifier,
msg.sender,
receiverAddress,
hopData.metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Hop-L1-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress The address receiving funds at the destination
* @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens
* @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.
* @param toChainId The chainId of the destination chain
* @param amount The amount being sent
* @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
* @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.
* @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.
*/
function bridgeNativeTo(
address receiverAddress,
address l1bridgeAddr,
address relayer,
uint256 toChainId,
uint256 amount,
uint256 amountOutMin,
uint256 relayerFee,
uint256 deadline,
bytes32 metadata
) external payable {
IHopL1Bridge(l1bridgeAddr).sendToL2{value: amount}(
toChainId,
receiverAddress,
amount,
amountOutMin,
deadline,
relayer,
relayerFee
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
toChainId,
HopIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../interfaces/amm.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {HOP} from "../../../static/RouteIdentifiers.sol";
/**
* @title Hop-L2 Route Implementation
* @notice This is the L2 implementation, so this is used when transferring from l2 to supported l2s
* Called via SocketGateway if the routeId in the request maps to the routeId of HopL2-Implementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract HopImplL2 is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable HopIdentifier = HOP;
/// @notice Function-selector for ERC20-token bridging on Hop-L2-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4 public immutable HOP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,uint256,bytes32))"
)
);
/// @notice Function-selector for Native bridging on Hop-L2-Route
/// @dev This function selector is to be used while building transaction-data to bridge Native tokens
bytes4 public immutable HOP_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,bytes32)"
)
);
bytes4 public immutable HOP_L2_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes32))"
)
);
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
constructor(
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {}
/// @notice Struct to be used as a input parameter for Bridging tokens via Hop-L2-route
/// @dev while building transactionData,values should be set in this sequence of properties in this struct
struct HopBridgeRequestData {
// fees passed to relayer
uint256 bonderFee;
// The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
uint256 amountOutMin;
// The deadline for swapping in the destination AMM market. 0 if no swap is intended.
uint256 deadline;
// Minimum amount expected to be received or bridged to destination
uint256 amountOutMinDestination;
// deadline for bridging to destination
uint256 deadlineDestination;
// socket offchain created hash
bytes32 metadata;
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct HopBridgeDataNoToken {
// The address receiving funds at the destination
address receiverAddress;
// AMM address of Hop on L2
address hopAMM;
// The chainId of the destination chain
uint256 toChainId;
// fees passed to relayer
uint256 bonderFee;
// The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
uint256 amountOutMin;
// The deadline for swapping in the destination AMM market. 0 if no swap is intended.
uint256 deadline;
// Minimum amount expected to be received or bridged to destination
uint256 amountOutMinDestination;
// deadline for bridging to destination
uint256 deadlineDestination;
// socket offchain created hash
bytes32 metadata;
}
struct HopBridgeData {
/// @notice address of token being bridged
address token;
// The address receiving funds at the destination
address receiverAddress;
// AMM address of Hop on L2
address hopAMM;
// The chainId of the destination chain
uint256 toChainId;
// fees passed to relayer
uint256 bonderFee;
// The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
uint256 amountOutMin;
// The deadline for swapping in the destination AMM market. 0 if no swap is intended.
uint256 deadline;
// Minimum amount expected to be received or bridged to destination
uint256 amountOutMinDestination;
// deadline for bridging to destination
uint256 deadlineDestination;
// socket offchain created hash
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in HopBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for Hop-L2-Bridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
HopBridgeData memory hopData = abi.decode(bridgeData, (HopBridgeData));
if (hopData.token == NATIVE_TOKEN_ADDRESS) {
HopAMM(hopData.hopAMM).swapAndSend{value: amount}(
hopData.toChainId,
hopData.receiverAddress,
amount,
hopData.bonderFee,
hopData.amountOutMin,
hopData.deadline,
hopData.amountOutMinDestination,
hopData.deadlineDestination
);
} else {
// perform bridging
HopAMM(hopData.hopAMM).swapAndSend(
hopData.toChainId,
hopData.receiverAddress,
amount,
hopData.bonderFee,
hopData.amountOutMin,
hopData.deadline,
hopData.amountOutMinDestination,
hopData.deadlineDestination
);
}
emit SocketBridge(
amount,
hopData.token,
hopData.toChainId,
HopIdentifier,
msg.sender,
hopData.receiverAddress,
hopData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in HopBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param hopData encoded data for HopData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
HopBridgeDataNoToken calldata hopData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
HopAMM(hopData.hopAMM).swapAndSend{value: bridgeAmount}(
hopData.toChainId,
hopData.receiverAddress,
bridgeAmount,
hopData.bonderFee,
hopData.amountOutMin,
hopData.deadline,
hopData.amountOutMinDestination,
hopData.deadlineDestination
);
} else {
// perform bridging
HopAMM(hopData.hopAMM).swapAndSend(
hopData.toChainId,
hopData.receiverAddress,
bridgeAmount,
hopData.bonderFee,
hopData.amountOutMin,
hopData.deadline,
hopData.amountOutMinDestination,
hopData.deadlineDestination
);
}
emit SocketBridge(
bridgeAmount,
token,
hopData.toChainId,
HopIdentifier,
msg.sender,
hopData.receiverAddress,
hopData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Hop-L2-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress The address receiving funds at the destination
* @param token token being bridged
* @param hopAMM AMM address of Hop on L2
* @param amount The amount being bridged
* @param toChainId The chainId of the destination chain
* @param hopBridgeRequestData extraData for Bridging across Hop-L2
*/
function bridgeERC20To(
address receiverAddress,
address token,
address hopAMM,
uint256 amount,
uint256 toChainId,
HopBridgeRequestData calldata hopBridgeRequestData
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
HopAMM(hopAMM).swapAndSend(
toChainId,
receiverAddress,
amount,
hopBridgeRequestData.bonderFee,
hopBridgeRequestData.amountOutMin,
hopBridgeRequestData.deadline,
hopBridgeRequestData.amountOutMinDestination,
hopBridgeRequestData.deadlineDestination
);
emit SocketBridge(
amount,
token,
toChainId,
HopIdentifier,
msg.sender,
receiverAddress,
hopBridgeRequestData.metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Hop-L2-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress The address receiving funds at the destination
* @param hopAMM AMM address of Hop on L2
* @param amount The amount being bridged
* @param toChainId The chainId of the destination chain
* @param bonderFee fees passed to relayer
* @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.
* @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.
* @param amountOutMinDestination Minimum amount expected to be received or bridged to destination
* @param deadlineDestination deadline for bridging to destination
*/
function bridgeNativeTo(
address receiverAddress,
address hopAMM,
uint256 amount,
uint256 toChainId,
uint256 bonderFee,
uint256 amountOutMin,
uint256 deadline,
uint256 amountOutMinDestination,
uint256 deadlineDestination,
bytes32 metadata
) external payable {
// token address might not be indication thats why passed through extraData
// perform bridging
HopAMM(hopAMM).swapAndSend{value: amount}(
toChainId,
receiverAddress,
amount,
bonderFee,
amountOutMin,
deadline,
amountOutMinDestination,
deadlineDestination
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
toChainId,
HopIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./interfaces/hyphen.sol";
import "../BridgeImplBase.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {HYPHEN} from "../../static/RouteIdentifiers.sol";
/**
* @title Hyphen-Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via Hyphen-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of HyphenImplementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract HyphenImpl is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable HyphenIdentifier = HYPHEN;
/// @notice Function-selector for ERC20-token bridging on Hyphen-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4 public immutable HYPHEN_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256("bridgeERC20To(uint256,bytes32,address,address,uint256)")
);
/// @notice Function-selector for Native bridging on Hyphen-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens
bytes4 public immutable HYPHEN_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(keccak256("bridgeNativeTo(uint256,bytes32,address,uint256)"));
bytes4 public immutable HYPHEN_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256("swapAndBridge(uint32,bytes,(address,uint256,bytes32))")
);
/// @notice liquidityPoolManager - liquidityPool Manager of Hyphen used to bridge ERC20 and native
/// @dev this is to be initialized in constructor with a valid deployed address of hyphen-liquidityPoolManager
HyphenLiquidityPoolManager public immutable liquidityPoolManager;
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure liquidityPoolManager-address are set properly for the chainId in which the contract is being deployed
constructor(
address _liquidityPoolManager,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
liquidityPoolManager = HyphenLiquidityPoolManager(
_liquidityPoolManager
);
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct HyphenData {
/// @notice address of token being bridged
address token;
/// @notice address of receiver
address receiverAddress;
/// @notice chainId of destination
uint256 toChainId;
/// @notice socket offchain created hash
bytes32 metadata;
}
struct HyphenDataNoToken {
/// @notice address of receiver
address receiverAddress;
/// @notice chainId of destination
uint256 toChainId;
/// @notice chainId of destination
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for HyphenBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
HyphenData memory hyphenData = abi.decode(bridgeData, (HyphenData));
if (hyphenData.token == NATIVE_TOKEN_ADDRESS) {
liquidityPoolManager.depositNative{value: amount}(
hyphenData.receiverAddress,
hyphenData.toChainId,
"SOCKET"
);
} else {
ERC20(hyphenData.token).safeApprove(
address(liquidityPoolManager),
amount
);
liquidityPoolManager.depositErc20(
hyphenData.toChainId,
hyphenData.token,
hyphenData.receiverAddress,
amount,
"SOCKET"
);
}
emit SocketBridge(
amount,
hyphenData.token,
hyphenData.toChainId,
HyphenIdentifier,
msg.sender,
hyphenData.receiverAddress,
hyphenData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param hyphenData encoded data for hyphenData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
HyphenDataNoToken calldata hyphenData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
liquidityPoolManager.depositNative{value: bridgeAmount}(
hyphenData.receiverAddress,
hyphenData.toChainId,
"SOCKET"
);
} else {
ERC20(token).safeApprove(
address(liquidityPoolManager),
bridgeAmount
);
liquidityPoolManager.depositErc20(
hyphenData.toChainId,
token,
hyphenData.receiverAddress,
bridgeAmount,
"SOCKET"
);
}
emit SocketBridge(
bridgeAmount,
token,
hyphenData.toChainId,
HyphenIdentifier,
msg.sender,
hyphenData.receiverAddress,
hyphenData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Hyphen-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount to be sent
* @param receiverAddress address of the token to bridged to the destination chain.
* @param token address of token being bridged
* @param toChainId chainId of destination
*/
function bridgeERC20To(
uint256 amount,
bytes32 metadata,
address receiverAddress,
address token,
uint256 toChainId
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(address(liquidityPoolManager), amount);
liquidityPoolManager.depositErc20(
toChainId,
token,
receiverAddress,
amount,
"SOCKET"
);
emit SocketBridge(
amount,
token,
toChainId,
HyphenIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Hyphen-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount to be sent
* @param receiverAddress address of the token to bridged to the destination chain.
* @param toChainId chainId of destination
*/
function bridgeNativeTo(
uint256 amount,
bytes32 metadata,
address receiverAddress,
uint256 toChainId
) external payable {
liquidityPoolManager.depositNative{value: amount}(
receiverAddress,
toChainId,
"SOCKET"
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
toChainId,
HyphenIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
/**
* @title HyphenLiquidityPoolManager
* @notice interface with functions to bridge ERC20 and Native via Hyphen-Bridge
* @author Socket dot tech.
*/
interface HyphenLiquidityPoolManager {
/**
* @dev Function used to deposit tokens into pool to initiate a cross chain token transfer.
* @param toChainId Chain id where funds needs to be transfered
* @param tokenAddress ERC20 Token address that needs to be transfered
* @param receiver Address on toChainId where tokens needs to be transfered
* @param amount Amount of token being transfered
*/
function depositErc20(
uint256 toChainId,
address tokenAddress,
address receiver,
uint256 amount,
string calldata tag
) external;
/**
* @dev Function used to deposit native token into pool to initiate a cross chain token transfer.
* @param receiver Address on toChainId where tokens needs to be transfered
* @param toChainId Chain id where funds needs to be transfered
*/
function depositNative(
address receiver,
uint256 toChainId,
string calldata tag
) external payable;
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
interface L1StandardBridge {
/**
* @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of
* the deposit.
* @param _to Account to give the deposit to on L2.
* @param _l2Gas Gas limit required to complete the deposit on L2.
* @param _data Optional data to forward to L2. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function depositETHTo(
address _to,
uint32 _l2Gas,
bytes calldata _data
) external payable;
/**
* @dev deposit an amount of ERC20 to a recipient's balance on L2.
* @param _l1Token Address of the L1 ERC20 we are depositing
* @param _l2Token Address of the L1 respective L2 ERC20
* @param _to L2 address to credit the withdrawal to.
* @param _amount Amount of the ERC20 to deposit.
* @param _l2Gas Gas limit required to complete the deposit on L2.
* @param _data Optional data to forward to L2. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function depositERC20To(
address _l1Token,
address _l2Token,
address _to,
uint256 _amount,
uint32 _l2Gas,
bytes calldata _data
) external;
}
interface OldL1TokenGateway {
/**
* @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow
*
* @param _to Account to give the deposit to on L2
* @param _amount Amount of the ERC20 to deposit.
*/
function depositTo(address _to, uint256 _amount) external;
/**
* @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow
*
* @param currencyKey currencyKey for the SynthToken
* @param destination Account to give the deposit to on L2
* @param amount Amount of the ERC20 to deposit.
*/
function initiateSynthTransfer(
bytes32 currencyKey,
address destination,
uint256 amount
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "../interfaces/optimism.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {UnsupportedInterfaceId} from "../../../errors/SocketErrors.sol";
import {NATIVE_OPTIMISM} from "../../../static/RouteIdentifiers.sol";
/**
* @title NativeOptimism-Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via NativeOptimism-Bridge
* Tokens are bridged from Ethereum to Optimism Chain.
* Called via SocketGateway if the routeId in the request maps to the routeId of NativeOptimism-Implementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract NativeOptimismImpl is BridgeImplBase {
using SafeTransferLib for ERC20;
bytes32 public immutable NativeOptimismIdentifier = NATIVE_OPTIMISM;
uint256 public constant DESTINATION_CHAIN_ID = 10;
/// @notice Function-selector for ERC20-token bridging on Native-Optimism-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4
public immutable NATIVE_OPTIMISM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(address,address,address,uint32,(bytes32,bytes32),uint256,uint256,address,bytes)"
)
);
/// @notice Function-selector for Native bridging on Native-Optimism-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge Native balance
bytes4
public immutable NATIVE_OPTIMISM_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(address,address,uint32,uint256,bytes32,bytes)"
)
);
bytes4 public immutable NATIVE_OPTIMISM_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(uint256,bytes32,bytes32,address,address,uint32,address,bytes))"
)
);
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
constructor(
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct OptimismBridgeDataNoToken {
// interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)
uint256 interfaceId;
// currencyKey of the token beingBridged
bytes32 currencyKey;
// socket offchain created hash
bytes32 metadata;
// address of receiver of bridged tokens
address receiverAddress;
/**
* OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token
* contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
*/
address customBridgeAddress;
// Gas limit required to complete the deposit on L2.
uint32 l2Gas;
// Address of the L1 respective L2 ERC20
address l2Token;
// additional data , for ll contracts this will be 0x data or empty data
bytes data;
}
struct OptimismBridgeData {
// interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)
uint256 interfaceId;
// currencyKey of the token beingBridged
bytes32 currencyKey;
// socket offchain created hash
bytes32 metadata;
// address of receiver of bridged tokens
address receiverAddress;
/**
* OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token
* contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
*/
address customBridgeAddress;
/// @notice address of token being bridged
address token;
// Gas limit required to complete the deposit on L2.
uint32 l2Gas;
// Address of the L1 respective L2 ERC20
address l2Token;
// additional data , for ll contracts this will be 0x data or empty data
bytes data;
}
struct OptimismERC20Data {
bytes32 currencyKey;
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for Optimism-Bridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
OptimismBridgeData memory optimismBridgeData = abi.decode(
bridgeData,
(OptimismBridgeData)
);
emit SocketBridge(
amount,
optimismBridgeData.token,
DESTINATION_CHAIN_ID,
NativeOptimismIdentifier,
msg.sender,
optimismBridgeData.receiverAddress,
optimismBridgeData.metadata
);
if (optimismBridgeData.token == NATIVE_TOKEN_ADDRESS) {
L1StandardBridge(optimismBridgeData.customBridgeAddress)
.depositETHTo{value: amount}(
optimismBridgeData.receiverAddress,
optimismBridgeData.l2Gas,
optimismBridgeData.data
);
} else {
if (optimismBridgeData.interfaceId == 0) {
revert UnsupportedInterfaceId();
}
ERC20(optimismBridgeData.token).safeApprove(
optimismBridgeData.customBridgeAddress,
amount
);
if (optimismBridgeData.interfaceId == 1) {
// deposit into standard bridge
L1StandardBridge(optimismBridgeData.customBridgeAddress)
.depositERC20To(
optimismBridgeData.token,
optimismBridgeData.l2Token,
optimismBridgeData.receiverAddress,
amount,
optimismBridgeData.l2Gas,
optimismBridgeData.data
);
return;
}
// Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)
if (optimismBridgeData.interfaceId == 2) {
OldL1TokenGateway(optimismBridgeData.customBridgeAddress)
.depositTo(optimismBridgeData.receiverAddress, amount);
return;
}
if (optimismBridgeData.interfaceId == 3) {
OldL1TokenGateway(optimismBridgeData.customBridgeAddress)
.initiateSynthTransfer(
optimismBridgeData.currencyKey,
optimismBridgeData.receiverAddress,
amount
);
return;
}
}
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param optimismBridgeData encoded data for OptimismBridgeData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
OptimismBridgeDataNoToken calldata optimismBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
emit SocketBridge(
bridgeAmount,
token,
DESTINATION_CHAIN_ID,
NativeOptimismIdentifier,
msg.sender,
optimismBridgeData.receiverAddress,
optimismBridgeData.metadata
);
if (token == NATIVE_TOKEN_ADDRESS) {
L1StandardBridge(optimismBridgeData.customBridgeAddress)
.depositETHTo{value: bridgeAmount}(
optimismBridgeData.receiverAddress,
optimismBridgeData.l2Gas,
optimismBridgeData.data
);
} else {
if (optimismBridgeData.interfaceId == 0) {
revert UnsupportedInterfaceId();
}
ERC20(token).safeApprove(
optimismBridgeData.customBridgeAddress,
bridgeAmount
);
if (optimismBridgeData.interfaceId == 1) {
// deposit into standard bridge
L1StandardBridge(optimismBridgeData.customBridgeAddress)
.depositERC20To(
token,
optimismBridgeData.l2Token,
optimismBridgeData.receiverAddress,
bridgeAmount,
optimismBridgeData.l2Gas,
optimismBridgeData.data
);
return;
}
// Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)
if (optimismBridgeData.interfaceId == 2) {
OldL1TokenGateway(optimismBridgeData.customBridgeAddress)
.depositTo(
optimismBridgeData.receiverAddress,
bridgeAmount
);
return;
}
if (optimismBridgeData.interfaceId == 3) {
OldL1TokenGateway(optimismBridgeData.customBridgeAddress)
.initiateSynthTransfer(
optimismBridgeData.currencyKey,
optimismBridgeData.receiverAddress,
bridgeAmount
);
return;
}
}
}
/**
* @notice function to handle ERC20 bridging to receipent via NativeOptimism-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param token address of token being bridged
* @param receiverAddress address of receiver of bridged tokens
* @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token
* contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
* @param l2Gas Gas limit required to complete the deposit on L2.
* @param optimismData extra data needed for optimism bridge
* @param amount amount being bridged
* @param interfaceId interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)
* @param l2Token Address of the L1 respective L2 ERC20
* @param data additional data , for ll contracts this will be 0x data or empty data
*/
function bridgeERC20To(
address token,
address receiverAddress,
address customBridgeAddress,
uint32 l2Gas,
OptimismERC20Data calldata optimismData,
uint256 amount,
uint256 interfaceId,
address l2Token,
bytes calldata data
) external payable {
if (interfaceId == 0) {
revert UnsupportedInterfaceId();
}
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(customBridgeAddress, amount);
emit SocketBridge(
amount,
token,
DESTINATION_CHAIN_ID,
NativeOptimismIdentifier,
msg.sender,
receiverAddress,
optimismData.metadata
);
if (interfaceId == 1) {
// deposit into standard bridge
L1StandardBridge(customBridgeAddress).depositERC20To(
token,
l2Token,
receiverAddress,
amount,
l2Gas,
data
);
return;
}
// Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)
if (interfaceId == 2) {
OldL1TokenGateway(customBridgeAddress).depositTo(
receiverAddress,
amount
);
return;
}
if (interfaceId == 3) {
OldL1TokenGateway(customBridgeAddress).initiateSynthTransfer(
optimismData.currencyKey,
receiverAddress,
amount
);
return;
}
}
/**
* @notice function to handle native balance bridging to receipent via NativeOptimism-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress address of receiver of bridged tokens
* @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token
* contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
* @param l2Gas Gas limit required to complete the deposit on L2.
* @param amount amount being bridged
* @param data additional data , for ll contracts this will be 0x data or empty data
*/
function bridgeNativeTo(
address receiverAddress,
address customBridgeAddress,
uint32 l2Gas,
uint256 amount,
bytes32 metadata,
bytes calldata data
) external payable {
L1StandardBridge(customBridgeAddress).depositETHTo{value: amount}(
receiverAddress,
l2Gas,
data
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
DESTINATION_CHAIN_ID,
NativeOptimismIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title RootChain Manager Interface for Polygon Bridge.
*/
interface IRootChainManager {
/**
* @notice Move ether from root to child chain, accepts ether transfer
* Keep in mind this ether cannot be used to pay gas on child chain
* Use Matic tokens deposited using plasma mechanism for that
* @param user address of account that should receive WETH on child chain
*/
function depositEtherFor(address user) external payable;
/**
* @notice Move tokens from root to child chain
* @dev This mechanism supports arbitrary tokens as long as its predicate has been registered and the token is mapped
* @param sender address of account that should receive this deposit on child chain
* @param token address of token that is being deposited
* @param extraData bytes data that is sent to predicate and child token contracts to handle deposit
*/
function depositFor(
address sender,
address token,
bytes memory extraData
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "./interfaces/polygon.sol";
import {BridgeImplBase} from "../BridgeImplBase.sol";
import {NATIVE_POLYGON} from "../../static/RouteIdentifiers.sol";
/**
* @title NativePolygon-Route Implementation
* @notice This is the L1 implementation, so this is used when transferring from ethereum to polygon via their native bridge.
* @author Socket dot tech.
*/
contract NativePolygonImpl is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable NativePolyonIdentifier = NATIVE_POLYGON;
/// @notice destination-chain-Id for this router is always arbitrum
uint256 public constant DESTINATION_CHAIN_ID = 137;
/// @notice Function-selector for ERC20-token bridging on NativePolygon-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4
public immutable NATIVE_POLYGON_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(keccak256("bridgeERC20To(uint256,bytes32,address,address)"));
/// @notice Function-selector for Native bridging on NativePolygon-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens
bytes4
public immutable NATIVE_POLYGON_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(keccak256("bridgeNativeTo(uint256,bytes32,address)"));
bytes4 public immutable NATIVE_POLYGON_SWAP_BRIDGE_SELECTOR =
bytes4(keccak256("swapAndBridge(uint32,address,bytes32,bytes)"));
/// @notice root chain manager proxy on the ethereum chain
/// @dev to be initialised in the constructor
IRootChainManager public immutable rootChainManagerProxy;
/// @notice ERC20 Predicate proxy on the ethereum chain
/// @dev to be initialised in the constructor
address public immutable erc20PredicateProxy;
/**
* // @notice We set all the required addresses in the constructor while deploying the contract.
* // These will be constant addresses.
* // @dev Please use the Proxy addresses and not the implementation addresses while setting these
* // @param _rootChainManagerProxy address of the root chain manager proxy on the ethereum chain
* // @param _erc20PredicateProxy address of the ERC20 Predicate proxy on the ethereum chain.
* // @param _socketGateway address of the socketGateway contract that calls this contract
*/
constructor(
address _rootChainManagerProxy,
address _erc20PredicateProxy,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
rootChainManagerProxy = IRootChainManager(_rootChainManagerProxy);
erc20PredicateProxy = _erc20PredicateProxy;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for NativePolygon-Bridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
(address token, address receiverAddress, bytes32 metadata) = abi.decode(
bridgeData,
(address, address, bytes32)
);
if (token == NATIVE_TOKEN_ADDRESS) {
IRootChainManager(rootChainManagerProxy).depositEtherFor{
value: amount
}(receiverAddress);
} else {
ERC20(token).safeApprove(erc20PredicateProxy, amount);
// deposit into rootchain manager
IRootChainManager(rootChainManagerProxy).depositFor(
receiverAddress,
token,
abi.encodePacked(amount)
);
}
emit SocketBridge(
amount,
token,
DESTINATION_CHAIN_ID,
NativePolyonIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct
* @param swapId routeId for the swapImpl
* @param receiverAddress address of the receiver
* @param swapData encoded data for swap
*/
function swapAndBridge(
uint32 swapId,
address receiverAddress,
bytes32 metadata,
bytes calldata swapData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
IRootChainManager(rootChainManagerProxy).depositEtherFor{
value: bridgeAmount
}(receiverAddress);
} else {
ERC20(token).safeApprove(erc20PredicateProxy, bridgeAmount);
// deposit into rootchain manager
IRootChainManager(rootChainManagerProxy).depositFor(
receiverAddress,
token,
abi.encodePacked(bridgeAmount)
);
}
emit SocketBridge(
bridgeAmount,
token,
DESTINATION_CHAIN_ID,
NativePolyonIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via NativePolygon-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount of tokens being bridged
* @param receiverAddress recipient address
* @param token address of token being bridged
*/
function bridgeERC20To(
uint256 amount,
bytes32 metadata,
address receiverAddress,
address token
) external payable {
ERC20 tokenInstance = ERC20(token);
// set allowance for erc20 predicate
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(erc20PredicateProxy, amount);
// deposit into rootchain manager
rootChainManagerProxy.depositFor(
receiverAddress,
token,
abi.encodePacked(amount)
);
emit SocketBridge(
amount,
token,
DESTINATION_CHAIN_ID,
NativePolyonIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle Native bridging to receipent via NativePolygon-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount of tokens being bridged
* @param receiverAddress recipient address
*/
function bridgeNativeTo(
uint256 amount,
bytes32 metadata,
address receiverAddress
) external payable {
rootChainManagerProxy.depositEtherFor{value: amount}(receiverAddress);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
DESTINATION_CHAIN_ID,
NativePolyonIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
/// @notice interface with functions to interact with Refuel contract
interface IRefuel {
/**
* @notice function to deposit nativeToken to Destination-address on destinationChain
* @param destinationChainId chainId of the Destination chain
* @param _to recipient address
*/
function depositNativeToken(
uint256 destinationChainId,
address _to
) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./interfaces/refuel.sol";
import "../BridgeImplBase.sol";
import {REFUEL} from "../../static/RouteIdentifiers.sol";
/**
* @title Refuel-Route Implementation
* @notice Route implementation with functions to bridge Native via Refuel-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of RefuelImplementation
* @author Socket dot tech.
*/
contract RefuelBridgeImpl is BridgeImplBase {
bytes32 public immutable RefuelIdentifier = REFUEL;
/// @notice refuelBridge-Contract address used to deposit Native on Refuel-Bridge
address public immutable refuelBridge;
/// @notice Function-selector for Native bridging via Refuel-Bridge
/// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens
bytes4 public immutable REFUEL_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(keccak256("bridgeNativeTo(uint256,address,uint256,bytes32)"));
bytes4 public immutable REFUEL_NATIVE_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256("swapAndBridge(uint32,address,uint256,bytes32,bytes)")
);
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure _refuelBridge are set properly for the chainId in which the contract is being deployed
constructor(
address _refuelBridge,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
refuelBridge = _refuelBridge;
}
// Function to receive Ether. msg.data must be empty
receive() external payable {}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct RefuelBridgeData {
address receiverAddress;
uint256 toChainId;
bytes32 metadata;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct
* @param amount amount of tokens being bridged. this must be only native
* @param bridgeData encoded data for RefuelBridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
RefuelBridgeData memory refuelBridgeData = abi.decode(
bridgeData,
(RefuelBridgeData)
);
IRefuel(refuelBridge).depositNativeToken{value: amount}(
refuelBridgeData.toChainId,
refuelBridgeData.receiverAddress
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
refuelBridgeData.toChainId,
RefuelIdentifier,
msg.sender,
refuelBridgeData.receiverAddress,
refuelBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct
* @param swapId routeId for the swapImpl
* @param receiverAddress receiverAddress
* @param toChainId toChainId
* @param swapData encoded data for swap
*/
function swapAndBridge(
uint32 swapId,
address receiverAddress,
uint256 toChainId,
bytes32 metadata,
bytes calldata swapData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, ) = abi.decode(result, (uint256, address));
IRefuel(refuelBridge).depositNativeToken{value: bridgeAmount}(
toChainId,
receiverAddress
);
emit SocketBridge(
bridgeAmount,
NATIVE_TOKEN_ADDRESS,
toChainId,
RefuelIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Refuel-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param amount amount of native being refuelled to destination chain
* @param receiverAddress recipient address of the refuelled native
* @param toChainId destinationChainId
*/
function bridgeNativeTo(
uint256 amount,
address receiverAddress,
uint256 toChainId,
bytes32 metadata
) external payable {
IRefuel(refuelBridge).depositNativeToken{value: amount}(
toChainId,
receiverAddress
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
toChainId,
RefuelIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
/**
* @title IBridgeStargate Interface Contract.
* @notice Interface used by Stargate-L1 and L2 Router implementations
* @dev router and routerETH addresses will be distinct for L1 and L2
*/
interface IBridgeStargate {
// @notice Struct to hold the additional-data for bridging ERC20 token
struct lzTxObj {
// gas limit to bridge the token in Stargate to destinationChain
uint256 dstGasForCall;
// destination nativeAmount, this is always set as 0
uint256 dstNativeAmount;
// destination nativeAddress, this is always set as 0x
bytes dstNativeAddr;
}
/// @notice function in stargate bridge which is used to bridge ERC20 tokens to recipient on destinationChain
function swap(
uint16 _dstChainId,
uint256 _srcPoolId,
uint256 _dstPoolId,
address payable _refundAddress,
uint256 _amountLD,
uint256 _minAmountLD,
lzTxObj memory _lzTxParams,
bytes calldata _to,
bytes calldata _payload
) external payable;
/// @notice function in stargate bridge which is used to bridge native tokens to recipient on destinationChain
function swapETH(
uint16 _dstChainId, // destination Stargate chainId
address payable _refundAddress, // refund additional messageFee to this address
bytes calldata _toAddress, // the receiver of the destination ETH
uint256 _amountLD, // the amount, in Local Decimals, to be swapped
uint256 _minAmountLD // the minimum amount accepted out on destination
) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "../interfaces/stargate.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {STARGATE} from "../../../static/RouteIdentifiers.sol";
/**
* @title Stargate-L1-Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L1-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L1-Implementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract StargateImplL1 is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable StargateIdentifier = STARGATE;
/// @notice Function-selector for ERC20-token bridging on Stargate-L1-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4
public immutable STARGATE_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))"
)
);
/// @notice Function-selector for Native bridging on Stargate-L1-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens
bytes4
public immutable STARGATE_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)"
)
);
bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))"
)
);
/// @notice Stargate Router to bridge ERC20 tokens
IBridgeStargate public immutable router;
/// @notice Stargate Router to bridge native tokens
IBridgeStargate public immutable routerETH;
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed
constructor(
address _router,
address _routerEth,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
router = IBridgeStargate(_router);
routerETH = IBridgeStargate(_routerEth);
}
struct StargateBridgeExtraData {
uint256 srcPoolId;
uint256 dstPoolId;
uint256 destinationGasLimit;
uint256 minReceivedAmt;
bytes32 metadata;
bytes destinationPayload;
uint16 stargateDstChainId; // stargate defines chain id in its way
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct StargateBridgeDataNoToken {
address receiverAddress;
address senderAddress;
uint16 stargateDstChainId; // stargate defines chain id in its way
uint256 value;
// a unique identifier that is uses to dedup transfers
// this value is the a timestamp sent from frontend, but in theory can be any unique number
uint256 srcPoolId;
uint256 dstPoolId;
uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination
uint256 optionalValue;
uint256 destinationGasLimit;
bytes32 metadata;
bytes destinationPayload;
}
struct StargateBridgeData {
address token;
address receiverAddress;
address senderAddress;
uint16 stargateDstChainId; // stargate defines chain id in its way
uint256 value;
// a unique identifier that is uses to dedup transfers
// this value is the a timestamp sent from frontend, but in theory can be any unique number
uint256 srcPoolId;
uint256 dstPoolId;
uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination
uint256 optionalValue;
uint256 destinationGasLimit;
bytes32 metadata;
bytes destinationPayload;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for Stargate-L1-Bridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
StargateBridgeData memory stargateBridgeData = abi.decode(
bridgeData,
(StargateBridgeData)
);
if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {
// perform bridging
routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(
stargateBridgeData.stargateDstChainId,
payable(stargateBridgeData.senderAddress),
abi.encodePacked(stargateBridgeData.receiverAddress),
amount,
stargateBridgeData.minReceivedAmt
);
} else {
ERC20(stargateBridgeData.token).safeApprove(
address(router),
amount
);
{
router.swap{value: stargateBridgeData.value}(
stargateBridgeData.stargateDstChainId,
stargateBridgeData.srcPoolId,
stargateBridgeData.dstPoolId,
payable(stargateBridgeData.senderAddress), // default to refund to main contract
amount,
stargateBridgeData.minReceivedAmt,
IBridgeStargate.lzTxObj(
stargateBridgeData.destinationGasLimit,
0, // zero amount since this is a ERC20 bridging
"0x" //empty data since this is for only ERC20
),
abi.encodePacked(stargateBridgeData.receiverAddress),
stargateBridgeData.destinationPayload
);
}
}
emit SocketBridge(
amount,
stargateBridgeData.token,
stargateBridgeData.stargateDstChainId,
StargateIdentifier,
msg.sender,
stargateBridgeData.receiverAddress,
stargateBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param stargateBridgeData encoded data for StargateBridgeData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
StargateBridgeDataNoToken calldata stargateBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
// perform bridging
routerETH.swapETH{
value: bridgeAmount + stargateBridgeData.optionalValue
}(
stargateBridgeData.stargateDstChainId,
payable(stargateBridgeData.senderAddress),
abi.encodePacked(stargateBridgeData.receiverAddress),
bridgeAmount,
stargateBridgeData.minReceivedAmt
);
} else {
ERC20(token).safeApprove(address(router), bridgeAmount);
{
router.swap{value: stargateBridgeData.value}(
stargateBridgeData.stargateDstChainId,
stargateBridgeData.srcPoolId,
stargateBridgeData.dstPoolId,
payable(stargateBridgeData.senderAddress), // default to refund to main contract
bridgeAmount,
stargateBridgeData.minReceivedAmt,
IBridgeStargate.lzTxObj(
stargateBridgeData.destinationGasLimit,
0, // zero amount since this is a ERC20 bridging
"0x" //empty data since this is for only ERC20
),
abi.encodePacked(stargateBridgeData.receiverAddress),
stargateBridgeData.destinationPayload
);
}
}
emit SocketBridge(
bridgeAmount,
token,
stargateBridgeData.stargateDstChainId,
StargateIdentifier,
msg.sender,
stargateBridgeData.receiverAddress,
stargateBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param token address of token being bridged
* @param senderAddress address of sender
* @param receiverAddress address of recipient
* @param amount amount of token being bridge
* @param value value
* @param stargateBridgeExtraData stargate bridge extradata
*/
function bridgeERC20To(
address token,
address senderAddress,
address receiverAddress,
uint256 amount,
uint256 value,
StargateBridgeExtraData calldata stargateBridgeExtraData
) external payable {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(address(router), amount);
{
router.swap{value: value}(
stargateBridgeExtraData.stargateDstChainId,
stargateBridgeExtraData.srcPoolId,
stargateBridgeExtraData.dstPoolId,
payable(senderAddress), // default to refund to main contract
amount,
stargateBridgeExtraData.minReceivedAmt,
IBridgeStargate.lzTxObj(
stargateBridgeExtraData.destinationGasLimit,
0, // zero amount since this is a ERC20 bridging
"0x" //empty data since this is for only ERC20
),
abi.encodePacked(receiverAddress),
stargateBridgeExtraData.destinationPayload
);
}
emit SocketBridge(
amount,
token,
stargateBridgeExtraData.stargateDstChainId,
StargateIdentifier,
msg.sender,
receiverAddress,
stargateBridgeExtraData.metadata
);
}
/**
* @notice function to handle Native bridging to receipent via Stargate-L1-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param receiverAddress address of receipient
* @param senderAddress address of sender
* @param stargateDstChainId stargate defines chain id in its way
* @param amount amount of token being bridge
* @param minReceivedAmt defines the slippage, the min qty you would accept on the destination
* @param optionalValue optionalValue Native amount
*/
function bridgeNativeTo(
address receiverAddress,
address senderAddress,
uint16 stargateDstChainId,
uint256 amount,
uint256 minReceivedAmt,
uint256 optionalValue,
bytes32 metadata
) external payable {
// perform bridging
routerETH.swapETH{value: amount + optionalValue}(
stargateDstChainId,
payable(senderAddress),
abi.encodePacked(receiverAddress),
amount,
minReceivedAmt
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
stargateDstChainId,
StargateIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "../interfaces/stargate.sol";
import "../../../errors/SocketErrors.sol";
import {BridgeImplBase} from "../../BridgeImplBase.sol";
import {STARGATE} from "../../../static/RouteIdentifiers.sol";
/**
* @title Stargate-L2-Route Implementation
* @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L2-Bridge
* Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L2-Implementation
* Contains function to handle bridging as post-step i.e linked to a preceeding step for swap
* RequestData is different to just bride and bridging chained with swap
* @author Socket dot tech.
*/
contract StargateImplL2 is BridgeImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable StargateIdentifier = STARGATE;
/// @notice Function-selector for ERC20-token bridging on Stargate-L2-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens
bytes4
public immutable STARGATE_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeERC20To(address,address,address,uint256,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))"
)
);
bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =
bytes4(
keccak256(
"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))"
)
);
/// @notice Function-selector for Native bridging on Stargate-L2-Route
/// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens
bytes4
public immutable STARGATE_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)"
)
);
/// @notice Stargate Router to bridge ERC20 tokens
IBridgeStargate public immutable router;
/// @notice Stargate Router to bridge native tokens
IBridgeStargate public immutable routerETH;
/// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase
/// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed
constructor(
address _router,
address _routerEth,
address _socketGateway,
address _socketDeployFactory
) BridgeImplBase(_socketGateway, _socketDeployFactory) {
router = IBridgeStargate(_router);
routerETH = IBridgeStargate(_routerEth);
}
/// @notice Struct to be used as a input parameter for Bridging tokens via Stargate-L2-route
/// @dev while building transactionData,values should be set in this sequence of properties in this struct
struct StargateBridgeExtraData {
uint256 srcPoolId;
uint256 dstPoolId;
uint256 destinationGasLimit;
uint256 minReceivedAmt;
bytes32 metadata;
bytes destinationPayload;
uint16 stargateDstChainId; // stargate defines chain id in its way
}
/// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.
/// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct
struct StargateBridgeDataNoToken {
address receiverAddress;
address senderAddress;
uint16 stargateDstChainId; // stargate defines chain id in its way
uint256 value;
// a unique identifier that is uses to dedup transfers
// this value is the a timestamp sent from frontend, but in theory can be any unique number
uint256 srcPoolId;
uint256 dstPoolId;
uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination
uint256 optionalValue;
uint256 destinationGasLimit;
bytes32 metadata;
bytes destinationPayload;
}
struct StargateBridgeData {
address token;
address receiverAddress;
address senderAddress;
uint16 stargateDstChainId; // stargate defines chain id in its way
uint256 value;
// a unique identifier that is uses to dedup transfers
// this value is the a timestamp sent from frontend, but in theory can be any unique number
uint256 srcPoolId;
uint256 dstPoolId;
uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination
uint256 optionalValue;
uint256 destinationGasLimit;
bytes32 metadata;
bytes destinationPayload;
}
/**
* @notice function to bridge tokens after swap.
* @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct
* @param amount amount of tokens being bridged. this can be ERC20 or native
* @param bridgeData encoded data for Stargate-L1-Bridge
*/
function bridgeAfterSwap(
uint256 amount,
bytes calldata bridgeData
) external payable override {
StargateBridgeData memory stargateBridgeData = abi.decode(
bridgeData,
(StargateBridgeData)
);
if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {
// perform bridging
routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(
stargateBridgeData.stargateDstChainId,
payable(stargateBridgeData.senderAddress),
abi.encodePacked(stargateBridgeData.receiverAddress),
amount,
stargateBridgeData.minReceivedAmt
);
} else {
ERC20(stargateBridgeData.token).safeApprove(
address(router),
amount
);
{
router.swap{value: stargateBridgeData.value}(
stargateBridgeData.stargateDstChainId,
stargateBridgeData.srcPoolId,
stargateBridgeData.dstPoolId,
payable(stargateBridgeData.senderAddress), // default to refund to main contract
amount,
stargateBridgeData.minReceivedAmt,
IBridgeStargate.lzTxObj(
stargateBridgeData.destinationGasLimit,
0, // zero amount since this is a ERC20 bridging
"0x" //empty data since this is for only ERC20
),
abi.encodePacked(stargateBridgeData.receiverAddress),
stargateBridgeData.destinationPayload
);
}
}
emit SocketBridge(
amount,
stargateBridgeData.token,
stargateBridgeData.stargateDstChainId,
StargateIdentifier,
msg.sender,
stargateBridgeData.receiverAddress,
stargateBridgeData.metadata
);
}
/**
* @notice function to bridge tokens after swapping.
* @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.
* @notice This method is payable because the caller is doing token transfer and briding operation
* @dev for usage, refer to controller implementations
* encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct
* @param swapId routeId for the swapImpl
* @param swapData encoded data for swap
* @param stargateBridgeData encoded data for StargateBridgeData
*/
function swapAndBridge(
uint32 swapId,
bytes calldata swapData,
StargateBridgeDataNoToken calldata stargateBridgeData
) external payable {
(bool success, bytes memory result) = socketRoute
.getRoute(swapId)
.delegatecall(swapData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
(uint256 bridgeAmount, address token) = abi.decode(
result,
(uint256, address)
);
if (token == NATIVE_TOKEN_ADDRESS) {
routerETH.swapETH{
value: bridgeAmount + stargateBridgeData.optionalValue
}(
stargateBridgeData.stargateDstChainId,
payable(stargateBridgeData.senderAddress),
abi.encodePacked(stargateBridgeData.receiverAddress),
bridgeAmount,
stargateBridgeData.minReceivedAmt
);
} else {
ERC20(token).safeApprove(address(router), bridgeAmount);
{
router.swap{value: stargateBridgeData.value}(
stargateBridgeData.stargateDstChainId,
stargateBridgeData.srcPoolId,
stargateBridgeData.dstPoolId,
payable(stargateBridgeData.senderAddress), // default to refund to main contract
bridgeAmount,
stargateBridgeData.minReceivedAmt,
IBridgeStargate.lzTxObj(
stargateBridgeData.destinationGasLimit,
0,
"0x"
),
abi.encodePacked(stargateBridgeData.receiverAddress),
stargateBridgeData.destinationPayload
);
}
}
emit SocketBridge(
bridgeAmount,
token,
stargateBridgeData.stargateDstChainId,
StargateIdentifier,
msg.sender,
stargateBridgeData.receiverAddress,
stargateBridgeData.metadata
);
}
/**
* @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param token address of token being bridged
* @param senderAddress address of sender
* @param receiverAddress address of recipient
* @param amount amount of token being bridge
* @param value value
* @param optionalValue optionalValue
* @param stargateBridgeExtraData stargate bridge extradata
*/
function bridgeERC20To(
address token,
address senderAddress,
address receiverAddress,
uint256 amount,
uint256 value,
uint256 optionalValue,
StargateBridgeExtraData calldata stargateBridgeExtraData
) external payable {
// token address might not be indication thats why passed through extraData
if (token == NATIVE_TOKEN_ADDRESS) {
// perform bridging
routerETH.swapETH{value: amount + optionalValue}(
stargateBridgeExtraData.stargateDstChainId,
payable(senderAddress),
abi.encodePacked(receiverAddress),
amount,
stargateBridgeExtraData.minReceivedAmt
);
} else {
ERC20 tokenInstance = ERC20(token);
tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);
tokenInstance.safeApprove(address(router), amount);
{
router.swap{value: value}(
stargateBridgeExtraData.stargateDstChainId,
stargateBridgeExtraData.srcPoolId,
stargateBridgeExtraData.dstPoolId,
payable(senderAddress), // default to refund to main contract
amount,
stargateBridgeExtraData.minReceivedAmt,
IBridgeStargate.lzTxObj(
stargateBridgeExtraData.destinationGasLimit,
0, // zero amount since this is a ERC20 bridging
"0x" //empty data since this is for only ERC20
),
abi.encodePacked(receiverAddress),
stargateBridgeExtraData.destinationPayload
);
}
}
emit SocketBridge(
amount,
token,
stargateBridgeExtraData.stargateDstChainId,
StargateIdentifier,
msg.sender,
receiverAddress,
stargateBridgeExtraData.metadata
);
}
function bridgeNativeTo(
address receiverAddress,
address senderAddress,
uint16 stargateDstChainId,
uint256 amount,
uint256 minReceivedAmt,
uint256 optionalValue,
bytes32 metadata
) external payable {
// perform bridging
routerETH.swapETH{value: amount + optionalValue}(
stargateDstChainId,
payable(senderAddress),
abi.encodePacked(receiverAddress),
amount,
minReceivedAmt
);
emit SocketBridge(
amount,
NATIVE_TOKEN_ADDRESS,
stargateDstChainId,
StargateIdentifier,
msg.sender,
receiverAddress,
metadata
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {ISocketRequest} from "../interfaces/ISocketRequest.sol";
import {ISocketRoute} from "../interfaces/ISocketRoute.sol";
/// @title BaseController Controller
/// @notice Base contract for all controller contracts
abstract contract BaseController {
/// @notice Address used to identify if it is a native token transfer or not
address public immutable NATIVE_TOKEN_ADDRESS =
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
/// @notice Address used to identify if it is a Zero address
address public immutable NULL_ADDRESS = address(0);
/// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation
bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =
bytes4(keccak256("bridgeAfterSwap(uint256,bytes)"));
/// @notice immutable variable to store the socketGateway address
address public immutable socketGatewayAddress;
/// @notice immutable variable with instance of SocketRoute to access route functions
ISocketRoute public immutable socketRoute;
/**
* @notice Construct the base for all controllers.
* @param _socketGatewayAddress Socketgateway address, an immutable variable to set.
* @notice initialize the immutable variables of SocketRoute, SocketGateway
*/
constructor(address _socketGatewayAddress) {
socketGatewayAddress = _socketGatewayAddress;
socketRoute = ISocketRoute(_socketGatewayAddress);
}
/**
* @notice Construct the base for all BridgeImplementations.
* @param routeId routeId mapped to the routrImplementation
* @param data transactionData generated with arguments of bridgeRequest (offchain or by caller)
* @return returns the bytes response of the route execution (bridging, refuel or swap executions)
*/
function _executeRoute(
uint32 routeId,
bytes memory data
) internal returns (bytes memory) {
(bool success, bytes memory result) = socketRoute
.getRoute(routeId)
.delegatecall(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
return result;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {BaseController} from "./BaseController.sol";
import {ISocketRequest} from "../interfaces/ISocketRequest.sol";
/**
* @title FeesTaker-Controller Implementation
* @notice Controller with composed actions to deduct-fees followed by Refuel, Swap and Bridge
* to be executed Sequentially and this is atomic
* @author Socket dot tech.
*/
contract FeesTakerController is BaseController {
using SafeTransferLib for ERC20;
/// @notice event emitted upon fee-deduction to fees-taker address
event SocketFeesDeducted(
uint256 fees,
address feesToken,
address feesTaker
);
/// @notice Function-selector to invoke deduct-fees and swap token
/// @dev This function selector is to be used while building transaction-data
bytes4 public immutable FEES_TAKER_SWAP_FUNCTION_SELECTOR =
bytes4(
keccak256("takeFeesAndSwap((address,address,uint256,uint32,bytes))")
);
/// @notice Function-selector to invoke deduct-fees and bridge token
/// @dev This function selector is to be used while building transaction-data
bytes4 public immutable FEES_TAKER_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"takeFeesAndBridge((address,address,uint256,uint32,bytes))"
)
);
/// @notice Function-selector to invoke deduct-fees and bridge multiple tokens
/// @dev This function selector is to be used while building transaction-data
bytes4 public immutable FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"takeFeesAndMultiBridge((address,address,uint256,uint32[],bytes[]))"
)
);
/// @notice Function-selector to invoke deduct-fees followed by swapping of a token and bridging the swapped bridge
/// @dev This function selector is to be used while building transaction-data
bytes4 public immutable FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"takeFeeAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes))"
)
);
/// @notice Function-selector to invoke deduct-fees refuel
/// @notice followed by swapping of a token and bridging the swapped bridge
/// @dev This function selector is to be used while building transaction-data
bytes4 public immutable FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"takeFeeAndRefuelAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes,uint32,bytes))"
)
);
/// @notice socketGatewayAddress to be initialised via storage variable BaseController
constructor(
address _socketGatewayAddress
) BaseController(_socketGatewayAddress) {}
/**
* @notice function to deduct-fees to fees-taker address on source-chain and swap token
* @dev ensure correct function selector is used to generate transaction-data for bridgeRequest
* @param ftsRequest feesTakerSwapRequest object generated either off-chain or the calling contract using
* the function-selector FEES_TAKER_SWAP_FUNCTION_SELECTOR
* @return output bytes from the swap operation (last operation in the composed actions)
*/
function takeFeesAndSwap(
ISocketRequest.FeesTakerSwapRequest calldata ftsRequest
) external payable returns (bytes memory) {
if (ftsRequest.feesToken == NATIVE_TOKEN_ADDRESS) {
//transfer the native amount to the feeTakerAddress
payable(ftsRequest.feesTakerAddress).transfer(
ftsRequest.feesAmount
);
} else {
//transfer feesAmount to feesTakerAddress
ERC20(ftsRequest.feesToken).safeTransferFrom(
msg.sender,
ftsRequest.feesTakerAddress,
ftsRequest.feesAmount
);
}
emit SocketFeesDeducted(
ftsRequest.feesAmount,
ftsRequest.feesTakerAddress,
ftsRequest.feesToken
);
//call bridge function (executeRoute for the swapRequestData)
return _executeRoute(ftsRequest.routeId, ftsRequest.swapRequestData);
}
/**
* @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain
* @dev ensure correct function selector is used to generate transaction-data for bridgeRequest
* @param ftbRequest feesTakerBridgeRequest object generated either off-chain or the calling contract using
* the function-selector FEES_TAKER_BRIDGE_FUNCTION_SELECTOR
* @return output bytes from the bridge operation (last operation in the composed actions)
*/
function takeFeesAndBridge(
ISocketRequest.FeesTakerBridgeRequest calldata ftbRequest
) external payable returns (bytes memory) {
if (ftbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {
//transfer the native amount to the feeTakerAddress
payable(ftbRequest.feesTakerAddress).transfer(
ftbRequest.feesAmount
);
} else {
//transfer feesAmount to feesTakerAddress
ERC20(ftbRequest.feesToken).safeTransferFrom(
msg.sender,
ftbRequest.feesTakerAddress,
ftbRequest.feesAmount
);
}
emit SocketFeesDeducted(
ftbRequest.feesAmount,
ftbRequest.feesTakerAddress,
ftbRequest.feesToken
);
//call bridge function (executeRoute for the bridgeData)
return _executeRoute(ftbRequest.routeId, ftbRequest.bridgeRequestData);
}
/**
* @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain
* @notice multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array
* @dev ensure correct function selector is used to generate transaction-data for bridgeRequest
* @param ftmbRequest feesTakerMultiBridgeRequest object generated either off-chain or the calling contract using
* the function-selector FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR
*/
function takeFeesAndMultiBridge(
ISocketRequest.FeesTakerMultiBridgeRequest calldata ftmbRequest
) external payable {
if (ftmbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {
//transfer the native amount to the feeTakerAddress
payable(ftmbRequest.feesTakerAddress).transfer(
ftmbRequest.feesAmount
);
} else {
//transfer feesAmount to feesTakerAddress
ERC20(ftmbRequest.feesToken).safeTransferFrom(
msg.sender,
ftmbRequest.feesTakerAddress,
ftmbRequest.feesAmount
);
}
emit SocketFeesDeducted(
ftmbRequest.feesAmount,
ftmbRequest.feesTakerAddress,
ftmbRequest.feesToken
);
// multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array
for (
uint256 index = 0;
index < ftmbRequest.bridgeRouteIds.length;
++index
) {
//call bridge function (executeRoute for the bridgeData)
_executeRoute(
ftmbRequest.bridgeRouteIds[index],
ftmbRequest.bridgeRequestDataItems[index]
);
}
}
/**
* @notice function to deduct-fees to fees-taker address on source-chain followed by swap the amount on sourceChain followed by
* bridging the swapped amount to destinationChain
* @dev while generating implData for swap and bridgeRequests, ensure correct function selector is used
* bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation
* @param fsbRequest feesTakerSwapBridgeRequest object generated either off-chain or the calling contract using
* the function-selector FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR
*/
function takeFeeAndSwapAndBridge(
ISocketRequest.FeesTakerSwapBridgeRequest calldata fsbRequest
) external payable returns (bytes memory) {
if (fsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {
//transfer the native amount to the feeTakerAddress
payable(fsbRequest.feesTakerAddress).transfer(
fsbRequest.feesAmount
);
} else {
//transfer feesAmount to feesTakerAddress
ERC20(fsbRequest.feesToken).safeTransferFrom(
msg.sender,
fsbRequest.feesTakerAddress,
fsbRequest.feesAmount
);
}
emit SocketFeesDeducted(
fsbRequest.feesAmount,
fsbRequest.feesTakerAddress,
fsbRequest.feesToken
);
// execute swap operation
bytes memory swapResponseData = _executeRoute(
fsbRequest.swapRouteId,
fsbRequest.swapData
);
uint256 swapAmount = abi.decode(swapResponseData, (uint256));
// swapped amount is to be bridged to the recipient on destinationChain
bytes memory bridgeImpldata = abi.encodeWithSelector(
BRIDGE_AFTER_SWAP_SELECTOR,
swapAmount,
fsbRequest.bridgeData
);
// execute bridge operation and return the byte-data from response of bridge operation
return _executeRoute(fsbRequest.bridgeRouteId, bridgeImpldata);
}
/**
* @notice function to deduct-fees to fees-taker address on source-chain followed by refuel followed by
* swap the amount on sourceChain followed by bridging the swapped amount to destinationChain
* @dev while generating implData for refuel, swap and bridge Requests, ensure correct function selector is used
* bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation
* @param frsbRequest feesTakerRefuelSwapBridgeRequest object generated either off-chain or the calling contract using
* the function-selector FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR
*/
function takeFeeAndRefuelAndSwapAndBridge(
ISocketRequest.FeesTakerRefuelSwapBridgeRequest calldata frsbRequest
) external payable returns (bytes memory) {
if (frsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {
//transfer the native amount to the feeTakerAddress
payable(frsbRequest.feesTakerAddress).transfer(
frsbRequest.feesAmount
);
} else {
//transfer feesAmount to feesTakerAddress
ERC20(frsbRequest.feesToken).safeTransferFrom(
msg.sender,
frsbRequest.feesTakerAddress,
frsbRequest.feesAmount
);
}
emit SocketFeesDeducted(
frsbRequest.feesAmount,
frsbRequest.feesTakerAddress,
frsbRequest.feesToken
);
// refuel is also done via bridge execution via refuelRouteImplementation identified by refuelRouteId
_executeRoute(frsbRequest.refuelRouteId, frsbRequest.refuelData);
// execute swap operation
bytes memory swapResponseData = _executeRoute(
frsbRequest.swapRouteId,
frsbRequest.swapData
);
uint256 swapAmount = abi.decode(swapResponseData, (uint256));
// swapped amount is to be bridged to the recipient on destinationChain
bytes memory bridgeImpldata = abi.encodeWithSelector(
BRIDGE_AFTER_SWAP_SELECTOR,
swapAmount,
frsbRequest.bridgeData
);
// execute bridge operation and return the byte-data from response of bridge operation
return _executeRoute(frsbRequest.bridgeRouteId, bridgeImpldata);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {ISocketRequest} from "../interfaces/ISocketRequest.sol";
import {ISocketRoute} from "../interfaces/ISocketRoute.sol";
import {BaseController} from "./BaseController.sol";
/**
* @title RefuelSwapAndBridge Controller Implementation
* @notice Controller with composed actions for Refuel,Swap and Bridge to be executed Sequentially and this is atomic
* @author Socket dot tech.
*/
contract RefuelSwapAndBridgeController is BaseController {
/// @notice Function-selector to invoke refuel-swap-bridge function
/// @dev This function selector is to be used while buidling transaction-data
bytes4 public immutable REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =
bytes4(
keccak256(
"refuelAndSwapAndBridge((uint32,bytes,uint32,bytes,uint32,bytes))"
)
);
/// @notice socketGatewayAddress to be initialised via storage variable BaseController
constructor(
address _socketGatewayAddress
) BaseController(_socketGatewayAddress) {}
/**
* @notice function to handle refuel followed by Swap and Bridge actions
* @notice This method is payable because the caller is doing token transfer and briding operation
* @param rsbRequest Request with data to execute refuel followed by swap and bridge
* @return output data from bridging operation
*/
function refuelAndSwapAndBridge(
ISocketRequest.RefuelSwapBridgeRequest calldata rsbRequest
) public payable returns (bytes memory) {
_executeRoute(rsbRequest.refuelRouteId, rsbRequest.refuelData);
// refuel is also a bridging activity via refuel-route-implementation
bytes memory swapResponseData = _executeRoute(
rsbRequest.swapRouteId,
rsbRequest.swapData
);
uint256 swapAmount = abi.decode(swapResponseData, (uint256));
//sequence of arguments for implData: amount, token, data
// Bridging the swapAmount received in the preceeding step
bytes memory bridgeImpldata = abi.encodeWithSelector(
BRIDGE_AFTER_SWAP_SELECTOR,
swapAmount,
rsbRequest.bridgeData
);
return _executeRoute(rsbRequest.bridgeRouteId, bridgeImpldata);
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {ISocketGateway} from "../interfaces/ISocketGateway.sol";
import {OnlySocketGatewayOwner} from "../errors/SocketErrors.sol";
contract DisabledSocketRoute {
using SafeTransferLib for ERC20;
/// @notice immutable variable to store the socketGateway address
address public immutable socketGateway;
error RouteDisabled();
/**
* @notice Construct the base for all BridgeImplementations.
* @param _socketGateway Socketgateway address, an immutable variable to set.
*/
constructor(address _socketGateway) {
socketGateway = _socketGateway;
}
/// @notice Implementing contract needs to make use of the modifier where restricted access is to be used
modifier isSocketGatewayOwner() {
if (msg.sender != ISocketGateway(socketGateway).owner()) {
revert OnlySocketGatewayOwner();
}
_;
}
/**
* @notice function to rescue the ERC20 tokens in the bridge Implementation contract
* @notice this is a function restricted to Owner of SocketGateway only
* @param token address of ERC20 token being rescued
* @param userAddress receipient address to which ERC20 tokens will be rescued to
* @param amount amount of ERC20 tokens being rescued
*/
function rescueFunds(
address token,
address userAddress,
uint256 amount
) external isSocketGatewayOwner {
ERC20(token).safeTransfer(userAddress, amount);
}
/**
* @notice function to rescue the native-balance in the bridge Implementation contract
* @notice this is a function restricted to Owner of SocketGateway only
* @param userAddress receipient address to which native-balance will be rescued to
* @param amount amount of native balance tokens being rescued
*/
function rescueEther(
address payable userAddress,
uint256 amount
) external isSocketGatewayOwner {
userAddress.transfer(amount);
}
/**
* @notice Handle route function calls gracefully.
*/
fallback() external payable {
revert RouteDisabled();
}
/**
* @notice Support receiving ether to handle refunds etc.
*/
receive() external payable {}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../utils/Ownable.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {ISocketBridgeBase} from "../interfaces/ISocketBridgeBase.sol";
/**
* @dev In the constructor, set up the initialization code for socket
* contracts as well as the keccak256 hash of the given initialization code.
* that will be used to deploy any transient contracts, which will deploy any
* socket contracts that require the use of a constructor.
*
* Socket contract initialization code (29 bytes):
*
* 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3
*
* Description:
*
* pc|op|name | [stack] | <memory>
*
* ** set the first stack item to zero - used later **
* 00 58 getpc [0] <>
*
* ** set second stack item to 32, length of word returned from staticcall **
* 01 60 push1
* 02 20 outsize [0, 32] <>
*
* ** set third stack item to 0, position of word returned from staticcall **
* 03 81 dup2 [0, 32, 0] <>
*
* ** set fourth stack item to 4, length of selector given to staticcall **
* 04 58 getpc [0, 32, 0, 4] <>
*
* ** set fifth stack item to 28, position of selector given to staticcall **
* 05 60 push1
* 06 1c inpos [0, 32, 0, 4, 28] <>
*
* ** set the sixth stack item to msg.sender, target address for staticcall **
* 07 33 caller [0, 32, 0, 4, 28, caller] <>
*
* ** set the seventh stack item to msg.gas, gas to forward for staticcall **
* 08 5a gas [0, 32, 0, 4, 28, caller, gas] <>
*
* ** set the eighth stack item to selector, "what" to store via mstore **
* 09 63 push4
* 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <>
*
* ** set the ninth stack item to 0, "where" to store via mstore ***
* 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <>
*
* ** call mstore, consume 8 and 9 from the stack, place selector in memory **
* 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42>
*
* ** call staticcall, consume items 2 through 7, place address in memory **
* 13 fa staticcall [0, 1 (if successful)] <address>
*
* ** flip success bit in second stack item to set to 0 **
* 14 15 iszero [0, 0] <address>
*
* ** push a third 0 to the stack, position of address in memory **
* 15 81 dup2 [0, 0, 0] <address>
*
* ** place address from position in memory onto third stack item **
* 16 51 mload [0, 0, address] <>
*
* ** place address to fourth stack item for extcodesize to consume **
* 17 80 dup1 [0, 0, address, address] <>
*
* ** get extcodesize on fourth stack item for extcodecopy **
* 18 3b extcodesize [0, 0, address, size] <>
*
* ** dup and swap size for use by return at end of init code **
* 19 80 dup1 [0, 0, address, size, size] <>
* 20 93 swap4 [size, 0, address, size, 0] <>
*
* ** push code position 0 to stack and reorder stack items for extcodecopy **
* 21 80 dup1 [size, 0, address, size, 0, 0] <>
* 22 91 swap2 [size, 0, address, 0, 0, size] <>
* 23 92 swap3 [size, 0, size, 0, 0, address] <>
*
* ** call extcodecopy, consume four items, clone runtime code to memory **
* 24 3c extcodecopy [size, 0] <code>
*
* ** return to deploy final code in memory **
* 25 f3 return [] *deployed!*
*/
contract SocketDeployFactory is Ownable {
using SafeTransferLib for ERC20;
address public immutable disabledRouteAddress;
mapping(address => address) _implementations;
mapping(uint256 => bool) isDisabled;
mapping(uint256 => bool) isRouteDeployed;
mapping(address => bool) canDisableRoute;
event Deployed(address _addr);
event DisabledRoute(address _addr);
event Destroyed(address _addr);
error ContractAlreadyDeployed();
error NothingToDestroy();
error AlreadyDisabled();
error CannotBeDisabled();
error OnlyDisabler();
constructor(address _owner, address disabledRoute) Ownable(_owner) {
disabledRouteAddress = disabledRoute;
canDisableRoute[_owner] = true;
}
modifier onlyDisabler() {
if (!canDisableRoute[msg.sender]) {
revert OnlyDisabler();
}
_;
}
function addDisablerAddress(address disabler) external onlyOwner {
canDisableRoute[disabler] = true;
}
function removeDisablerAddress(address disabler) external onlyOwner {
canDisableRoute[disabler] = false;
}
/**
* @notice Deploys a route contract at predetermined location
* @notice Caller must first deploy the route contract at another location and pass its address as implementation.
* @param routeId route identifier
* @param implementationContract address of deployed route contract. Its byte code will be copied to predetermined location.
*/
function deploy(
uint256 routeId,
address implementationContract
) external onlyOwner returns (address) {
// assign the initialization code for the socket contract.
bytes memory initCode = (
hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3"
);
// determine the address of the socket contract.
address routeContractAddress = _getContractAddress(routeId);
if (isRouteDeployed[routeId]) {
revert ContractAlreadyDeployed();
}
isRouteDeployed[routeId] = true;
//first we deploy the code we want to deploy on a separate address
// store the implementation to be retrieved by the socket contract.
_implementations[routeContractAddress] = implementationContract;
address addr;
assembly {
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load init code's length.
addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt
}
require(
addr == routeContractAddress,
"Failed to deploy the new socket contract."
);
emit Deployed(addr);
return addr;
}
/**
* @notice Destroy the route deployed at a location.
* @param routeId route identifier to be destroyed.
*/
function destroy(uint256 routeId) external onlyDisabler {
// determine the address of the socket contract.
_destroy(routeId);
}
/**
* @notice Deploy a disabled contract at destroyed route to handle it gracefully.
* @param routeId route identifier to be disabled.
*/
function disableRoute(
uint256 routeId
) external onlyDisabler returns (address) {
return _disableRoute(routeId);
}
/**
* @notice Destroy a list of routeIds
* @param routeIds array of routeIds to be destroyed.
*/
function multiDestroy(uint256[] calldata routeIds) external onlyDisabler {
for (uint32 index = 0; index < routeIds.length; ) {
_destroy(routeIds[index]);
unchecked {
++index;
}
}
}
/**
* @notice Deploy a disabled contract at list of routeIds.
* @param routeIds array of routeIds to be disabled.
*/
function multiDisableRoute(
uint256[] calldata routeIds
) external onlyDisabler {
for (uint32 index = 0; index < routeIds.length; ) {
_disableRoute(routeIds[index]);
unchecked {
++index;
}
}
}
/**
* @dev External view function for calculating a socket contract address
* given a particular routeId.
*/
function getContractAddress(
uint256 routeId
) external view returns (address) {
// determine the address of the socket contract.
return _getContractAddress(routeId);
}
//those two functions are getting called by the socket Contract
function getImplementation()
external
view
returns (address implementation)
{
return _implementations[msg.sender];
}
function _disableRoute(uint256 routeId) internal returns (address) {
// assign the initialization code for the socket contract.
bytes memory initCode = (
hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3"
);
// determine the address of the socket contract.
address routeContractAddress = _getContractAddress(routeId);
if (!isRouteDeployed[routeId]) {
revert CannotBeDisabled();
}
if (isDisabled[routeId]) {
revert AlreadyDisabled();
}
isDisabled[routeId] = true;
//first we deploy the code we want to deploy on a separate address
// store the implementation to be retrieved by the socket contract.
_implementations[routeContractAddress] = disabledRouteAddress;
address addr;
assembly {
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load init code's length.
addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt.
}
require(
addr == routeContractAddress,
"Failed to deploy the new socket contract."
);
emit Deployed(addr);
return addr;
}
function _destroy(uint256 routeId) internal {
// determine the address of the socket contract.
address routeContractAddress = _getContractAddress(routeId);
if (!isRouteDeployed[routeId]) {
revert NothingToDestroy();
}
ISocketBridgeBase(routeContractAddress).killme();
emit Destroyed(routeContractAddress);
}
/**
* @dev Internal view function for calculating a socket contract address
* given a particular routeId.
*/
function _getContractAddress(
uint256 routeId
) internal view returns (address) {
// determine the address of the socket contract.
bytes memory initCode = (
hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3"
);
return
address(
uint160( // downcast to match the address type.
uint256( // convert to uint to truncate upper digits.
keccak256( // compute the CREATE2 hash using 4 inputs.
abi.encodePacked( // pack all inputs to the hash together.
hex"ff", // start with 0xff to distinguish from RLP.
address(this), // this contract will be the caller.
routeId, // the routeId is used as salt.
keccak256(abi.encodePacked(initCode)) // the init code hash.
)
)
)
)
);
}
/**
* @notice Rescues the ERC20 token to an address
this is a restricted function to be called by only socketGatewayOwner
* @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address
* @param token address of the ERC20 token being rescued
* @param userAddress address to which ERC20 is to be rescued
* @param amount amount of ERC20 tokens being rescued
*/
function rescueFunds(
address token,
address userAddress,
uint256 amount
) external onlyOwner {
ERC20(token).safeTransfer(userAddress, amount);
}
/**
* @notice Rescues the native balance to an address
this is a restricted function to be called by only socketGatewayOwner
* @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address
* @param userAddress address to which native-balance is to be rescued
* @param amount amount of native-balance being rescued
*/
function rescueEther(
address payable userAddress,
uint256 amount
) external onlyOwner {
userAddress.transfer(amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
error CelerRefundNotReady();
error OnlySocketDeployer();
error OnlySocketGatewayOwner();
error OnlySocketGateway();
error OnlyOwner();
error OnlyNominee();
error TransferIdExists();
error TransferIdDoesnotExist();
error Address0Provided();
error SwapFailed();
error UnsupportedInterfaceId();
error InvalidCelerRefund();
error CelerAlreadyRefunded();
error IncorrectBridgeRatios();
error ZeroAddressNotAllowed();
error ArrayLengthMismatch();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface ISocketBridgeBase {
function killme() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title ISocketController
* @notice Interface for SocketController functions.
* @dev functions can be added here for invocation from external contracts or off-chain
* only restriction is that this should have functions to manage controllers
* @author Socket dot tech.
*/
interface ISocketController {
/**
* @notice Add controller to the socketGateway
This is a restricted function to be called by only socketGatewayOwner
* @dev ensure controllerAddress is a verified controller implementation address
* @param _controllerAddress The address of controller implementation contract deployed
* @return Id of the controller added to the controllers-mapping in socketGateway storage
*/
function addController(
address _controllerAddress
) external returns (uint32);
/**
* @notice disable controller by setting ZeroAddress to the entry in controllers-mapping
identified by controllerId as key.
This is a restricted function to be called by only socketGatewayOwner
* @param _controllerId The Id of controller-implementation in the controllers mapping
*/
function disableController(uint32 _controllerId) external;
/**
* @notice Get controllerImplementation address mapped to the controllerId
* @param _controllerId controllerId is the key in the mapping for controllers
* @return controller-implementation address
*/
function getController(uint32 _controllerId) external returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title ISocketGateway
* @notice Interface for SocketGateway functions.
* @dev functions can be added here for invocation from external contracts or off-chain
* @author Socket dot tech.
*/
interface ISocketGateway {
/**
* @notice Request-struct for controllerRequests
* @dev ensure the value for data is generated using the function-selectors defined in the controllerImplementation contracts
*/
struct SocketControllerRequest {
// controllerId is the id mapped to the controllerAddress
uint32 controllerId;
// transactionImplData generated off-chain or by caller using function-selector of the controllerContract
bytes data;
}
// @notice view to get owner-address
function owner() external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title ISocketRoute
* @notice Interface with Request DataStructures to invoke controller functions.
* @author Socket dot tech.
*/
interface ISocketRequest {
struct SwapMultiBridgeRequest {
uint32 swapRouteId;
bytes swapImplData;
uint32[] bridgeRouteIds;
bytes[] bridgeImplDataItems;
uint256[] bridgeRatios;
bytes[] eventDataItems;
}
// Datastructure for Refuel-Swap-Bridge function
struct RefuelSwapBridgeRequest {
uint32 refuelRouteId;
bytes refuelData;
uint32 swapRouteId;
bytes swapData;
uint32 bridgeRouteId;
bytes bridgeData;
}
// Datastructure for DeductFees-Swap function
struct FeesTakerSwapRequest {
address feesTakerAddress;
address feesToken;
uint256 feesAmount;
uint32 routeId;
bytes swapRequestData;
}
// Datastructure for DeductFees-Bridge function
struct FeesTakerBridgeRequest {
address feesTakerAddress;
address feesToken;
uint256 feesAmount;
uint32 routeId;
bytes bridgeRequestData;
}
// Datastructure for DeductFees-MultiBridge function
struct FeesTakerMultiBridgeRequest {
address feesTakerAddress;
address feesToken;
uint256 feesAmount;
uint32[] bridgeRouteIds;
bytes[] bridgeRequestDataItems;
}
// Datastructure for DeductFees-Swap-Bridge function
struct FeesTakerSwapBridgeRequest {
address feesTakerAddress;
address feesToken;
uint256 feesAmount;
uint32 swapRouteId;
bytes swapData;
uint32 bridgeRouteId;
bytes bridgeData;
}
// Datastructure for DeductFees-Refuel-Swap-Bridge function
struct FeesTakerRefuelSwapBridgeRequest {
address feesTakerAddress;
address feesToken;
uint256 feesAmount;
uint32 refuelRouteId;
bytes refuelData;
uint32 swapRouteId;
bytes swapData;
uint32 bridgeRouteId;
bytes bridgeData;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @title ISocketRoute
* @notice Interface for routeManagement functions in SocketGateway.
* @author Socket dot tech.
*/
interface ISocketRoute {
/**
* @notice Add route to the socketGateway
This is a restricted function to be called by only socketGatewayOwner
* @dev ensure routeAddress is a verified bridge or middleware implementation address
* @param routeAddress The address of bridge or middleware implementation contract deployed
* @return Id of the route added to the routes-mapping in socketGateway storage
*/
function addRoute(address routeAddress) external returns (uint256);
/**
* @notice disable a route by setting ZeroAddress to the entry in routes-mapping
identified by routeId as key.
This is a restricted function to be called by only socketGatewayOwner
* @param routeId The Id of route-implementation in the routes mapping
*/
function disableRoute(uint32 routeId) external;
/**
* @notice Get routeImplementation address mapped to the routeId
* @param routeId routeId is the key in the mapping for routes
* @return route-implementation address
*/
function getRoute(uint32 routeId) external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// Functions taken out from https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
library LibBytes {
// solhint-disable no-inline-assembly
// LibBytes specific errors
error SliceOverflow();
error SliceOutOfBounds();
error AddressOutOfBounds();
error UintOutOfBounds();
// -------------------------
function concat(
bytes memory _preBytes,
bytes memory _postBytes
) internal pure returns (bytes memory) {
bytes memory tempBytes;
assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)
// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)
for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}
// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(
0x40,
and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
)
)
}
return tempBytes;
}
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
) internal pure returns (bytes memory) {
if (_length + 31 < _length) {
revert SliceOverflow();
}
if (_bytes.length < _start + _length) {
revert SliceOutOfBounds();
}
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(
add(tempBytes, lengthmod),
mul(0x20, iszero(lengthmod))
)
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(
add(
add(_bytes, lengthmod),
mul(0x20, iszero(lengthmod))
),
_start
)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./LibBytes.sol";
/// @title LibUtil library
/// @notice library with helper functions to operate on bytes-data and addresses
/// @author socket dot tech
library LibUtil {
/// @notice LibBytes library to handle operations on bytes
using LibBytes for bytes;
/// @notice function to extract revertMessage from bytes data
/// @dev use the revertMessage and then further revert with a custom revert and message
/// @param _res bytes data received from the transaction call
function getRevertMsg(
bytes memory _res
) internal pure returns (string memory) {
// If the _res length is less than 68, then the transaction failed silently (without a revert message)
if (_res.length < 68) {
return "Transaction reverted silently";
}
bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes
return abi.decode(revertData, (string)); // All that remains is the revert string
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.4;
// runtime proto sol library
library Pb {
enum WireType {
Varint,
Fixed64,
LengthDelim,
StartGroup,
EndGroup,
Fixed32
}
struct Buffer {
uint256 idx; // the start index of next read. when idx=b.length, we're done
bytes b; // hold serialized proto msg, readonly
}
// create a new in-memory Buffer object from raw msg bytes
function fromBytes(
bytes memory raw
) internal pure returns (Buffer memory buf) {
buf.b = raw;
buf.idx = 0;
}
// whether there are unread bytes
function hasMore(Buffer memory buf) internal pure returns (bool) {
return buf.idx < buf.b.length;
}
// decode current field number and wiretype
function decKey(
Buffer memory buf
) internal pure returns (uint256 tag, WireType wiretype) {
uint256 v = decVarint(buf);
tag = v / 8;
wiretype = WireType(v & 7);
}
// read varint from current buf idx, move buf.idx to next read, return the int value
function decVarint(Buffer memory buf) internal pure returns (uint256 v) {
bytes10 tmp; // proto int is at most 10 bytes (7 bits can be used per byte)
bytes memory bb = buf.b; // get buf.b mem addr to use in assembly
v = buf.idx; // use v to save one additional uint variable
assembly {
tmp := mload(add(add(bb, 32), v)) // load 10 bytes from buf.b[buf.idx] to tmp
}
uint256 b; // store current byte content
v = 0; // reset to 0 for return value
for (uint256 i = 0; i < 10; i++) {
assembly {
b := byte(i, tmp) // don't use tmp[i] because it does bound check and costs extra
}
v |= (b & 0x7F) << (i * 7);
if (b & 0x80 == 0) {
buf.idx += i + 1;
return v;
}
}
revert(); // i=10, invalid varint stream
}
// read length delimited field and return bytes
function decBytes(
Buffer memory buf
) internal pure returns (bytes memory b) {
uint256 len = decVarint(buf);
uint256 end = buf.idx + len;
require(end <= buf.b.length); // avoid overflow
b = new bytes(len);
bytes memory bufB = buf.b; // get buf.b mem addr to use in assembly
uint256 bStart;
uint256 bufBStart = buf.idx;
assembly {
bStart := add(b, 32)
bufBStart := add(add(bufB, 32), bufBStart)
}
for (uint256 i = 0; i < len; i += 32) {
assembly {
mstore(add(bStart, i), mload(add(bufBStart, i)))
}
}
buf.idx = end;
}
// move idx pass current value field, to beginning of next tag or msg end
function skipValue(Buffer memory buf, WireType wire) internal pure {
if (wire == WireType.Varint) {
decVarint(buf);
} else if (wire == WireType.LengthDelim) {
uint256 len = decVarint(buf);
buf.idx += len; // skip len bytes value data
require(buf.idx <= buf.b.length); // avoid overflow
} else {
revert();
} // unsupported wiretype
}
function _uint256(bytes memory b) internal pure returns (uint256 v) {
require(b.length <= 32); // b's length must be smaller than or equal to 32
assembly {
v := mload(add(b, 32))
} // load all 32bytes to v
v = v >> (8 * (32 - b.length)); // only first b.length is valid
}
function _address(bytes memory b) internal pure returns (address v) {
v = _addressPayable(b);
}
function _addressPayable(
bytes memory b
) internal pure returns (address payable v) {
require(b.length == 20);
//load 32bytes then shift right 12 bytes
assembly {
v := div(mload(add(b, 32)), 0x1000000000000000000000000)
}
}
function _bytes32(bytes memory b) internal pure returns (bytes32 v) {
require(b.length == 32);
assembly {
v := mload(add(b, 32))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma experimental ABIEncoderV2;
import "./utils/Ownable.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {LibUtil} from "./libraries/LibUtil.sol";
import "./libraries/LibBytes.sol";
import {ISocketRoute} from "./interfaces/ISocketRoute.sol";
import {ISocketRequest} from "./interfaces/ISocketRequest.sol";
import {ISocketGateway} from "./interfaces/ISocketGateway.sol";
import {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from "./errors/SocketErrors.sol";
/// @title SocketGatewayContract
/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer
/// @author Socket Team
contract SocketGatewayTemplate is Ownable {
using LibBytes for bytes;
using LibBytes for bytes4;
using SafeTransferLib for ERC20;
/// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation
bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =
bytes4(keccak256("bridgeAfterSwap(uint256,bytes)"));
/// @notice storage variable to keep track of total number of routes registered in socketgateway
uint32 public routesCount = 385;
/// @notice storage variable to keep track of total number of controllers registered in socketgateway
uint32 public controllerCount;
address public immutable disabledRouteAddress;
uint256 public constant CENT_PERCENT = 100e18;
/// @notice storage mapping for route implementation addresses
mapping(uint32 => address) public routes;
/// storage mapping for controller implemenation addresses
mapping(uint32 => address) public controllers;
// Events ------------------------------------------------------------------------------------------------------->
/// @notice Event emitted when a router is added to socketgateway
event NewRouteAdded(uint32 indexed routeId, address indexed route);
/// @notice Event emitted when a route is disabled
event RouteDisabled(uint32 indexed routeId);
/// @notice Event emitted when ownership transfer is requested by socket-gateway-owner
event OwnershipTransferRequested(
address indexed _from,
address indexed _to
);
/// @notice Event emitted when a controller is added to socketgateway
event ControllerAdded(
uint32 indexed controllerId,
address indexed controllerAddress
);
/// @notice Event emitted when a controller is disabled
event ControllerDisabled(uint32 indexed controllerId);
constructor(address _owner, address _disabledRoute) Ownable(_owner) {
disabledRouteAddress = _disabledRoute;
}
// Able to receive ether
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
/*******************************************
* EXTERNAL AND PUBLIC FUNCTIONS *
*******************************************/
/**
* @notice executes functions in the routes identified using routeId and functionSelectorData
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in routeData to be built using the function-selector defined as a
* constant in the route implementation contract
* @param routeId route identifier
* @param routeData functionSelectorData generated using the function-selector defined in the route Implementation
*/
function executeRoute(
uint32 routeId,
bytes calldata routeData
) external payable returns (bytes memory) {
(bool success, bytes memory result) = addressAt(routeId).delegatecall(
routeData
);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
return result;
}
/**
* @notice swaps a token on sourceChain and split it across multiple bridge-recipients
* @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped
* @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address
* @param swapMultiBridgeRequest request
*/
function swapAndMultiBridge(
ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest
) external payable {
uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;
if (
requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length
) {
revert ArrayLengthMismatch();
}
uint256 ratioAggregate;
for (uint256 index = 0; index < requestLength; ) {
ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];
}
if (ratioAggregate != CENT_PERCENT) {
revert IncorrectBridgeRatios();
}
(bool swapSuccess, bytes memory swapResult) = addressAt(
swapMultiBridgeRequest.swapRouteId
).delegatecall(swapMultiBridgeRequest.swapImplData);
if (!swapSuccess) {
assembly {
revert(add(swapResult, 32), mload(swapResult))
}
}
uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));
uint256 bridgedAmount;
for (uint256 index = 0; index < requestLength; ) {
uint256 bridgingAmount;
// if it is the last bridge request, bridge the remaining amount
if (index == requestLength - 1) {
bridgingAmount = amountReceivedFromSwap - bridgedAmount;
} else {
// bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap
bridgingAmount =
(amountReceivedFromSwap *
swapMultiBridgeRequest.bridgeRatios[index]) /
(CENT_PERCENT);
}
// update the bridged amount, this would be used for computation for last bridgeRequest
bridgedAmount += bridgingAmount;
bytes memory bridgeImpldata = abi.encodeWithSelector(
BRIDGE_AFTER_SWAP_SELECTOR,
bridgingAmount,
swapMultiBridgeRequest.bridgeImplDataItems[index]
);
(bool bridgeSuccess, bytes memory bridgeResult) = addressAt(
swapMultiBridgeRequest.bridgeRouteIds[index]
).delegatecall(bridgeImpldata);
if (!bridgeSuccess) {
assembly {
revert(add(bridgeResult, 32), mload(bridgeResult))
}
}
unchecked {
++index;
}
}
}
/**
* @notice sequentially executes functions in the routes identified using routeId and functionSelectorData
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in each dataItem to be built using the function-selector defined as a
* constant in the route implementation contract
* @param routeIds a list of route identifiers
* @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation
*/
function executeRoutes(
uint32[] calldata routeIds,
bytes[] calldata dataItems
) external payable {
uint256 routeIdslength = routeIds.length;
if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();
for (uint256 index = 0; index < routeIdslength; ) {
(bool success, bytes memory result) = addressAt(routeIds[index])
.delegatecall(dataItems[index]);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
unchecked {
++index;
}
}
}
/**
* @notice execute a controller function identified using the controllerId in the request
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in request to be built using the function-selector defined as a
* constant in the controller implementation contract
* @param socketControllerRequest socketControllerRequest with controllerId to identify the
* controllerAddress and byteData constructed using functionSelector
* of the function being invoked
* @return bytes data received from the call delegated to controller
*/
function executeController(
ISocketGateway.SocketControllerRequest calldata socketControllerRequest
) external payable returns (bytes memory) {
(bool success, bytes memory result) = controllers[
socketControllerRequest.controllerId
].delegatecall(socketControllerRequest.data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
return result;
}
/**
* @notice sequentially executes all controller requests
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in each controller-request to be built using the function-selector defined as a
* constant in the controller implementation contract
* @param controllerRequests a list of socketControllerRequest
* Each controllerRequest contains controllerId to identify the controllerAddress and
* byteData constructed using functionSelector of the function being invoked
*/
function executeControllers(
ISocketGateway.SocketControllerRequest[] calldata controllerRequests
) external payable {
for (uint32 index = 0; index < controllerRequests.length; ) {
(bool success, bytes memory result) = controllers[
controllerRequests[index].controllerId
].delegatecall(controllerRequests[index].data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
unchecked {
++index;
}
}
}
/**************************************
* ADMIN FUNCTIONS *
**************************************/
/**
* @notice Add route to the socketGateway
This is a restricted function to be called by only socketGatewayOwner
* @dev ensure routeAddress is a verified bridge or middleware implementation address
* @param routeAddress The address of bridge or middleware implementation contract deployed
* @return Id of the route added to the routes-mapping in socketGateway storage
*/
function addRoute(
address routeAddress
) external onlyOwner returns (uint32) {
uint32 routeId = routesCount;
routes[routeId] = routeAddress;
routesCount += 1;
emit NewRouteAdded(routeId, routeAddress);
return routeId;
}
/**
* @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress
This is a restricted function to be called by only socketGatewayOwner
*/
function setApprovalForRouters(
address[] memory routeAddresses,
address[] memory tokenAddresses,
bool isMax
) external onlyOwner {
for (uint32 index = 0; index < routeAddresses.length; ) {
ERC20(tokenAddresses[index]).approve(
routeAddresses[index],
isMax ? type(uint256).max : 0
);
unchecked {
++index;
}
}
}
/**
* @notice Add controller to the socketGateway
This is a restricted function to be called by only socketGatewayOwner
* @dev ensure controllerAddress is a verified controller implementation address
* @param controllerAddress The address of controller implementation contract deployed
* @return Id of the controller added to the controllers-mapping in socketGateway storage
*/
function addController(
address controllerAddress
) external onlyOwner returns (uint32) {
uint32 controllerId = controllerCount;
controllers[controllerId] = controllerAddress;
controllerCount += 1;
emit ControllerAdded(controllerId, controllerAddress);
return controllerId;
}
/**
* @notice disable controller by setting ZeroAddress to the entry in controllers-mapping
identified by controllerId as key.
This is a restricted function to be called by only socketGatewayOwner
* @param controllerId The Id of controller-implementation in the controllers mapping
*/
function disableController(uint32 controllerId) public onlyOwner {
controllers[controllerId] = disabledRouteAddress;
emit ControllerDisabled(controllerId);
}
/**
* @notice disable a route by setting ZeroAddress to the entry in routes-mapping
identified by routeId as key.
This is a restricted function to be called by only socketGatewayOwner
* @param routeId The Id of route-implementation in the routes mapping
*/
function disableRoute(uint32 routeId) external onlyOwner {
routes[routeId] = disabledRouteAddress;
emit RouteDisabled(routeId);
}
/*******************************************
* RESTRICTED RESCUE FUNCTIONS *
*******************************************/
/**
* @notice Rescues the ERC20 token to an address
this is a restricted function to be called by only socketGatewayOwner
* @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address
* @param token address of the ERC20 token being rescued
* @param userAddress address to which ERC20 is to be rescued
* @param amount amount of ERC20 tokens being rescued
*/
function rescueFunds(
address token,
address userAddress,
uint256 amount
) external onlyOwner {
ERC20(token).safeTransfer(userAddress, amount);
}
/**
* @notice Rescues the native balance to an address
this is a restricted function to be called by only socketGatewayOwner
* @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address
* @param userAddress address to which native-balance is to be rescued
* @param amount amount of native-balance being rescued
*/
function rescueEther(
address payable userAddress,
uint256 amount
) external onlyOwner {
userAddress.transfer(amount);
}
/*******************************************
* VIEW FUNCTIONS *
*******************************************/
/**
* @notice Get routeImplementation address mapped to the routeId
* @param routeId routeId is the key in the mapping for routes
* @return route-implementation address
*/
function getRoute(uint32 routeId) public view returns (address) {
return addressAt(routeId);
}
/**
* @notice Get controllerImplementation address mapped to the controllerId
* @param controllerId controllerId is the key in the mapping for controllers
* @return controller-implementation address
*/
function getController(uint32 controllerId) public view returns (address) {
return controllers[controllerId];
}
function addressAt(uint32 routeId) public view returns (address) {
if (routeId < 385) {
if (routeId < 257) {
if (routeId < 129) {
if (routeId < 65) {
if (routeId < 33) {
if (routeId < 17) {
if (routeId < 9) {
if (routeId < 5) {
if (routeId < 3) {
if (routeId == 1) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 3) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 7) {
if (routeId == 5) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 7) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 13) {
if (routeId < 11) {
if (routeId == 9) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 11) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 15) {
if (routeId == 13) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 15) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 25) {
if (routeId < 21) {
if (routeId < 19) {
if (routeId == 17) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 19) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 23) {
if (routeId == 21) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 23) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 29) {
if (routeId < 27) {
if (routeId == 25) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 27) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 31) {
if (routeId == 29) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 31) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
} else {
if (routeId < 49) {
if (routeId < 41) {
if (routeId < 37) {
if (routeId < 35) {
if (routeId == 33) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 35) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 39) {
if (routeId == 37) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 39) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 45) {
if (routeId < 43) {
if (routeId == 41) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 43) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 47) {
if (routeId == 45) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 47) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 57) {
if (routeId < 53) {
if (routeId < 51) {
if (routeId == 49) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 51) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 55) {
if (routeId == 53) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 55) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 61) {
if (routeId < 59) {
if (routeId == 57) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 59) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 63) {
if (routeId == 61) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 63) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
}
} else {
if (routeId < 97) {
if (routeId < 81) {
if (routeId < 73) {
if (routeId < 69) {
if (routeId < 67) {
if (routeId == 65) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 67) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 71) {
if (routeId == 69) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 71) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 77) {
if (routeId < 75) {
if (routeId == 73) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 75) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 79) {
if (routeId == 77) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 79) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 89) {
if (routeId < 85) {
if (routeId < 83) {
if (routeId == 81) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 83) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 87) {
if (routeId == 85) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 87) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 93) {
if (routeId < 91) {
if (routeId == 89) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 91) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 95) {
if (routeId == 93) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 95) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
} else {
if (routeId < 113) {
if (routeId < 105) {
if (routeId < 101) {
if (routeId < 99) {
if (routeId == 97) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 99) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 103) {
if (routeId == 101) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 103) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 109) {
if (routeId < 107) {
if (routeId == 105) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 107) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 111) {
if (routeId == 109) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 111) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 121) {
if (routeId < 117) {
if (routeId < 115) {
if (routeId == 113) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 115) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 119) {
if (routeId == 117) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 119) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 125) {
if (routeId < 123) {
if (routeId == 121) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 123) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 127) {
if (routeId == 125) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 127) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
}
}
} else {
if (routeId < 193) {
if (routeId < 161) {
if (routeId < 145) {
if (routeId < 137) {
if (routeId < 133) {
if (routeId < 131) {
if (routeId == 129) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 131) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 135) {
if (routeId == 133) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 135) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 141) {
if (routeId < 139) {
if (routeId == 137) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 139) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 143) {
if (routeId == 141) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 143) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 153) {
if (routeId < 149) {
if (routeId < 147) {
if (routeId == 145) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 147) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 151) {
if (routeId == 149) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 151) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 157) {
if (routeId < 155) {
if (routeId == 153) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 155) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 159) {
if (routeId == 157) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 159) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
} else {
if (routeId < 177) {
if (routeId < 169) {
if (routeId < 165) {
if (routeId < 163) {
if (routeId == 161) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 163) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 167) {
if (routeId == 165) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 167) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 173) {
if (routeId < 171) {
if (routeId == 169) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 171) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 175) {
if (routeId == 173) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 175) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 185) {
if (routeId < 181) {
if (routeId < 179) {
if (routeId == 177) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 179) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 183) {
if (routeId == 181) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 183) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 189) {
if (routeId < 187) {
if (routeId == 185) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 187) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 191) {
if (routeId == 189) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 191) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
}
} else {
if (routeId < 225) {
if (routeId < 209) {
if (routeId < 201) {
if (routeId < 197) {
if (routeId < 195) {
if (routeId == 193) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 195) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 199) {
if (routeId == 197) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 199) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 205) {
if (routeId < 203) {
if (routeId == 201) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 203) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 207) {
if (routeId == 205) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 207) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 217) {
if (routeId < 213) {
if (routeId < 211) {
if (routeId == 209) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 211) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 215) {
if (routeId == 213) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 215) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 221) {
if (routeId < 219) {
if (routeId == 217) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 219) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 223) {
if (routeId == 221) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 223) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
} else {
if (routeId < 241) {
if (routeId < 233) {
if (routeId < 229) {
if (routeId < 227) {
if (routeId == 225) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 227) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 231) {
if (routeId == 229) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 231) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 237) {
if (routeId < 235) {
if (routeId == 233) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 235) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 239) {
if (routeId == 237) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 239) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 249) {
if (routeId < 245) {
if (routeId < 243) {
if (routeId == 241) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 243) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 247) {
if (routeId == 245) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 247) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 253) {
if (routeId < 251) {
if (routeId == 249) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 251) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 255) {
if (routeId == 253) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 255) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
}
}
}
} else {
if (routeId < 321) {
if (routeId < 289) {
if (routeId < 273) {
if (routeId < 265) {
if (routeId < 261) {
if (routeId < 259) {
if (routeId == 257) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 259) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 263) {
if (routeId == 261) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 263) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 269) {
if (routeId < 267) {
if (routeId == 265) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 267) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 271) {
if (routeId == 269) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 271) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 281) {
if (routeId < 277) {
if (routeId < 275) {
if (routeId == 273) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 275) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 279) {
if (routeId == 277) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 279) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 285) {
if (routeId < 283) {
if (routeId == 281) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 283) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 287) {
if (routeId == 285) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 287) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
} else {
if (routeId < 305) {
if (routeId < 297) {
if (routeId < 293) {
if (routeId < 291) {
if (routeId == 289) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 291) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 295) {
if (routeId == 293) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 295) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 301) {
if (routeId < 299) {
if (routeId == 297) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 299) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 303) {
if (routeId == 301) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 303) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 313) {
if (routeId < 309) {
if (routeId < 307) {
if (routeId == 305) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 307) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 311) {
if (routeId == 309) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 311) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 317) {
if (routeId < 315) {
if (routeId == 313) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 315) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 319) {
if (routeId == 317) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 319) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
}
} else {
if (routeId < 353) {
if (routeId < 337) {
if (routeId < 329) {
if (routeId < 325) {
if (routeId < 323) {
if (routeId == 321) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 323) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 327) {
if (routeId == 325) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 327) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 333) {
if (routeId < 331) {
if (routeId == 329) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 331) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 335) {
if (routeId == 333) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 335) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 345) {
if (routeId < 341) {
if (routeId < 339) {
if (routeId == 337) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 339) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 343) {
if (routeId == 341) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 343) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 349) {
if (routeId < 347) {
if (routeId == 345) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 347) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 351) {
if (routeId == 349) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 351) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
} else {
if (routeId < 369) {
if (routeId < 361) {
if (routeId < 357) {
if (routeId < 355) {
if (routeId == 353) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 355) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 359) {
if (routeId == 357) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 359) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 365) {
if (routeId < 363) {
if (routeId == 361) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 363) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 367) {
if (routeId == 365) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 367) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
} else {
if (routeId < 377) {
if (routeId < 373) {
if (routeId < 371) {
if (routeId == 369) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 371) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 375) {
if (routeId == 373) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 375) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
} else {
if (routeId < 381) {
if (routeId < 379) {
if (routeId == 377) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 379) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
} else {
if (routeId < 383) {
if (routeId == 381) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
} else {
if (routeId == 383) {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
} else {
return
0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;
}
}
}
}
}
}
}
}
}
if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();
return routes[routeId];
}
/// @notice fallback function to handle swap, bridge execution
/// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction
fallback() external payable {
address routeAddress = addressAt(uint32(msg.sig));
bytes memory result;
assembly {
// copy function selector and any arguments
calldatacopy(0, 4, sub(calldatasize(), 4))
// execute function call using the facet
result := delegatecall(
gas(),
routeAddress,
0,
sub(calldatasize(), 4),
0,
0
)
// get any return value
returndatacopy(0, 0, returndatasize())
// return any return value or error back to the caller
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma experimental ABIEncoderV2;
import "./utils/Ownable.sol";
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {LibUtil} from "./libraries/LibUtil.sol";
import "./libraries/LibBytes.sol";
import {ISocketRoute} from "./interfaces/ISocketRoute.sol";
import {ISocketRequest} from "./interfaces/ISocketRequest.sol";
import {ISocketGateway} from "./interfaces/ISocketGateway.sol";
import {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from "./errors/SocketErrors.sol";
/// @title SocketGatewayContract
/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer
/// @author Socket Team
contract SocketGateway is Ownable {
using LibBytes for bytes;
using LibBytes for bytes4;
using SafeTransferLib for ERC20;
/// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation
bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =
bytes4(keccak256("bridgeAfterSwap(uint256,bytes)"));
/// @notice storage variable to keep track of total number of routes registered in socketgateway
uint32 public routesCount = 385;
/// @notice storage variable to keep track of total number of controllers registered in socketgateway
uint32 public controllerCount;
address public immutable disabledRouteAddress;
uint256 public constant CENT_PERCENT = 100e18;
/// @notice storage mapping for route implementation addresses
mapping(uint32 => address) public routes;
/// storage mapping for controller implemenation addresses
mapping(uint32 => address) public controllers;
// Events ------------------------------------------------------------------------------------------------------->
/// @notice Event emitted when a router is added to socketgateway
event NewRouteAdded(uint32 indexed routeId, address indexed route);
/// @notice Event emitted when a route is disabled
event RouteDisabled(uint32 indexed routeId);
/// @notice Event emitted when ownership transfer is requested by socket-gateway-owner
event OwnershipTransferRequested(
address indexed _from,
address indexed _to
);
/// @notice Event emitted when a controller is added to socketgateway
event ControllerAdded(
uint32 indexed controllerId,
address indexed controllerAddress
);
/// @notice Event emitted when a controller is disabled
event ControllerDisabled(uint32 indexed controllerId);
constructor(address _owner, address _disabledRoute) Ownable(_owner) {
disabledRouteAddress = _disabledRoute;
}
// Able to receive ether
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
/*******************************************
* EXTERNAL AND PUBLIC FUNCTIONS *
*******************************************/
/**
* @notice executes functions in the routes identified using routeId and functionSelectorData
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in routeData to be built using the function-selector defined as a
* constant in the route implementation contract
* @param routeId route identifier
* @param routeData functionSelectorData generated using the function-selector defined in the route Implementation
*/
function executeRoute(
uint32 routeId,
bytes calldata routeData
) external payable returns (bytes memory) {
(bool success, bytes memory result) = addressAt(routeId).delegatecall(
routeData
);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
return result;
}
/**
* @notice swaps a token on sourceChain and split it across multiple bridge-recipients
* @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped
* @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address
* @param swapMultiBridgeRequest request
*/
function swapAndMultiBridge(
ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest
) external payable {
uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;
if (
requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length
) {
revert ArrayLengthMismatch();
}
uint256 ratioAggregate;
for (uint256 index = 0; index < requestLength; ) {
ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];
}
if (ratioAggregate != CENT_PERCENT) {
revert IncorrectBridgeRatios();
}
(bool swapSuccess, bytes memory swapResult) = addressAt(
swapMultiBridgeRequest.swapRouteId
).delegatecall(swapMultiBridgeRequest.swapImplData);
if (!swapSuccess) {
assembly {
revert(add(swapResult, 32), mload(swapResult))
}
}
uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));
uint256 bridgedAmount;
for (uint256 index = 0; index < requestLength; ) {
uint256 bridgingAmount;
// if it is the last bridge request, bridge the remaining amount
if (index == requestLength - 1) {
bridgingAmount = amountReceivedFromSwap - bridgedAmount;
} else {
// bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap
bridgingAmount =
(amountReceivedFromSwap *
swapMultiBridgeRequest.bridgeRatios[index]) /
(CENT_PERCENT);
}
// update the bridged amount, this would be used for computation for last bridgeRequest
bridgedAmount += bridgingAmount;
bytes memory bridgeImpldata = abi.encodeWithSelector(
BRIDGE_AFTER_SWAP_SELECTOR,
bridgingAmount,
swapMultiBridgeRequest.bridgeImplDataItems[index]
);
(bool bridgeSuccess, bytes memory bridgeResult) = addressAt(
swapMultiBridgeRequest.bridgeRouteIds[index]
).delegatecall(bridgeImpldata);
if (!bridgeSuccess) {
assembly {
revert(add(bridgeResult, 32), mload(bridgeResult))
}
}
unchecked {
++index;
}
}
}
/**
* @notice sequentially executes functions in the routes identified using routeId and functionSelectorData
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in each dataItem to be built using the function-selector defined as a
* constant in the route implementation contract
* @param routeIds a list of route identifiers
* @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation
*/
function executeRoutes(
uint32[] calldata routeIds,
bytes[] calldata dataItems
) external payable {
uint256 routeIdslength = routeIds.length;
if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();
for (uint256 index = 0; index < routeIdslength; ) {
(bool success, bytes memory result) = addressAt(routeIds[index])
.delegatecall(dataItems[index]);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
unchecked {
++index;
}
}
}
/**
* @notice execute a controller function identified using the controllerId in the request
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in request to be built using the function-selector defined as a
* constant in the controller implementation contract
* @param socketControllerRequest socketControllerRequest with controllerId to identify the
* controllerAddress and byteData constructed using functionSelector
* of the function being invoked
* @return bytes data received from the call delegated to controller
*/
function executeController(
ISocketGateway.SocketControllerRequest calldata socketControllerRequest
) external payable returns (bytes memory) {
(bool success, bytes memory result) = controllers[
socketControllerRequest.controllerId
].delegatecall(socketControllerRequest.data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
return result;
}
/**
* @notice sequentially executes all controller requests
* @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped
* @dev ensure the data in each controller-request to be built using the function-selector defined as a
* constant in the controller implementation contract
* @param controllerRequests a list of socketControllerRequest
* Each controllerRequest contains controllerId to identify the controllerAddress and
* byteData constructed using functionSelector of the function being invoked
*/
function executeControllers(
ISocketGateway.SocketControllerRequest[] calldata controllerRequests
) external payable {
for (uint32 index = 0; index < controllerRequests.length; ) {
(bool success, bytes memory result) = controllers[
controllerRequests[index].controllerId
].delegatecall(controllerRequests[index].data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
unchecked {
++index;
}
}
}
/**************************************
* ADMIN FUNCTIONS *
**************************************/
/**
* @notice Add route to the socketGateway
This is a restricted function to be called by only socketGatewayOwner
* @dev ensure routeAddress is a verified bridge or middleware implementation address
* @param routeAddress The address of bridge or middleware implementation contract deployed
* @return Id of the route added to the routes-mapping in socketGateway storage
*/
function addRoute(
address routeAddress
) external onlyOwner returns (uint32) {
uint32 routeId = routesCount;
routes[routeId] = routeAddress;
routesCount += 1;
emit NewRouteAdded(routeId, routeAddress);
return routeId;
}
/**
* @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress
This is a restricted function to be called by only socketGatewayOwner
*/
function setApprovalForRouters(
address[] memory routeAddresses,
address[] memory tokenAddresses,
bool isMax
) external onlyOwner {
for (uint32 index = 0; index < routeAddresses.length; ) {
ERC20(tokenAddresses[index]).approve(
routeAddresses[index],
isMax ? type(uint256).max : 0
);
unchecked {
++index;
}
}
}
/**
* @notice Add controller to the socketGateway
This is a restricted function to be called by only socketGatewayOwner
* @dev ensure controllerAddress is a verified controller implementation address
* @param controllerAddress The address of controller implementation contract deployed
* @return Id of the controller added to the controllers-mapping in socketGateway storage
*/
function addController(
address controllerAddress
) external onlyOwner returns (uint32) {
uint32 controllerId = controllerCount;
controllers[controllerId] = controllerAddress;
controllerCount += 1;
emit ControllerAdded(controllerId, controllerAddress);
return controllerId;
}
/**
* @notice disable controller by setting ZeroAddress to the entry in controllers-mapping
identified by controllerId as key.
This is a restricted function to be called by only socketGatewayOwner
* @param controllerId The Id of controller-implementation in the controllers mapping
*/
function disableController(uint32 controllerId) public onlyOwner {
controllers[controllerId] = disabledRouteAddress;
emit ControllerDisabled(controllerId);
}
/**
* @notice disable a route by setting ZeroAddress to the entry in routes-mapping
identified by routeId as key.
This is a restricted function to be called by only socketGatewayOwner
* @param routeId The Id of route-implementation in the routes mapping
*/
function disableRoute(uint32 routeId) external onlyOwner {
routes[routeId] = disabledRouteAddress;
emit RouteDisabled(routeId);
}
/*******************************************
* RESTRICTED RESCUE FUNCTIONS *
*******************************************/
/**
* @notice Rescues the ERC20 token to an address
this is a restricted function to be called by only socketGatewayOwner
* @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address
* @param token address of the ERC20 token being rescued
* @param userAddress address to which ERC20 is to be rescued
* @param amount amount of ERC20 tokens being rescued
*/
function rescueFunds(
address token,
address userAddress,
uint256 amount
) external onlyOwner {
ERC20(token).safeTransfer(userAddress, amount);
}
/**
* @notice Rescues the native balance to an address
this is a restricted function to be called by only socketGatewayOwner
* @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address
* @param userAddress address to which native-balance is to be rescued
* @param amount amount of native-balance being rescued
*/
function rescueEther(
address payable userAddress,
uint256 amount
) external onlyOwner {
userAddress.transfer(amount);
}
/*******************************************
* VIEW FUNCTIONS *
*******************************************/
/**
* @notice Get routeImplementation address mapped to the routeId
* @param routeId routeId is the key in the mapping for routes
* @return route-implementation address
*/
function getRoute(uint32 routeId) public view returns (address) {
return addressAt(routeId);
}
/**
* @notice Get controllerImplementation address mapped to the controllerId
* @param controllerId controllerId is the key in the mapping for controllers
* @return controller-implementation address
*/
function getController(uint32 controllerId) public view returns (address) {
return controllers[controllerId];
}
function addressAt(uint32 routeId) public view returns (address) {
if (routeId < 385) {
if (routeId < 257) {
if (routeId < 129) {
if (routeId < 65) {
if (routeId < 33) {
if (routeId < 17) {
if (routeId < 9) {
if (routeId < 5) {
if (routeId < 3) {
if (routeId == 1) {
return
0x8cd6BaCDAe46B449E2e5B34e348A4eD459c84D50;
} else {
return
0x31524750Cd865fF6A3540f232754Fb974c18585C;
}
} else {
if (routeId == 3) {
return
0xEd9b37342BeC8f3a2D7b000732ec87498aA6EC6a;
} else {
return
0xE8704Ef6211F8988Ccbb11badC89841808d66890;
}
}
} else {
if (routeId < 7) {
if (routeId == 5) {
return
0x9aFF58C460a461578C433e11C4108D1c4cF77761;
} else {
return
0x2D1733886cFd465B0B99F1492F40847495f334C5;
}
} else {
if (routeId == 7) {
return
0x715497Be4D130F04B8442F0A1F7a9312D4e54FC4;
} else {
return
0x90C8a40c38E633B5B0e0d0585b9F7FA05462CaaF;
}
}
}
} else {
if (routeId < 13) {
if (routeId < 11) {
if (routeId == 9) {
return
0xa402b70FCfF3F4a8422B93Ef58E895021eAdE4F6;
} else {
return
0xc1B718522E15CD42C4Ac385a929fc2B51f5B892e;
}
} else {
if (routeId == 11) {
return
0xa97bf2f7c26C43c010c349F52f5eA5dC49B2DD38;
} else {
return
0x969423d71b62C81d2f28d707364c9Dc4a0764c53;
}
}
} else {
if (routeId < 15) {
if (routeId == 13) {
return
0xF86729934C083fbEc8C796068A1fC60701Ea1207;
} else {
return
0xD7cC2571F5823caCA26A42690D2BE7803DD5393f;
}
} else {
if (routeId == 15) {
return
0x7c8837a279bbbf7d8B93413763176de9F65d5bB9;
} else {
return
0x13b81C27B588C07D04458ed7dDbdbD26D1e39bcc;
}
}
}
}
} else {
if (routeId < 25) {
if (routeId < 21) {
if (routeId < 19) {
if (routeId == 17) {
return
0x52560Ac678aFA1345D15474287d16Dc1eA3F78aE;
} else {
return
0x1E31e376551459667cd7643440c1b21CE69065A0;
}
} else {
if (routeId == 19) {
return
0xc57D822CB3288e7b97EF8f8af0EcdcD1B783529B;
} else {
return
0x2197A1D9Af24b4d6a64Bff95B4c29Fcd3Ff28C30;
}
}
} else {
if (routeId < 23) {
if (routeId == 21) {
return
0xE3700feAa5100041Bf6b7AdBA1f72f647809Fd00;
} else {
return
0xc02E8a0Fdabf0EeFCEA025163d90B5621E2b9948;
}
} else {
if (routeId == 23) {
return
0xF5144235E2926cAb3c69b30113254Fa632f72d62;
} else {
return
0xBa3F92313B00A1f7Bc53b2c24EB195c8b2F57682;
}
}
}
} else {
if (routeId < 29) {
if (routeId < 27) {
if (routeId == 25) {
return
0x77a6856fe1fFA5bEB55A1d2ED86E27C7c482CB76;
} else {
return
0x4826Ff4e01E44b1FCEFBfb38cd96687Eb7786b44;
}
} else {
if (routeId == 27) {
return
0x55FF3f5493cf5e80E76DEA7E327b9Cd8440Af646;
} else {
return
0xF430Db544bE9770503BE4aa51997aA19bBd5BA4f;
}
}
} else {
if (routeId < 31) {
if (routeId == 29) {
return
0x0f166446ce1484EE3B0663E7E67DF10F5D240115;
} else {
return
0x6365095D92537f242Db5EdFDd572745E72aC33d9;
}
} else {
if (routeId == 31) {
return
0x5c7BC93f06ce3eAe75ADf55E10e23d2c1dE5Bc65;
} else {
return
0xe46383bAD90d7A08197ccF08972e9DCdccCE9BA4;
}
}
}
}
}
} else {
if (routeId < 49) {
if (routeId < 41) {
if (routeId < 37) {
if (routeId < 35) {
if (routeId == 33) {
return
0xf0f21710c071E3B728bdc4654c3c0b873aAaa308;
} else {
return
0x63Bc9ed3AcAAeB0332531C9fB03b0a2352E9Ff25;
}
} else {
if (routeId == 35) {
return
0xd1CE808625CB4007a1708824AE82CdB0ece57De9;
} else {
return
0x57BbB148112f4ba224841c3FE018884171004661;
}
}
} else {
if (routeId < 39) {
if (routeId == 37) {
return
0x037f7d6933036F34DFabd40Ff8e4D789069f92e3;
} else {
return
0xeF978c280915CfF3Dca4EDfa8932469e40ADA1e1;
}
} else {
if (routeId == 39) {
return
0x92ee9e071B13f7ecFD62B7DED404A16CBc223CD3;
} else {
return
0x94Ae539c186e41ed762271338Edf140414D1E442;
}
}
}
} else {
if (routeId < 45) {
if (routeId < 43) {
if (routeId == 41) {
return
0x30A64BBe4DdBD43dA2368EFd1eB2d80C10d84DAb;
} else {
return
0x3aEABf81c1Dc4c1b73d5B2a95410f126426FB596;
}
} else {
if (routeId == 43) {
return
0x25b08aB3D0C8ea4cC9d967b79688C6D98f3f563a;
} else {
return
0xea40cB15C9A3BBd27af6474483886F7c0c9AE406;
}
}
} else {
if (routeId < 47) {
if (routeId == 45) {
return
0x9580113Cc04e5a0a03359686304EF3A80b936Dd3;
} else {
return
0xD211c826d568957F3b66a3F4d9c5f68cCc66E619;
}
} else {
if (routeId == 47) {
return
0xCEE24D0635c4C56315d133b031984d4A6f509476;
} else {
return
0x3922e6B987983229798e7A20095EC372744d4D4c;
}
}
}
}
} else {
if (routeId < 57) {
if (routeId < 53) {
if (routeId < 51) {
if (routeId == 49) {
return
0x2d92D03413d296e1F31450479349757187F2a2b7;
} else {
return
0x0fe5308eE90FC78F45c89dB6053eA859097860CA;
}
} else {
if (routeId == 51) {
return
0x08Ba68e067C0505bAF0C1311E0cFB2B1B59b969c;
} else {
return
0x9bee5DdDF75C24897374f92A534B7A6f24e97f4a;
}
}
} else {
if (routeId < 55) {
if (routeId == 53) {
return
0x1FC5A90B232208704B930c1edf82FFC6ACc02734;
} else {
return
0x5b1B0417cb44c761C2a23ee435d011F0214b3C85;
}
} else {
if (routeId == 55) {
return
0x9d70cDaCA12A738C283020760f449D7816D592ec;
} else {
return
0x95a23b9CB830EcCFDDD5dF56A4ec665e3381Fa12;
}
}
}
} else {
if (routeId < 61) {
if (routeId < 59) {
if (routeId == 57) {
return
0x483a957Cf1251c20e096C35c8399721D1200A3Fc;
} else {
return
0xb4AD39Cb293b0Ec7FEDa743442769A7FF04987CD;
}
} else {
if (routeId == 59) {
return
0x4C543AD78c1590D81BAe09Fc5B6Df4132A2461d0;
} else {
return
0x471d5E5195c563902781734cfe1FF3981F8B6c86;
}
}
} else {
if (routeId < 63) {
if (routeId == 61) {
return
0x1B12a54B5E606D95B8B8D123c9Cb09221Ee37584;
} else {
return
0xE4127cC550baC433646a7D998775a84daC16c7f3;
}
} else {
if (routeId == 63) {
return
0xecb1b55AB12E7dd788D585c6C5cD61B5F87be836;
} else {
return
0xf91ef487C5A1579f70601b6D347e19756092eEBf;
}
}
}
}
}
}
} else {
if (routeId < 97) {
if (routeId < 81) {
if (routeId < 73) {
if (routeId < 69) {
if (routeId < 67) {
if (routeId == 65) {
return
0x34a16a7e9BADEEFD4f056310cbE0b1423Fa1b760;
} else {
return
0x60E10E80c7680f429dBbC232830BEcd3D623c4CF;
}
} else {
if (routeId == 67) {
return
0x66465285B8D65362A1d86CE00fE2bE949Fd6debF;
} else {
return
0x5aB231B7e1A3A74a48f67Ab7bde5Cdd4267022E0;
}
}
} else {
if (routeId < 71) {
if (routeId == 69) {
return
0x3A1C3633eE79d43366F5c67802a746aFD6b162Ba;
} else {
return
0x0C4BfCbA8dC3C811437521a80E81e41DAF479039;
}
} else {
if (routeId == 71) {
return
0x6caf25d2e139C5431a1FA526EAf8d73ff2e6252C;
} else {
return
0x74ad21e09FDa68638CE14A3009A79B6D16574257;
}
}
}
} else {
if (routeId < 77) {
if (routeId < 75) {
if (routeId == 73) {
return
0xD4923A61008894b99cc1CD3407eF9524f02aA0Ca;
} else {
return
0x6F159b5EB823BD415886b9271aA2A723a00a1987;
}
} else {
if (routeId == 75) {
return
0x742a8aA42E7bfB4554dE30f4Fb07FFb6f2068863;
} else {
return
0x4AE9702d3360400E47B446e76DE063ACAb930101;
}
}
} else {
if (routeId < 79) {
if (routeId == 77) {
return
0x0E19a0a44ddA7dAD854ec5Cc867d16869c4E80F4;
} else {
return
0xE021A51968f25148F726E326C88d2556c5647557;
}
} else {
if (routeId == 79) {
return
0x64287BDDDaeF4d94E4599a3D882bed29E6Ada4B6;
} else {
return
0xcBB57Fd2e19cc7e9D444d5b4325A2F1047d0C73f;
}
}
}
}
} else {
if (routeId < 89) {
if (routeId < 85) {
if (routeId < 83) {
if (routeId == 81) {
return
0x373DE80DF7D82cFF6D76F29581b360C56331e957;
} else {
return
0x0466356E131AD61596a51F86BAd1C03A328960D8;
}
} else {
if (routeId == 83) {
return
0x01726B960992f1b74311b248E2a922fC707d43A6;
} else {
return
0x2E21bdf9A4509b89795BCE7E132f248a75814CEc;
}
}
} else {
if (routeId < 87) {
if (routeId == 85) {
return
0x769512b23aEfF842379091d3B6E4B5456F631D42;
} else {
return
0xe7eD9be946a74Ec19325D39C6EEb57887ccB2B0D;
}
} else {
if (routeId == 87) {
return
0xc4D01Ec357c2b511d10c15e6b6974380F0E62e67;
} else {
return
0x5bC49CC9dD77bECF2fd3A3C55611e84E69AFa3AE;
}
}
}
} else {
if (routeId < 93) {
if (routeId < 91) {
if (routeId == 89) {
return
0x48bcD879954fA14e7DbdAeb56F79C1e9DDcb69ec;
} else {
return
0xE929bDde21b462572FcAA4de6F49B9D3246688D0;
}
} else {
if (routeId == 91) {
return
0x85Aae300438222f0e3A9Bc870267a5633A9438bd;
} else {
return
0x51f72E1096a81C55cd142d66d39B688C657f9Be8;
}
}
} else {
if (routeId < 95) {
if (routeId == 93) {
return
0x3A8a05BF68ac54B01E6C0f492abF97465F3d15f9;
} else {
return
0x145aA67133F0c2C36b9771e92e0B7655f0D59040;
}
} else {
if (routeId == 95) {
return
0xa030315d7DB11F9892758C9e7092D841e0ADC618;
} else {
return
0xdF1f8d81a3734bdDdEfaC6Ca1596E081e57c3044;
}
}
}
}
}
} else {
if (routeId < 113) {
if (routeId < 105) {
if (routeId < 101) {
if (routeId < 99) {
if (routeId == 97) {
return
0xFF2833123B58aa05d04D7fb99f5FB768B2b435F8;
} else {
return
0xc8f09c1fD751C570233765f71b0e280d74e6e743;
}
} else {
if (routeId == 99) {
return
0x3026DA6Ceca2E5A57A05153653D9212FFAaA49d8;
} else {
return
0xdE68Ee703dE0D11f67B0cE5891cB4a903de6D160;
}
}
} else {
if (routeId < 103) {
if (routeId == 101) {
return
0xE23a7730e81FB4E87A6D0bd9f63EE77ac86C3DA4;
} else {
return
0x8b1DBe04aD76a7d8bC079cACd3ED4D99B897F4a0;
}
} else {
if (routeId == 103) {
return
0xBB227240FA459b69C6889B2b8cb1BE76F118061f;
} else {
return
0xC062b9b3f0dB28BB8afAfcD4d075729344114ffe;
}
}
}
} else {
if (routeId < 109) {
if (routeId < 107) {
if (routeId == 105) {
return
0x553188Aa45f5FDB83EC4Ca485982F8fC082480D1;
} else {
return
0x0109d83D746EaCb6d4014953D9E12d6ca85e330b;
}
} else {
if (routeId == 107) {
return
0x45B1bEd29812F5bf6711074ACD180B2aeB783AD9;
} else {
return
0xdA06eC8c19aea31D77F60299678Cba40E743e1aD;
}
}
} else {
if (routeId < 111) {
if (routeId == 109) {
return
0x3cC5235c97d975a9b4FD4501B3446c981ea3D855;
} else {
return
0xa1827267d6Bd989Ff38580aE3d9deff6Acf19163;
}
} else {
if (routeId == 111) {
return
0x3663CAA0433A3D4171b3581Cf2410702840A735A;
} else {
return
0x7575D0a7614F655BA77C74a72a43bbd4fA6246a3;
}
}
}
}
} else {
if (routeId < 121) {
if (routeId < 117) {
if (routeId < 115) {
if (routeId == 113) {
return
0x2516Defc18bc07089c5dAFf5eafD7B0EF64611E2;
} else {
return
0xfec5FF08E20fbc107a97Af2D38BD0025b84ee233;
}
} else {
if (routeId == 115) {
return
0x0FB5763a87242B25243e23D73f55945fE787523A;
} else {
return
0xe4C00db89678dBf8391f430C578Ca857Dd98aDE1;
}
}
} else {
if (routeId < 119) {
if (routeId == 117) {
return
0x8F2A22061F9F35E64f14523dC1A5f8159e6a21B7;
} else {
return
0x18e4b838ae966917E20E9c9c5Ad359cDD38303bB;
}
} else {
if (routeId == 119) {
return
0x61ACb1d3Dcb3e3429832A164Cc0fC9849fb75A4a;
} else {
return
0x7681e3c8e7A41DCA55C257cc0d1Ae757f5530E65;
}
}
}
} else {
if (routeId < 125) {
if (routeId < 123) {
if (routeId == 121) {
return
0x806a2AB9748C3D1DB976550890E3f528B7E8Faec;
} else {
return
0xBDb8A5DD52C2c239fbC31E9d43B763B0197028FF;
}
} else {
if (routeId == 123) {
return
0x474EC9203706010B9978D6bD0b105D36755e4848;
} else {
return
0x8dfd0D829b303F2239212E591a0F92a32880f36E;
}
}
} else {
if (routeId < 127) {
if (routeId == 125) {
return
0xad4BcE9745860B1adD6F1Bd34a916f050E4c82C2;
} else {
return
0xBC701115b9fe14bC8CC5934cdC92517173e308C4;
}
} else {
if (routeId == 127) {
return
0x0D1918d786Db8546a11aDeD475C98370E06f255E;
} else {
return
0xee44f57cD6936DB55B99163f3Df367B01EdA785a;
}
}
}
}
}
}
}
} else {
if (routeId < 193) {
if (routeId < 161) {
if (routeId < 145) {
if (routeId < 137) {
if (routeId < 133) {
if (routeId < 131) {
if (routeId == 129) {
return
0x63044521fe5a1e488D7eD419cD0e35b7C24F2aa7;
} else {
return
0x410085E73BD85e90d97b84A68C125aDB9F91f85b;
}
} else {
if (routeId == 131) {
return
0x7913fe97E07C7A397Ec274Ab1d4E2622C88EC5D1;
} else {
return
0x977f9fE93c064DCf54157406DaABC3a722e8184C;
}
}
} else {
if (routeId < 135) {
if (routeId == 133) {
return
0xCD2236468722057cFbbABad2db3DEA9c20d5B01B;
} else {
return
0x17c7287A491cf5Ff81E2678cF2BfAE4333F6108c;
}
} else {
if (routeId == 135) {
return
0x354D9a5Dbf96c71B79a265F03B595C6Fdc04dadd;
} else {
return
0xb4e409EB8e775eeFEb0344f9eee884cc7ed21c69;
}
}
}
} else {
if (routeId < 141) {
if (routeId < 139) {
if (routeId == 137) {
return
0xa1a3c4670Ad69D9be4ab2D39D1231FEC2a63b519;
} else {
return
0x4589A22199870729C1be5CD62EE93BeD858113E6;
}
} else {
if (routeId == 139) {
return
0x8E7b864dB26Bd6C798C38d4Ba36EbA0d6602cF11;
} else {
return
0xA2D17C7260a4CB7b9854e89Fc367E80E87872a2d;
}
}
} else {
if (routeId < 143) {
if (routeId == 141) {
return
0xC7F0EDf0A1288627b0432304918A75e9084CBD46;
} else {
return
0xE4B4EF1f9A4aBFEdB371fA7a6143993B15d4df25;
}
} else {
if (routeId == 143) {
return
0xfe3D84A2Ef306FEBb5452441C9BDBb6521666F6A;
} else {
return
0x8A12B6C64121920110aE58F7cd67DfEc21c6a4C3;
}
}
}
}
} else {
if (routeId < 153) {
if (routeId < 149) {
if (routeId < 147) {
if (routeId == 145) {
return
0x76c4d9aFC4717a2BAac4e5f26CccF02351f7a3DA;
} else {
return
0xd4719BA550E397aeAcca1Ad2201c1ba69024FAAf;
}
} else {
if (routeId == 147) {
return
0x9646126Ce025224d1682C227d915a386efc0A1Fb;
} else {
return
0x4DD8Af2E3F2044842f0247920Bc4BABb636915ea;
}
}
} else {
if (routeId < 151) {
if (routeId == 149) {
return
0x8e8a327183Af0cf8C2ece9F0ed547C42A160D409;
} else {
return
0x9D49614CaE1C685C71678CA6d8CDF7584bfd0740;
}
} else {
if (routeId == 151) {
return
0x5a00ef257394cbc31828d48655E3d39e9c11c93d;
} else {
return
0xC9a2751b38d3dDD161A41Ca0135C5C6c09EC1d56;
}
}
}
} else {
if (routeId < 157) {
if (routeId < 155) {
if (routeId == 153) {
return
0x7e1c261640a525C94Ca4f8c25b48CF754DD83590;
} else {
return
0x409Fe24ba6F6BD5aF31C1aAf8059b986A3158233;
}
} else {
if (routeId == 155) {
return
0x704Cf5BFDADc0f55fDBb53B6ed8B582E018A72A2;
} else {
return
0x3982bF65d7d6E77E3b6661cd6F6468c247512737;
}
}
} else {
if (routeId < 159) {
if (routeId == 157) {
return
0x3982b9f26FFD67a13Ee371e2C0a9Da338BA70E7f;
} else {
return
0x6D834AB385900c1f49055D098e90264077FbC4f2;
}
} else {
if (routeId == 159) {
return
0x11FE5F70779A094B7166B391e1Fb73d422eF4e4d;
} else {
return
0xD347e4E47280d21F13B73D89c6d16f867D50DD13;
}
}
}
}
}
} else {
if (routeId < 177) {
if (routeId < 169) {
if (routeId < 165) {
if (routeId < 163) {
if (routeId == 161) {
return
0xb6035eDD53DDA28d8B69b4ae9836E40C80306CD7;
} else {
return
0x54c884e6f5C7CcfeCA990396c520C858c922b6CA;
}
} else {
if (routeId == 163) {
return
0x5eA93E240b083d686558Ed607BC013d88057cE46;
} else {
return
0x4C7131eE812De685cBe4e2cCb033d46ecD46612E;
}
}
} else {
if (routeId < 167) {
if (routeId == 165) {
return
0xc1a5Be9F0c33D8483801D702111068669f81fF91;
} else {
return
0x9E5fAb91455Be5E5b2C05967E73F456c8118B1Fc;
}
} else {
if (routeId == 167) {
return
0x3d9A05927223E0DC2F382831770405885e22F0d8;
} else {
return
0x6303A011fB6063f5B1681cb5a9938EA278dc6128;
}
}
}
} else {
if (routeId < 173) {
if (routeId < 171) {
if (routeId == 169) {
return
0xe9c60795c90C66797e4c8E97511eA07CdAda32bE;
} else {
return
0xD56cC98e69A1e13815818b466a8aA6163d84234A;
}
} else {
if (routeId == 171) {
return
0x47EbB9D36a6e40895316cD894E4860D774E2c531;
} else {
return
0xA5EB293629410065d14a7B1663A67829b0618292;
}
}
} else {
if (routeId < 175) {
if (routeId == 173) {
return
0x1b3B4C8146F939cE00899db8B3ddeF0062b7E023;
} else {
return
0x257Bbc11653625EbfB6A8587eF4f4FBe49828EB3;
}
} else {
if (routeId == 175) {
return
0x44cc979C01b5bB1eAC21301E73C37200dFD06F59;
} else {
return
0x2972fDF43352225D82754C0174Ff853819D1ef2A;
}
}
}
}
} else {
if (routeId < 185) {
if (routeId < 181) {
if (routeId < 179) {
if (routeId == 177) {
return
0x3e54144f032648A04D62d79f7B4b93FF3aC2333b;
} else {
return
0x444016102dB8adbE73C3B6703a1ea7F2f75A510D;
}
} else {
if (routeId == 179) {
return
0xac079143f98a6eb744Fde34541ebF243DF5B5dED;
} else {
return
0xAe9010767Fb112d29d35CEdfba2b372Ad7A308d3;
}
}
} else {
if (routeId < 183) {
if (routeId == 181) {
return
0xfE0BCcF9cCC2265D5fB3450743f17DfE57aE1e56;
} else {
return
0x04ED8C0545716119437a45386B1d691C63234C7D;
}
} else {
if (routeId == 183) {
return
0x636c14013e531A286Bc4C848da34585f0bB73d59;
} else {
return
0x2Fa67fc7ECC5cAA01C653d3BFeA98ecc5db9C42A;
}
}
}
} else {
if (routeId < 189) {
if (routeId < 187) {
if (routeId == 185) {
return
0x23e9a0FC180818aA872D2079a985217017E97bd9;
} else {
return
0x79A95c3Ef81b3ae64ee03A9D5f73e570495F164E;
}
} else {
if (routeId == 187) {
return
0xa7EA0E88F04a84ba0ad1E396cb07Fa3fDAD7dF6D;
} else {
return
0xd23cA1278a2B01a3C0Ca1a00d104b11c1Ebe6f42;
}
}
} else {
if (routeId < 191) {
if (routeId == 189) {
return
0x707bc4a9FA2E349AED5df4e9f5440C15aA9D14Bd;
} else {
return
0x7E290F2dd539Ac6CE58d8B4C2B944931a1fD3612;
}
} else {
if (routeId == 191) {
return
0x707AA5503088Ce06Ba450B6470A506122eA5c8eF;
} else {
return
0xFbB3f7BF680deeb149f4E7BC30eA3DDfa68F3C3f;
}
}
}
}
}
}
} else {
if (routeId < 225) {
if (routeId < 209) {
if (routeId < 201) {
if (routeId < 197) {
if (routeId < 195) {
if (routeId == 193) {
return
0xDE74aD8cCC3dbF14992f49Cf24f36855912f4934;
} else {
return
0x409BA83df7777F070b2B50a10a41DE2468d2a3B3;
}
} else {
if (routeId == 195) {
return
0x5CB7Be90A5DD7CfDa54e87626e254FE8C18255B4;
} else {
return
0x0A684fE12BC64fb72B59d0771a566F49BC090356;
}
}
} else {
if (routeId < 199) {
if (routeId == 197) {
return
0xDf30048d91F8FA2bCfC54952B92bFA8e161D3360;
} else {
return
0x050825Fff032a547C47061CF0696FDB0f65AEa5D;
}
} else {
if (routeId == 199) {
return
0xd55e671dAC1f03d366d8535073ada5DB2Aab1Ea2;
} else {
return
0x9470C704A9616c8Cd41c595Fcd2181B6fe2183C2;
}
}
}
} else {
if (routeId < 205) {
if (routeId < 203) {
if (routeId == 201) {
return
0x2D9ffD275181F5865d5e11CbB4ced1521C4dF9f1;
} else {
return
0x816d28Dec10ec95DF5334f884dE85cA6215918d8;
}
} else {
if (routeId == 203) {
return
0xd1f87267c4A43835E666dd69Df077e578A3b6299;
} else {
return
0x39E89Bde9DACbe5468C025dE371FbDa12bDeBAB1;
}
}
} else {
if (routeId < 207) {
if (routeId == 205) {
return
0x7b40A3207956ecad6686E61EfcaC48912FcD0658;
} else {
return
0x090cF10D793B1Efba9c7D76115878814B663859A;
}
} else {
if (routeId == 207) {
return
0x312A59c06E41327878F2063eD0e9c282C1DA3AfC;
} else {
return
0x4F1188f46236DD6B5de11Ebf2a9fF08716E7DeB6;
}
}
}
}
} else {
if (routeId < 217) {
if (routeId < 213) {
if (routeId < 211) {
if (routeId == 209) {
return
0x0A6F9a3f4fA49909bBfb4339cbE12B42F53BbBeD;
} else {
return
0x01d13d7aCaCbB955B81935c80ffF31e14BdFa71f;
}
} else {
if (routeId == 211) {
return
0x691a14Fa6C7360422EC56dF5876f84d4eDD7f00A;
} else {
return
0x97Aad18d886d181a9c726B3B6aE15a0A69F5aF73;
}
}
} else {
if (routeId < 215) {
if (routeId == 213) {
return
0x2917241371D2099049Fa29432DC46735baEC33b4;
} else {
return
0x5F20F20F7890c2e383E29D4147C9695A371165f5;
}
} else {
if (routeId == 215) {
return
0xeC0a60e639958335662C5219A320cCEbb56C6077;
} else {
return
0x96d63CF5062975C09845d17ec672E10255866053;
}
}
}
} else {
if (routeId < 221) {
if (routeId < 219) {
if (routeId == 217) {
return
0xFF57429e57D383939CAB50f09ABBfB63C0e6c9AD;
} else {
return
0x18E393A7c8578fb1e235C242076E50013cDdD0d7;
}
} else {
if (routeId == 219) {
return
0xE7E5238AF5d61f52E9B4ACC025F713d1C0216507;
} else {
return
0x428401D4d0F25A2EE1DA4d5366cB96Ded425D9bD;
}
}
} else {
if (routeId < 223) {
if (routeId == 221) {
return
0x42E5733551ff1Ee5B48Aa9fc2B61Af9b58C812E6;
} else {
return
0x64Df9c7A0551B056d860Bc2419Ca4c1EF75320bE;
}
} else {
if (routeId == 223) {
return
0x46006925506145611bBf0263243D8627dAf26B0F;
} else {
return
0x8D64BE884314662804eAaB884531f5C50F4d500c;
}
}
}
}
}
} else {
if (routeId < 241) {
if (routeId < 233) {
if (routeId < 229) {
if (routeId < 227) {
if (routeId == 225) {
return
0x157a62D92D07B5ce221A5429645a03bBaCE85373;
} else {
return
0xaF037D33e1F1F2F87309B425fe8a9d895Ef3722B;
}
} else {
if (routeId == 227) {
return
0x921D1154E494A2f7218a37ad7B17701f94b4B40e;
} else {
return
0xF282b4555186d8Dea51B8b3F947E1E0568d09bc4;
}
}
} else {
if (routeId < 231) {
if (routeId == 229) {
return
0xa794E2E1869765a4600b3DFd8a4ebcF16350f6B6;
} else {
return
0xFEFb048e20c5652F7940A49B1980E0125Ec4D358;
}
} else {
if (routeId == 231) {
return
0x220104b641971e9b25612a8F001bf48AbB23f1cF;
} else {
return
0xcB9D373Bb54A501B35dd3be5bF4Ba43cA31F7035;
}
}
}
} else {
if (routeId < 237) {
if (routeId < 235) {
if (routeId == 233) {
return
0x37D627F56e3FF36aC316372109ea82E03ac97DAc;
} else {
return
0x4E81355FfB4A271B4EA59ff78da2b61c7833161f;
}
} else {
if (routeId == 235) {
return
0xADd8D65cAF6Cc9ad73127B49E16eA7ac29d91e87;
} else {
return
0x630F9b95626487dfEAe3C97A44DB6C59cF35d996;
}
}
} else {
if (routeId < 239) {
if (routeId == 237) {
return
0x78CE2BC8238B679680A67FCB98C5A60E4ec17b2D;
} else {
return
0xA38D776028eD1310b9A6b086f67F788201762E21;
}
} else {
if (routeId == 239) {
return
0x7Bb5178827B76B86753Ed62a0d662c72cEcb1bD3;
} else {
return
0x4faC26f61C76eC5c3D43b43eDfAFF0736Ae0e3da;
}
}
}
}
} else {
if (routeId < 249) {
if (routeId < 245) {
if (routeId < 243) {
if (routeId == 241) {
return
0x791Bb49bfFA7129D6889FDB27744422Ac4571A85;
} else {
return
0x26766fFEbb5fa564777913A6f101dF019AB32afa;
}
} else {
if (routeId == 243) {
return
0x05e98E5e95b4ECBbbAf3258c3999Cc81ed8048Be;
} else {
return
0xC5c4621e52f1D6A1825A5ed4F95855401a3D9C6b;
}
}
} else {
if (routeId < 247) {
if (routeId == 245) {
return
0xfcb15f909BA7FC7Ea083503Fb4c1020203c107EB;
} else {
return
0xbD27603279d969c74f2486ad14E71080829DFd38;
}
} else {
if (routeId == 247) {
return
0xff2f756BcEcC1A55BFc09a30cc5F64720458cFCB;
} else {
return
0x3bfB968FEbC12F4e8420B2d016EfcE1E615f7246;
}
}
}
} else {
if (routeId < 253) {
if (routeId < 251) {
if (routeId == 249) {
return
0x982EE9Ffe23051A2ec945ed676D864fa8345222b;
} else {
return
0xe101899100785E74767d454FFF0131277BaD48d9;
}
} else {
if (routeId == 251) {
return
0x4F730C0c6b3B5B7d06ca511379f4Aa5BfB2E9525;
} else {
return
0x5499c36b365795e4e0Ef671aF6C2ce26D7c78265;
}
}
} else {
if (routeId < 255) {
if (routeId == 253) {
return
0x8AF51F7237Fc8fB2fc3E700488a94a0aC6Ad8b5a;
} else {
return
0xda8716df61213c0b143F2849785FB85928084857;
}
} else {
if (routeId == 255) {
return
0xF040Cf9b1ebD11Bf28e04e80740DF3DDe717e4f5;
} else {
return
0xB87ba32f759D14023C7520366B844dF7f0F036C2;
}
}
}
}
}
}
}
}
} else {
if (routeId < 321) {
if (routeId < 289) {
if (routeId < 273) {
if (routeId < 265) {
if (routeId < 261) {
if (routeId < 259) {
if (routeId == 257) {
return
0x0Edde681b8478F0c3194f468EdD2dB5e75c65CDD;
} else {
return
0x59C70900Fca06eE2aCE1BDd5A8D0Af0cc3BBA720;
}
} else {
if (routeId == 259) {
return
0x8041F0f180D17dD07087199632c45E17AeB0BAd5;
} else {
return
0x4fB4727064BA595995DD516b63b5921Df9B93aC6;
}
}
} else {
if (routeId < 263) {
if (routeId == 261) {
return
0x86e98b594565857eD098864F560915C0dAfd6Ea1;
} else {
return
0x70f8818E8B698EFfeCd86A513a4c87c0c380Bef6;
}
} else {
if (routeId == 263) {
return
0x78Ed227c8A897A21Da2875a752142dd80d865158;
} else {
return
0xd02A30BB5C3a8C51d2751A029a6fcfDE2Af9fbc6;
}
}
}
} else {
if (routeId < 269) {
if (routeId < 267) {
if (routeId == 265) {
return
0x0F00d5c5acb24e975e2a56730609f7F40aa763b8;
} else {
return
0xC3e2091edc2D3D9D98ba09269138b617B536834A;
}
} else {
if (routeId == 267) {
return
0xa6FbaF7F30867C9633908998ea8C3da28920E75C;
} else {
return
0xE6dDdcD41E2bBe8122AE32Ac29B8fbAB79CD21d9;
}
}
} else {
if (routeId < 271) {
if (routeId == 269) {
return
0x537aa8c1Ef6a8Eaf039dd6e1Eb67694a48195cE4;
} else {
return
0x96ABAC485fd2D0B03CF4a10df8BD58b8dED28300;
}
} else {
if (routeId == 271) {
return
0xda8e7D46d04Bd4F62705Cd80355BDB6d441DafFD;
} else {
return
0xbE50018E7a5c67E2e5f5414393e971CC96F293f2;
}
}
}
}
} else {
if (routeId < 281) {
if (routeId < 277) {
if (routeId < 275) {
if (routeId == 273) {
return
0xa1b3907D6CB542a4cbe2eE441EfFAA909FAb62C3;
} else {
return
0x6d08ee8511C0237a515013aC389e7B3968Cb1753;
}
} else {
if (routeId == 275) {
return
0x22faa5B5Fe43eAdbB52745e35a5cdA8bD5F96bbA;
} else {
return
0x7a673eB74D79e4868D689E7852abB5f93Ec2fD4b;
}
}
} else {
if (routeId < 279) {
if (routeId == 277) {
return
0x0b8531F8AFD4190b76F3e10deCaDb84c98b4d419;
} else {
return
0x78eABC743A93583DeE403D6b84795490e652216B;
}
} else {
if (routeId == 279) {
return
0x3A95D907b2a7a8604B59BccA08585F58Afe0Aa64;
} else {
return
0xf4271f0C8c9Af0F06A80b8832fa820ccE64FAda8;
}
}
}
} else {
if (routeId < 285) {
if (routeId < 283) {
if (routeId == 281) {
return
0x74b2DF841245C3748c0d31542e1335659a25C33b;
} else {
return
0xdFC99Fd0Ad7D16f30f295a5EEFcE029E04d0fa65;
}
} else {
if (routeId == 283) {
return
0xE992416b6aC1144eD8148a9632973257839027F6;
} else {
return
0x54ce55ba954E981BB1fd9399054B35Ce1f2C0816;
}
}
} else {
if (routeId < 287) {
if (routeId == 285) {
return
0xD4AB52f9e7E5B315Bd7471920baD04F405Ab1c38;
} else {
return
0x3670C990994d12837e95eE127fE2f06FD3E2104B;
}
} else {
if (routeId == 287) {
return
0xDcf190B09C47E4f551E30BBb79969c3FdEA1e992;
} else {
return
0xa65057B967B59677237e57Ab815B209744b9bc40;
}
}
}
}
}
} else {
if (routeId < 305) {
if (routeId < 297) {
if (routeId < 293) {
if (routeId < 291) {
if (routeId == 289) {
return
0x6Efc86B40573e4C7F28659B13327D55ae955C483;
} else {
return
0x06BcC25CF8e0E72316F53631b3aA7134E9f73Ae0;
}
} else {
if (routeId == 291) {
return
0x710b6414E1D53882b1FCD3A168aD5Ccd435fc6D0;
} else {
return
0x5Ebb2C3d78c4e9818074559e7BaE7FCc99781DC1;
}
}
} else {
if (routeId < 295) {
if (routeId == 293) {
return
0xAf0a409c3AEe0bD08015cfb29D89E90b6e89A88F;
} else {
return
0x522559d8b99773C693B80cE06DF559036295Ce44;
}
} else {
if (routeId == 295) {
return
0xB65290A5Bae838aaa7825c9ECEC68041841a1B64;
} else {
return
0x801b8F2068edd5Bcb659E6BDa0c425909043C420;
}
}
}
} else {
if (routeId < 301) {
if (routeId < 299) {
if (routeId == 297) {
return
0x29b5F00515d093627E0B7bd0b5c8E84F6b4cDb87;
} else {
return
0x652839Ae74683cbF9f1293F1019D938F87464D3E;
}
} else {
if (routeId == 299) {
return
0x5Bc95dCebDDE9B79F2b6DC76121BC7936eF8D666;
} else {
return
0x90db359CEA62E53051158Ab5F99811C0a07Fe686;
}
}
} else {
if (routeId < 303) {
if (routeId == 301) {
return
0x2c3625EedadbDcDbB5330eb0d17b3C39ff269807;
} else {
return
0xC3f0324471b5c9d415acD625b8d8694a4e48e001;
}
} else {
if (routeId == 303) {
return
0x8C60e7E05fa0FfB6F720233736f245134685799d;
} else {
return
0x98fAF2c09aa4EBb995ad0B56152993E7291a500e;
}
}
}
}
} else {
if (routeId < 313) {
if (routeId < 309) {
if (routeId < 307) {
if (routeId == 305) {
return
0x802c1063a861414dFAEc16bacb81429FC0d40D6e;
} else {
return
0x11C4AeFCC0dC156f64195f6513CB1Fb3Be0Ae056;
}
} else {
if (routeId == 307) {
return
0xEff1F3258214E31B6B4F640b4389d55715C3Be2B;
} else {
return
0x47e379Abe8DDFEA4289aBa01235EFF7E93758fd7;
}
}
} else {
if (routeId < 311) {
if (routeId == 309) {
return
0x3CC26384c3eA31dDc8D9789e8872CeA6F20cD3ff;
} else {
return
0xEdd9EFa6c69108FAA4611097d643E20Ba0Ed1634;
}
} else {
if (routeId == 311) {
return
0xCb93525CA5f3D371F74F3D112bC19526740717B8;
} else {
return
0x7071E0124EB4438137e60dF1b8DD8Af1BfB362cF;
}
}
}
} else {
if (routeId < 317) {
if (routeId < 315) {
if (routeId == 313) {
return
0x4691096EB0b78C8F4b4A8091E5B66b18e1835c10;
} else {
return
0x8d953c9b2d1C2137CF95992079f3A77fCd793272;
}
} else {
if (routeId == 315) {
return
0xbdCc2A3Bf6e3Ba49ff86595e6b2b8D70d8368c92;
} else {
return
0x95E6948aB38c61b2D294E8Bd896BCc4cCC0713cf;
}
}
} else {
if (routeId < 319) {
if (routeId == 317) {
return
0x607b27C881fFEE4Cb95B1c5862FaE7224ccd0b4A;
} else {
return
0x09D28aFA166e566A2Ee1cB834ea8e78C7E627eD2;
}
} else {
if (routeId == 319) {
return
0x9c01449b38bDF0B263818401044Fb1401B29fDfA;
} else {
return
0x1F7723599bbB658c051F8A39bE2688388d22ceD6;
}
}
}
}
}
}
} else {
if (routeId < 353) {
if (routeId < 337) {
if (routeId < 329) {
if (routeId < 325) {
if (routeId < 323) {
if (routeId == 321) {
return
0x52B71603f7b8A5d15B4482e965a0619aa3210194;
} else {
return
0x01c0f072CB210406653752FecFA70B42dA9173a2;
}
} else {
if (routeId == 323) {
return
0x3021142f021E943e57fc1886cAF58D06147D09A6;
} else {
return
0xe6f2AF38e76AB09Db59225d97d3E770942D3D842;
}
}
} else {
if (routeId < 327) {
if (routeId == 325) {
return
0x06a25554e5135F08b9e2eD1DEC1fc3CEd52e0B48;
} else {
return
0x71d75e670EE3511C8290C705E0620126B710BF8D;
}
} else {
if (routeId == 327) {
return
0x8b9cE142b80FeA7c932952EC533694b1DF9B3c54;
} else {
return
0xd7Be24f32f39231116B3fDc483C2A12E1521f73B;
}
}
}
} else {
if (routeId < 333) {
if (routeId < 331) {
if (routeId == 329) {
return
0xb40cafBC4797d4Ff64087E087F6D2e661f954CbE;
} else {
return
0xBdDCe7771EfEe81893e838f62204A4c76D72757e;
}
} else {
if (routeId == 331) {
return
0x5d3D299EA7Fd4F39AcDb336E26631Dfee41F9287;
} else {
return
0x6BfEE09E1Fc0684e0826A9A0dC1352a14B136FAC;
}
}
} else {
if (routeId < 335) {
if (routeId == 333) {
return
0xd0001bB8E2Cb661436093f96458a4358B5156E3c;
} else {
return
0x1867c6485CfD1eD448988368A22bfB17a7747293;
}
} else {
if (routeId == 335) {
return
0x8997EF9F95dF24aB67703AB6C262aABfeEBE33bD;
} else {
return
0x1e39E9E601922deD91BCFc8F78836302133465e2;
}
}
}
}
} else {
if (routeId < 345) {
if (routeId < 341) {
if (routeId < 339) {
if (routeId == 337) {
return
0x8A8ec6CeacFf502a782216774E5AF3421562C6ff;
} else {
return
0x3B8FC561df5415c8DC01e97Ee6E38435A8F9C40A;
}
} else {
if (routeId == 339) {
return
0xD5d5f5B37E67c43ceA663aEDADFFc3a93a2065B0;
} else {
return
0xCC8F55EC43B4f25013CE1946FBB740c43Be5B96D;
}
}
} else {
if (routeId < 343) {
if (routeId == 341) {
return
0x18f586E816eEeDbb57B8011239150367561B58Fb;
} else {
return
0xd0CD802B19c1a52501cb2f07d656e3Cd7B0Ce124;
}
} else {
if (routeId == 343) {
return
0xe0AeD899b39C6e4f2d83e4913a1e9e0cf6368abE;
} else {
return
0x0606e1b6c0f1A398C38825DCcc4678a7Cbc2737c;
}
}
}
} else {
if (routeId < 349) {
if (routeId < 347) {
if (routeId == 345) {
return
0x2d188e85b27d18EF80f16686EA1593ABF7Ed2A63;
} else {
return
0x64412292fA4A135a3300E24366E99ff59Db2eAc1;
}
} else {
if (routeId == 347) {
return
0x38b74c173f3733E8b90aAEf0e98B89791266149F;
} else {
return
0x36DAA49A79aaEF4E7a217A11530D3cCD84414124;
}
}
} else {
if (routeId < 351) {
if (routeId == 349) {
return
0x10f088FE2C88F90270E4449c46c8B1b232511d58;
} else {
return
0x4FeDbd25B58586838ABD17D10272697dF1dC3087;
}
} else {
if (routeId == 351) {
return
0x685278209248CB058E5cEe93e37f274A80Faf6eb;
} else {
return
0xDd9F8F1eeC3955f78168e2Fb2d1e808fa8A8f15b;
}
}
}
}
}
} else {
if (routeId < 369) {
if (routeId < 361) {
if (routeId < 357) {
if (routeId < 355) {
if (routeId == 353) {
return
0x7392aEeFD5825aaC28817031dEEBbFaAA20983D9;
} else {
return
0x0Cc182555E00767D6FB8AD161A10d0C04C476d91;
}
} else {
if (routeId == 355) {
return
0x90E52837d56715c79FD592E8D58bFD20365798b2;
} else {
return
0x6F4451DE14049B6770ad5BF4013118529e68A40C;
}
}
} else {
if (routeId < 359) {
if (routeId == 357) {
return
0x89B97ef2aFAb9ed9c7f0FDb095d02E6840b52d9c;
} else {
return
0x92A5cC5C42d94d3e23aeB1214fFf43Db2B97759E;
}
} else {
if (routeId == 359) {
return
0x63ddc52F135A1dcBA831EAaC11C63849F018b739;
} else {
return
0x692A691533B571C2c54C1D7F8043A204b3d8120E;
}
}
}
} else {
if (routeId < 365) {
if (routeId < 363) {
if (routeId == 361) {
return
0x97c7492CF083969F61C6f302d45c8270391b921c;
} else {
return
0xDeFD2B8643553dAd19548eB14fd94A57F4B9e543;
}
} else {
if (routeId == 363) {
return
0x30645C04205cA3f670B67b02F971B088930ACB8C;
} else {
return
0xA6f80ed2d607Cd67aEB4109B64A0BEcc4D7d03CF;
}
}
} else {
if (routeId < 367) {
if (routeId == 365) {
return
0xBbbbC6c276eB3F7E674f2D39301509236001c42f;
} else {
return
0xC20E77d349FB40CE88eB01824e2873ad9f681f3C;
}
} else {
if (routeId == 367) {
return
0x5fCfD9a962De19294467C358C1FA55082285960b;
} else {
return
0x4D87BD6a0E4E5cc6332923cb3E85fC71b287F58A;
}
}
}
}
} else {
if (routeId < 377) {
if (routeId < 373) {
if (routeId < 371) {
if (routeId == 369) {
return
0x3AA5B757cd6Dde98214E56D57Dde7fcF0F7aB04E;
} else {
return
0xe28eFCE7192e11a2297f44059113C1fD6967b2d4;
}
} else {
if (routeId == 371) {
return
0x3251cAE10a1Cf246e0808D76ACC26F7B5edA0eE5;
} else {
return
0xbA2091cc9357Cf4c4F25D64F30d1b4Ba3A5a174B;
}
}
} else {
if (routeId < 375) {
if (routeId == 373) {
return
0x49c8e1Da9693692096F63C82D11b52d738566d55;
} else {
return
0xA0731615aB5FFF451031E9551367A4F7dB27b39c;
}
} else {
if (routeId == 375) {
return
0xFb214541888671AE1403CecC1D59763a12fc1609;
} else {
return
0x1D6bCB17642E2336405df73dF22F07688cAec020;
}
}
}
} else {
if (routeId < 381) {
if (routeId < 379) {
if (routeId == 377) {
return
0xfC9c0C7bfe187120fF7f4E21446161794A617a9e;
} else {
return
0xBa5bF37678EeE2dAB17AEf9D898153258252250E;
}
} else {
if (routeId == 379) {
return
0x7c55690bd2C9961576A32c02f8EB29ed36415Ec7;
} else {
return
0xcA40073E868E8Bc611aEc8Fe741D17E68Fe422f6;
}
}
} else {
if (routeId < 383) {
if (routeId == 381) {
return
0x31641bAFb87E9A58f78835050a7BE56921986339;
} else {
return
0xA54766424f6dA74b45EbCc5Bf0Bd1D74D2CCcaAB;
}
} else {
if (routeId == 383) {
return
0xc7bBa57F8C179EDDBaa62117ddA360e28f3F8252;
} else {
return
0x5e663ED97ea77d393B8858C90d0683bF180E0ffd;
}
}
}
}
}
}
}
}
}
if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();
return routes[routeId];
}
/// @notice fallback function to handle swap, bridge execution
/// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction
fallback() external payable {
address routeAddress = addressAt(uint32(msg.sig));
bytes memory result;
assembly {
// copy function selector and any arguments
calldatacopy(0, 4, sub(calldatasize(), 4))
// execute function call using the facet
result := delegatecall(
gas(),
routeAddress,
0,
sub(calldatasize(), 4),
0,
0
)
// get any return value
returndatacopy(0, 0, returndatasize())
// return any return value or error back to the caller
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
bytes32 constant ACROSS = keccak256("Across");
bytes32 constant ANYSWAP = keccak256("Anyswap");
bytes32 constant CBRIDGE = keccak256("CBridge");
bytes32 constant HOP = keccak256("Hop");
bytes32 constant HYPHEN = keccak256("Hyphen");
bytes32 constant NATIVE_OPTIMISM = keccak256("NativeOptimism");
bytes32 constant NATIVE_ARBITRUM = keccak256("NativeArbitrum");
bytes32 constant NATIVE_POLYGON = keccak256("NativePolygon");
bytes32 constant REFUEL = keccak256("Refuel");
bytes32 constant STARGATE = keccak256("Stargate");
bytes32 constant ONEINCH = keccak256("OneInch");
bytes32 constant ZEROX = keccak256("Zerox");
bytes32 constant RAINBOW = keccak256("Rainbow");
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "../SwapImplBase.sol";
import {SwapFailed} from "../../errors/SocketErrors.sol";
import {ONEINCH} from "../../static/RouteIdentifiers.sol";
/**
* @title OneInch-Swap-Route Implementation
* @notice Route implementation with functions to swap tokens via OneInch-Swap
* Called via SocketGateway if the routeId in the request maps to the routeId of OneInchImplementation
* @author Socket dot tech.
*/
contract OneInchImpl is SwapImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable OneInchIdentifier = ONEINCH;
/// @notice address of OneInchAggregator to swap the tokens on Chain
address public immutable ONEINCH_AGGREGATOR;
/// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase
/// @dev ensure _oneinchAggregator are set properly for the chainId in which the contract is being deployed
constructor(
address _oneinchAggregator,
address _socketGateway,
address _socketDeployFactory
) SwapImplBase(_socketGateway, _socketDeployFactory) {
ONEINCH_AGGREGATOR = _oneinchAggregator;
}
/**
* @notice function to swap tokens on the chain and transfer to receiver address
* via OneInch-Middleware-Aggregator
* @param fromToken token to be swapped
* @param toToken token to which fromToken has to be swapped
* @param amount amount of fromToken being swapped
* @param receiverAddress address of toToken recipient
* @param swapExtraData encoded value of properties in the swapData Struct
* @return swapped amount (in toToken Address)
*/
function performAction(
address fromToken,
address toToken,
uint256 amount,
address receiverAddress,
bytes calldata swapExtraData
) external payable override returns (uint256) {
uint256 returnAmount;
if (fromToken != NATIVE_TOKEN_ADDRESS) {
ERC20 token = ERC20(fromToken);
token.safeTransferFrom(msg.sender, socketGateway, amount);
token.safeApprove(ONEINCH_AGGREGATOR, amount);
{
// additional data is generated in off-chain using the OneInch API which takes in
// fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate
(bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(
swapExtraData
);
token.safeApprove(ONEINCH_AGGREGATOR, 0);
if (!success) {
revert SwapFailed();
}
returnAmount = abi.decode(result, (uint256));
}
} else {
// additional data is generated in off-chain using the OneInch API which takes in
// fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate
(bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{
value: amount
}(swapExtraData);
if (!success) {
revert SwapFailed();
}
returnAmount = abi.decode(result, (uint256));
}
emit SocketSwapTokens(
fromToken,
toToken,
returnAmount,
amount,
OneInchIdentifier,
receiverAddress
);
return returnAmount;
}
/**
* @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient
* via OneInch-Middleware-Aggregator
* @param fromToken token to be swapped
* @param toToken token to which fromToken has to be swapped
* @param amount amount of fromToken being swapped
* @param swapExtraData encoded value of properties in the swapData Struct
* @return swapped amount (in toToken Address)
*/
function performActionWithIn(
address fromToken,
address toToken,
uint256 amount,
bytes calldata swapExtraData
) external payable override returns (uint256, address) {
uint256 returnAmount;
if (fromToken != NATIVE_TOKEN_ADDRESS) {
ERC20 token = ERC20(fromToken);
token.safeTransferFrom(msg.sender, socketGateway, amount);
token.safeApprove(ONEINCH_AGGREGATOR, amount);
{
// additional data is generated in off-chain using the OneInch API which takes in
// fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate
(bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(
swapExtraData
);
token.safeApprove(ONEINCH_AGGREGATOR, 0);
if (!success) {
revert SwapFailed();
}
returnAmount = abi.decode(result, (uint256));
}
} else {
// additional data is generated in off-chain using the OneInch API which takes in
// fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate
(bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{
value: amount
}(swapExtraData);
if (!success) {
revert SwapFailed();
}
returnAmount = abi.decode(result, (uint256));
}
emit SocketSwapTokens(
fromToken,
toToken,
returnAmount,
amount,
OneInchIdentifier,
socketGateway
);
return (returnAmount, toToken);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "../SwapImplBase.sol";
import {Address0Provided, SwapFailed} from "../../errors/SocketErrors.sol";
import {RAINBOW} from "../../static/RouteIdentifiers.sol";
/**
* @title Rainbow-Swap-Route Implementation
* @notice Route implementation with functions to swap tokens via Rainbow-Swap
* Called via SocketGateway if the routeId in the request maps to the routeId of RainbowImplementation
* @author Socket dot tech.
*/
contract RainbowSwapImpl is SwapImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable RainbowIdentifier = RAINBOW;
/// @notice unique name to identify the router, used to emit event upon successful bridging
bytes32 public immutable NAME = keccak256("Rainbow-Router");
/// @notice address of rainbow-swap-aggregator to swap the tokens on Chain
address payable public immutable rainbowSwapAggregator;
/// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase
/// @notice rainbow swap aggregator contract is payable to allow ethereum swaps
/// @dev ensure _rainbowSwapAggregator are set properly for the chainId in which the contract is being deployed
constructor(
address _rainbowSwapAggregator,
address _socketGateway,
address _socketDeployFactory
) SwapImplBase(_socketGateway, _socketDeployFactory) {
rainbowSwapAggregator = payable(_rainbowSwapAggregator);
}
receive() external payable {}
fallback() external payable {}
/**
* @notice function to swap tokens on the chain and transfer to receiver address
* @notice This method is payable because the caller is doing token transfer and swap operation
* @param fromToken address of token being Swapped
* @param toToken address of token that recipient will receive after swap
* @param amount amount of fromToken being swapped
* @param receiverAddress recipient-address
* @param swapExtraData additional Data to perform Swap via Rainbow-Aggregator
* @return swapped amount (in toToken Address)
*/
function performAction(
address fromToken,
address toToken,
uint256 amount,
address receiverAddress,
bytes calldata swapExtraData
) external payable override returns (uint256) {
if (fromToken == address(0)) {
revert Address0Provided();
}
bytes memory swapCallData = abi.decode(swapExtraData, (bytes));
uint256 _initialBalanceTokenOut;
uint256 _finalBalanceTokenOut;
ERC20 toTokenERC20 = ERC20(toToken);
if (toToken != NATIVE_TOKEN_ADDRESS) {
_initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);
} else {
_initialBalanceTokenOut = address(this).balance;
}
if (fromToken != NATIVE_TOKEN_ADDRESS) {
ERC20 token = ERC20(fromToken);
token.safeTransferFrom(msg.sender, socketGateway, amount);
token.safeApprove(rainbowSwapAggregator, amount);
// solhint-disable-next-line
(bool success, ) = rainbowSwapAggregator.call(swapCallData);
if (!success) {
revert SwapFailed();
}
token.safeApprove(rainbowSwapAggregator, 0);
} else {
(bool success, ) = rainbowSwapAggregator.call{value: amount}(
swapCallData
);
if (!success) {
revert SwapFailed();
}
}
if (toToken != NATIVE_TOKEN_ADDRESS) {
_finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);
} else {
_finalBalanceTokenOut = address(this).balance;
}
uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;
if (toToken == NATIVE_TOKEN_ADDRESS) {
payable(receiverAddress).transfer(returnAmount);
} else {
toTokenERC20.transfer(receiverAddress, returnAmount);
}
emit SocketSwapTokens(
fromToken,
toToken,
returnAmount,
amount,
RainbowIdentifier,
receiverAddress
);
return returnAmount;
}
/**
* @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient
* @param fromToken token to be swapped
* @param toToken token to which fromToken has to be swapped
* @param amount amount of fromToken being swapped
* @param swapExtraData encoded value of properties in the swapData Struct
* @return swapped amount (in toToken Address)
*/
function performActionWithIn(
address fromToken,
address toToken,
uint256 amount,
bytes calldata swapExtraData
) external payable override returns (uint256, address) {
if (fromToken == address(0)) {
revert Address0Provided();
}
bytes memory swapCallData = abi.decode(swapExtraData, (bytes));
uint256 _initialBalanceTokenOut;
uint256 _finalBalanceTokenOut;
ERC20 toTokenERC20 = ERC20(toToken);
if (toToken != NATIVE_TOKEN_ADDRESS) {
_initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);
} else {
_initialBalanceTokenOut = address(this).balance;
}
if (fromToken != NATIVE_TOKEN_ADDRESS) {
ERC20 token = ERC20(fromToken);
token.safeTransferFrom(msg.sender, socketGateway, amount);
token.safeApprove(rainbowSwapAggregator, amount);
// solhint-disable-next-line
(bool success, ) = rainbowSwapAggregator.call(swapCallData);
if (!success) {
revert SwapFailed();
}
token.safeApprove(rainbowSwapAggregator, 0);
} else {
(bool success, ) = rainbowSwapAggregator.call{value: amount}(
swapCallData
);
if (!success) {
revert SwapFailed();
}
}
if (toToken != NATIVE_TOKEN_ADDRESS) {
_finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);
} else {
_finalBalanceTokenOut = address(this).balance;
}
uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;
emit SocketSwapTokens(
fromToken,
toToken,
returnAmount,
amount,
RainbowIdentifier,
socketGateway
);
return (returnAmount, toToken);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import {ISocketGateway} from "../interfaces/ISocketGateway.sol";
import {OnlySocketGatewayOwner, OnlySocketDeployer} from "../errors/SocketErrors.sol";
/**
* @title Abstract Implementation Contract.
* @notice All Swap Implementation will follow this interface.
* @author Socket dot tech.
*/
abstract contract SwapImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
/// @notice Address used to identify if it is a native token transfer or not
address public immutable NATIVE_TOKEN_ADDRESS =
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
/// @notice immutable variable to store the socketGateway address
address public immutable socketGateway;
/// @notice immutable variable to store the socketGateway address
address public immutable socketDeployFactory;
/// @notice FunctionSelector used to delegatecall to the performAction function of swap-router-implementation
bytes4 public immutable SWAP_FUNCTION_SELECTOR =
bytes4(
keccak256("performAction(address,address,uint256,address,bytes)")
);
/// @notice FunctionSelector used to delegatecall to the performActionWithIn function of swap-router-implementation
bytes4 public immutable SWAP_WITHIN_FUNCTION_SELECTOR =
bytes4(keccak256("performActionWithIn(address,address,uint256,bytes)"));
/****************************************
* EVENTS *
****************************************/
event SocketSwapTokens(
address fromToken,
address toToken,
uint256 buyAmount,
uint256 sellAmount,
bytes32 routeName,
address receiver
);
/**
* @notice Construct the base for all SwapImplementations.
* @param _socketGateway Socketgateway address, an immutable variable to set.
*/
constructor(address _socketGateway, address _socketDeployFactory) {
socketGateway = _socketGateway;
socketDeployFactory = _socketDeployFactory;
}
/****************************************
* MODIFIERS *
****************************************/
/// @notice Implementing contract needs to make use of the modifier where restricted access is to be used
modifier isSocketGatewayOwner() {
if (msg.sender != ISocketGateway(socketGateway).owner()) {
revert OnlySocketGatewayOwner();
}
_;
}
/// @notice Implementing contract needs to make use of the modifier where restricted access is to be used
modifier isSocketDeployFactory() {
if (msg.sender != socketDeployFactory) {
revert OnlySocketDeployer();
}
_;
}
/****************************************
* RESTRICTED FUNCTIONS *
****************************************/
/**
* @notice function to rescue the ERC20 tokens in the Swap-Implementation contract
* @notice this is a function restricted to Owner of SocketGateway only
* @param token address of ERC20 token being rescued
* @param userAddress receipient address to which ERC20 tokens will be rescued to
* @param amount amount of ERC20 tokens being rescued
*/
function rescueFunds(
address token,
address userAddress,
uint256 amount
) external isSocketGatewayOwner {
ERC20(token).safeTransfer(userAddress, amount);
}
/**
* @notice function to rescue the native-balance in the Swap-Implementation contract
* @notice this is a function restricted to Owner of SocketGateway only
* @param userAddress receipient address to which native-balance will be rescued to
* @param amount amount of native balance tokens being rescued
*/
function rescueEther(
address payable userAddress,
uint256 amount
) external isSocketGatewayOwner {
userAddress.transfer(amount);
}
function killme() external isSocketDeployFactory {
selfdestruct(payable(msg.sender));
}
/******************************
* VIRTUAL FUNCTIONS *
*****************************/
/**
* @notice function to swap tokens on the chain
* All swap implementation contracts must implement this function
* @param fromToken token to be swapped
* @param toToken token to which fromToken has to be swapped
* @param amount amount of fromToken being swapped
* @param receiverAddress recipient address of toToken
* @param data encoded value of properties in the swapData Struct
*/
function performAction(
address fromToken,
address toToken,
uint256 amount,
address receiverAddress,
bytes memory data
) external payable virtual returns (uint256);
/**
* @notice function to swapWith - swaps tokens on the chain to socketGateway as recipient
* All swap implementation contracts must implement this function
* @param fromToken token to be swapped
* @param toToken token to which fromToken has to be swapped
* @param amount amount of fromToken being swapped
* @param swapExtraData encoded value of properties in the swapData Struct
*/
function performActionWithIn(
address fromToken,
address toToken,
uint256 amount,
bytes memory swapExtraData
) external payable virtual returns (uint256, address);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {SafeTransferLib} from "lib/solmate/src/utils/SafeTransferLib.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";
import "../SwapImplBase.sol";
import {Address0Provided, SwapFailed} from "../../errors/SocketErrors.sol";
import {ZEROX} from "../../static/RouteIdentifiers.sol";
/**
* @title ZeroX-Swap-Route Implementation
* @notice Route implementation with functions to swap tokens via ZeroX-Swap
* Called via SocketGateway if the routeId in the request maps to the routeId of ZeroX-Swap-Implementation
* @author Socket dot tech.
*/
contract ZeroXSwapImpl is SwapImplBase {
/// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens
using SafeTransferLib for ERC20;
bytes32 public immutable ZeroXIdentifier = ZEROX;
/// @notice unique name to identify the router, used to emit event upon successful bridging
bytes32 public immutable NAME = keccak256("Zerox-Router");
/// @notice address of ZeroX-Exchange-Proxy to swap the tokens on Chain
address payable public immutable zeroXExchangeProxy;
/// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase
/// @notice ZeroXExchangeProxy contract is payable to allow ethereum swaps
/// @dev ensure _zeroXExchangeProxy are set properly for the chainId in which the contract is being deployed
constructor(
address _zeroXExchangeProxy,
address _socketGateway,
address _socketDeployFactory
) SwapImplBase(_socketGateway, _socketDeployFactory) {
zeroXExchangeProxy = payable(_zeroXExchangeProxy);
}
receive() external payable {}
fallback() external payable {}
/**
* @notice function to swap tokens on the chain and transfer to receiver address
* @dev This is called only when there is a request for a swap.
* @param fromToken token to be swapped
* @param toToken token to which fromToken is to be swapped
* @param amount amount to be swapped
* @param receiverAddress address of toToken recipient
* @param swapExtraData data required for zeroX Exchange to get the swap done
*/
function performAction(
address fromToken,
address toToken,
uint256 amount,
address receiverAddress,
bytes calldata swapExtraData
) external payable override returns (uint256) {
if (fromToken == address(0)) {
revert Address0Provided();
}
bytes memory swapCallData = abi.decode(swapExtraData, (bytes));
uint256 _initialBalanceTokenOut;
uint256 _finalBalanceTokenOut;
ERC20 erc20ToToken = ERC20(toToken);
if (toToken != NATIVE_TOKEN_ADDRESS) {
_initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));
} else {
_initialBalanceTokenOut = address(this).balance;
}
if (fromToken != NATIVE_TOKEN_ADDRESS) {
ERC20 token = ERC20(fromToken);
token.safeTransferFrom(msg.sender, address(this), amount);
token.safeApprove(zeroXExchangeProxy, amount);
// solhint-disable-next-line
(bool success, ) = zeroXExchangeProxy.call(swapCallData);
if (!success) {
revert SwapFailed();
}
token.safeApprove(zeroXExchangeProxy, 0);
} else {
(bool success, ) = zeroXExchangeProxy.call{value: amount}(
swapCallData
);
if (!success) {
revert SwapFailed();
}
}
if (toToken != NATIVE_TOKEN_ADDRESS) {
_finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));
} else {
_finalBalanceTokenOut = address(this).balance;
}
uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;
if (toToken == NATIVE_TOKEN_ADDRESS) {
payable(receiverAddress).transfer(returnAmount);
} else {
erc20ToToken.transfer(receiverAddress, returnAmount);
}
emit SocketSwapTokens(
fromToken,
toToken,
returnAmount,
amount,
ZeroXIdentifier,
receiverAddress
);
return returnAmount;
}
/**
* @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient
* @param fromToken token to be swapped
* @param toToken token to which fromToken has to be swapped
* @param amount amount of fromToken being swapped
* @param swapExtraData encoded value of properties in the swapData Struct
* @return swapped amount (in toToken Address)
*/
function performActionWithIn(
address fromToken,
address toToken,
uint256 amount,
bytes calldata swapExtraData
) external payable override returns (uint256, address) {
if (fromToken == address(0)) {
revert Address0Provided();
}
bytes memory swapCallData = abi.decode(swapExtraData, (bytes));
uint256 _initialBalanceTokenOut;
uint256 _finalBalanceTokenOut;
ERC20 erc20ToToken = ERC20(toToken);
if (toToken != NATIVE_TOKEN_ADDRESS) {
_initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));
} else {
_initialBalanceTokenOut = address(this).balance;
}
if (fromToken != NATIVE_TOKEN_ADDRESS) {
ERC20 token = ERC20(fromToken);
token.safeTransferFrom(msg.sender, address(this), amount);
token.safeApprove(zeroXExchangeProxy, amount);
// solhint-disable-next-line
(bool success, ) = zeroXExchangeProxy.call(swapCallData);
if (!success) {
revert SwapFailed();
}
token.safeApprove(zeroXExchangeProxy, 0);
} else {
(bool success, ) = zeroXExchangeProxy.call{value: amount}(
swapCallData
);
if (!success) {
revert SwapFailed();
}
}
if (toToken != NATIVE_TOKEN_ADDRESS) {
_finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));
} else {
_finalBalanceTokenOut = address(this).balance;
}
uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;
emit SocketSwapTokens(
fromToken,
toToken,
returnAmount,
amount,
ZeroXIdentifier,
socketGateway
);
return (returnAmount, toToken);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.4;
import {OnlyOwner, OnlyNominee} from "../errors/SocketErrors.sol";
abstract contract Ownable {
address private _owner;
address private _nominee;
event OwnerNominated(address indexed nominee);
event OwnerClaimed(address indexed claimer);
constructor(address owner_) {
_claimOwner(owner_);
}
modifier onlyOwner() {
if (msg.sender != _owner) {
revert OnlyOwner();
}
_;
}
function owner() public view returns (address) {
return _owner;
}
function nominee() public view returns (address) {
return _nominee;
}
function nominateOwner(address nominee_) external {
if (msg.sender != _owner) {
revert OnlyOwner();
}
_nominee = nominee_;
emit OwnerNominated(_nominee);
}
function claimOwner() external {
if (msg.sender != _nominee) {
revert OnlyNominee();
}
_claimOwner(msg.sender);
}
function _claimOwner(address claimer_) internal {
_owner = claimer_;
_nominee = address(0);
emit OwnerClaimed(claimer_);
}
}
File 2 of 7: Proxy
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/**
* @title Proxy
* @notice Proxy is a transparent proxy that passes through the call if the caller is the owner or
* if the caller is address(0), meaning that the call originated from an off-chain
* simulation.
*/
contract Proxy {
/**
* @notice The storage slot that holds the address of the implementation.
* bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
*/
bytes32 internal constant IMPLEMENTATION_KEY =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @notice The storage slot that holds the address of the owner.
* bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
*/
bytes32 internal constant OWNER_KEY =
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @notice An event that is emitted each time the implementation is changed. This event is part
* of the EIP-1967 specification.
*
* @param implementation The address of the implementation contract
*/
event Upgraded(address indexed implementation);
/**
* @notice An event that is emitted each time the owner is upgraded. This event is part of the
* EIP-1967 specification.
*
* @param previousAdmin The previous owner of the contract
* @param newAdmin The new owner of the contract
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @notice A modifier that reverts if not called by the owner or by address(0) to allow
* eth_call to interact with this proxy without needing to use low-level storage
* inspection. We assume that nobody is able to trigger calls from address(0) during
* normal EVM execution.
*/
modifier proxyCallIfNotAdmin() {
if (msg.sender == _getAdmin() || msg.sender == address(0)) {
_;
} else {
// This WILL halt the call frame on completion.
_doProxyCall();
}
}
/**
* @notice Sets the initial admin during contract deployment. Admin address is stored at the
* EIP-1967 admin storage slot so that accidental storage collision with the
* implementation is not possible.
*
* @param _admin Address of the initial contract admin. Admin as the ability to access the
* transparent proxy interface.
*/
constructor(address _admin) {
_changeAdmin(_admin);
}
// slither-disable-next-line locked-ether
receive() external payable {
// Proxy call by default.
_doProxyCall();
}
// slither-disable-next-line locked-ether
fallback() external payable {
// Proxy call by default.
_doProxyCall();
}
/**
* @notice Set the implementation contract address. The code at the given address will execute
* when this contract is called.
*
* @param _implementation Address of the implementation contract.
*/
function upgradeTo(address _implementation) public virtual proxyCallIfNotAdmin {
_setImplementation(_implementation);
}
/**
* @notice Set the implementation and call a function in a single transaction. Useful to ensure
* atomic execution of initialization-based upgrades.
*
* @param _implementation Address of the implementation contract.
* @param _data Calldata to delegatecall the new implementation with.
*/
function upgradeToAndCall(address _implementation, bytes calldata _data)
public
payable
virtual
proxyCallIfNotAdmin
returns (bytes memory)
{
_setImplementation(_implementation);
(bool success, bytes memory returndata) = _implementation.delegatecall(_data);
require(success, "Proxy: delegatecall to new implementation contract failed");
return returndata;
}
/**
* @notice Changes the owner of the proxy contract. Only callable by the owner.
*
* @param _admin New owner of the proxy contract.
*/
function changeAdmin(address _admin) public virtual proxyCallIfNotAdmin {
_changeAdmin(_admin);
}
/**
* @notice Gets the owner of the proxy contract.
*
* @return Owner address.
*/
function admin() public virtual proxyCallIfNotAdmin returns (address) {
return _getAdmin();
}
/**
* @notice Queries the implementation address.
*
* @return Implementation address.
*/
function implementation() public virtual proxyCallIfNotAdmin returns (address) {
return _getImplementation();
}
/**
* @notice Sets the implementation address.
*
* @param _implementation New implementation address.
*/
function _setImplementation(address _implementation) internal {
assembly {
sstore(IMPLEMENTATION_KEY, _implementation)
}
emit Upgraded(_implementation);
}
/**
* @notice Changes the owner of the proxy contract.
*
* @param _admin New owner of the proxy contract.
*/
function _changeAdmin(address _admin) internal {
address previous = _getAdmin();
assembly {
sstore(OWNER_KEY, _admin)
}
emit AdminChanged(previous, _admin);
}
/**
* @notice Performs the proxy call via a delegatecall.
*/
function _doProxyCall() internal {
address impl = _getImplementation();
require(impl != address(0), "Proxy: implementation not initialized");
assembly {
// Copy calldata into memory at 0x0....calldatasize.
calldatacopy(0x0, 0x0, calldatasize())
// Perform the delegatecall, make sure to pass all available gas.
let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)
// Copy returndata into memory at 0x0....returndatasize. Note that this *will*
// overwrite the calldata that we just copied into memory but that doesn't really
// matter because we'll be returning in a second anyway.
returndatacopy(0x0, 0x0, returndatasize())
// Success == 0 means a revert. We'll revert too and pass the data up.
if iszero(success) {
revert(0x0, returndatasize())
}
// Otherwise we'll just return and pass the data up.
return(0x0, returndatasize())
}
}
/**
* @notice Queries the implementation address.
*
* @return Implementation address.
*/
function _getImplementation() internal view returns (address) {
address impl;
assembly {
impl := sload(IMPLEMENTATION_KEY)
}
return impl;
}
/**
* @notice Queries the owner of the proxy contract.
*
* @return Owner address.
*/
function _getAdmin() internal view returns (address) {
address owner;
assembly {
owner := sload(OWNER_KEY)
}
return owner;
}
}
File 3 of 7: Lib_ResolvedDelegateProxy
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* External Imports */
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Lib_AddressManager
*/
contract Lib_AddressManager is Ownable {
/**********
* Events *
**********/
event AddressSet(
string indexed _name,
address _newAddress,
address _oldAddress
);
/*************
* Variables *
*************/
mapping (bytes32 => address) private addresses;
/********************
* Public Functions *
********************/
/**
* Changes the address associated with a particular name.
* @param _name String name to associate an address with.
* @param _address Address to associate with the name.
*/
function setAddress(
string memory _name,
address _address
)
external
onlyOwner
{
bytes32 nameHash = _getNameHash(_name);
address oldAddress = addresses[nameHash];
addresses[nameHash] = _address;
emit AddressSet(
_name,
_address,
oldAddress
);
}
/**
* Retrieves the address associated with a given name.
* @param _name Name to retrieve an address for.
* @return Address associated with the given name.
*/
function getAddress(
string memory _name
)
external
view
returns (
address
)
{
return addresses[_getNameHash(_name)];
}
/**********************
* Internal Functions *
**********************/
/**
* Computes the hash of a name.
* @param _name Name to compute a hash for.
* @return Hash of the given name.
*/
function _getNameHash(
string memory _name
)
internal
pure
returns (
bytes32
)
{
return keccak256(abi.encodePacked(_name));
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* Library Imports */
import { Lib_AddressManager } from "./Lib_AddressManager.sol";
/**
* @title Lib_ResolvedDelegateProxy
*/
contract Lib_ResolvedDelegateProxy {
/*************
* Variables *
*************/
// Using mappings to store fields to avoid overwriting storage slots in the
// implementation contract. For example, instead of storing these fields at
// storage slot `0` & `1`, they are stored at `keccak256(key + slot)`.
// See: https://solidity.readthedocs.io/en/v0.7.0/internals/layout_in_storage.html
// NOTE: Do not use this code in your own contract system.
// There is a known flaw in this contract, and we will remove it from the repository
// in the near future. Due to the very limited way that we are using it, this flaw is
// not an issue in our system.
mapping (address => string) private implementationName;
mapping (address => Lib_AddressManager) private addressManager;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Lib_AddressManager.
* @param _implementationName implementationName of the contract to proxy to.
*/
constructor(
address _libAddressManager,
string memory _implementationName
) {
addressManager[address(this)] = Lib_AddressManager(_libAddressManager);
implementationName[address(this)] = _implementationName;
}
/*********************
* Fallback Function *
*********************/
fallback()
external
payable
{
address target = addressManager[address(this)].getAddress(
(implementationName[address(this)])
);
require(
target != address(0),
"Target address must be initialized."
);
(bool success, bytes memory returndata) = target.delegatecall(msg.data);
if (success == true) {
assembly {
return(add(returndata, 0x20), mload(returndata))
}
} else {
assembly {
revert(add(returndata, 0x20), mload(returndata))
}
}
}
}
File 4 of 7: L1_ETH_Bridge
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "./L1_Bridge.sol";
/**
* @dev A L1_Bridge that uses an ETH as the canonical token
*/
contract L1_ETH_Bridge is L1_Bridge {
constructor (address[] memory bonders, address _governance) public L1_Bridge(bonders, _governance) {}
/* ========== Override Functions ========== */
function _transferFromBridge(address recipient, uint256 amount) internal override {
(bool success, ) = recipient.call{value: amount}(new bytes(0));
require(success, 'L1_ETH_BRG: ETH transfer failed');
}
function _transferToBridge(address /*from*/, uint256 amount) internal override {
require(msg.value == amount, "L1_ETH_BRG: Value does not match amount");
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "./Bridge.sol";
import "../interfaces/IMessengerWrapper.sol";
/**
* @dev L1_Bridge is responsible for the bonding and challenging of TransferRoots. All TransferRoots
* originate in the L1_Bridge through `bondTransferRoot` and are propagated up to destination L2s.
*/
abstract contract L1_Bridge is Bridge {
struct TransferBond {
address bonder;
uint256 createdAt;
uint256 totalAmount;
uint256 challengeStartTime;
address challenger;
bool challengeResolved;
}
/* ========== State ========== */
mapping(uint256 => mapping(bytes32 => uint256)) public transferRootCommittedAt;
mapping(bytes32 => TransferBond) public transferBonds;
mapping(uint256 => mapping(address => uint256)) public timeSlotToAmountBonded;
mapping(uint256 => uint256) public chainBalance;
/* ========== Config State ========== */
address public governance;
mapping(uint256 => IMessengerWrapper) public crossDomainMessengerWrappers;
mapping(uint256 => bool) public isChainIdPaused;
uint256 public challengePeriod = 1 days;
uint256 public challengeResolutionPeriod = 10 days;
uint256 public minTransferRootBondDelay = 15 minutes;
uint256 public constant CHALLENGE_AMOUNT_DIVISOR = 10;
uint256 public constant TIME_SLOT_SIZE = 4 hours;
/* ========== Events ========== */
event TransferSentToL2(
uint256 indexed chainId,
address indexed recipient,
uint256 amount,
uint256 amountOutMin,
uint256 deadline,
address indexed relayer,
uint256 relayerFee
);
event TransferRootBonded (
bytes32 indexed root,
uint256 amount
);
event TransferRootConfirmed(
uint256 indexed originChainId,
uint256 indexed destinationChainId,
bytes32 indexed rootHash,
uint256 totalAmount
);
event TransferBondChallenged(
bytes32 indexed transferRootId,
bytes32 indexed rootHash,
uint256 originalAmount
);
event ChallengeResolved(
bytes32 indexed transferRootId,
bytes32 indexed rootHash,
uint256 originalAmount
);
/* ========== Modifiers ========== */
modifier onlyL2Bridge(uint256 chainId) {
IMessengerWrapper messengerWrapper = crossDomainMessengerWrappers[chainId];
messengerWrapper.verifySender(msg.sender, msg.data);
_;
}
constructor (address[] memory bonders, address _governance) public Bridge(bonders) {
governance = _governance;
}
/* ========== Send Functions ========== */
/**
* @notice `amountOutMin` and `deadline` should be 0 when no swap is intended at the destination.
* @notice `amount` is the total amount the user wants to send including the relayer fee
* @dev Send tokens to a supported layer-2 to mint hToken and optionally swap the hToken in the
* AMM at the destination.
* @param chainId The chainId of the destination chain
* @param recipient The address receiving funds at the destination
* @param amount The amount being sent
* @param amountOutMin The minimum amount received after attempting to swap in the destination
* AMM market. 0 if no swap is intended.
* @param deadline The deadline for swapping in the destination AMM market. 0 if no
* swap is intended.
* @param relayer The address of the relayer at the destination.
* @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.
*/
function sendToL2(
uint256 chainId,
address recipient,
uint256 amount,
uint256 amountOutMin,
uint256 deadline,
address relayer,
uint256 relayerFee
)
external
payable
{
IMessengerWrapper messengerWrapper = crossDomainMessengerWrappers[chainId];
require(messengerWrapper != IMessengerWrapper(0), "L1_BRG: chainId not supported");
require(isChainIdPaused[chainId] == false, "L1_BRG: Sends to this chainId are paused");
require(amount > 0, "L1_BRG: Must transfer a non-zero amount");
require(amount >= relayerFee, "L1_BRG: Relayer fee cannot exceed amount");
_transferToBridge(msg.sender, amount);
bytes memory message = abi.encodeWithSignature(
"distribute(address,uint256,uint256,uint256,address,uint256)",
recipient,
amount,
amountOutMin,
deadline,
relayer,
relayerFee
);
chainBalance[chainId] = chainBalance[chainId].add(amount);
messengerWrapper.sendCrossDomainMessage(message);
emit TransferSentToL2(
chainId,
recipient,
amount,
amountOutMin,
deadline,
relayer,
relayerFee
);
}
/* ========== TransferRoot Functions ========== */
/**
* @dev Setting a TransferRoot is a two step process.
* @dev 1. The TransferRoot is bonded with `bondTransferRoot`. Withdrawals can now begin on L1
* @dev and recipient L2's
* @dev 2. The TransferRoot is confirmed after `confirmTransferRoot` is called by the l2 bridge
* @dev where the TransferRoot originated.
*/
/**
* @dev Used by the Bonder to bond a TransferRoot and propagate it up to destination L2s
* @param rootHash The Merkle root of the TransferRoot Merkle tree
* @param destinationChainId The id of the destination chain
* @param totalAmount The amount destined for the destination chain
*/
function bondTransferRoot(
bytes32 rootHash,
uint256 destinationChainId,
uint256 totalAmount
)
external
onlyBonder
requirePositiveBalance
{
bytes32 transferRootId = getTransferRootId(rootHash, totalAmount);
require(transferRootCommittedAt[destinationChainId][transferRootId] == 0, "L1_BRG: TransferRoot has already been confirmed");
require(transferBonds[transferRootId].createdAt == 0, "L1_BRG: TransferRoot has already been bonded");
uint256 currentTimeSlot = getTimeSlot(block.timestamp);
uint256 bondAmount = getBondForTransferAmount(totalAmount);
timeSlotToAmountBonded[currentTimeSlot][msg.sender] = timeSlotToAmountBonded[currentTimeSlot][msg.sender].add(bondAmount);
transferBonds[transferRootId] = TransferBond(
msg.sender,
block.timestamp,
totalAmount,
uint256(0),
address(0),
false
);
_distributeTransferRoot(rootHash, destinationChainId, totalAmount);
emit TransferRootBonded(rootHash, totalAmount);
}
/**
* @dev Used by an L2 bridge to confirm a TransferRoot via cross-domain message. Once a TransferRoot
* has been confirmed, any challenge against that TransferRoot can be resolved as unsuccessful.
* @param originChainId The id of the origin chain
* @param rootHash The Merkle root of the TransferRoot Merkle tree
* @param destinationChainId The id of the destination chain
* @param totalAmount The amount destined for each destination chain
* @param rootCommittedAt The block timestamp when the TransferRoot was committed on its origin chain
*/
function confirmTransferRoot(
uint256 originChainId,
bytes32 rootHash,
uint256 destinationChainId,
uint256 totalAmount,
uint256 rootCommittedAt
)
external
onlyL2Bridge(originChainId)
{
bytes32 transferRootId = getTransferRootId(rootHash, totalAmount);
require(transferRootCommittedAt[destinationChainId][transferRootId] == 0, "L1_BRG: TransferRoot already confirmed");
require(rootCommittedAt > 0, "L1_BRG: rootCommittedAt must be greater than 0");
transferRootCommittedAt[destinationChainId][transferRootId] = rootCommittedAt;
chainBalance[originChainId] = chainBalance[originChainId].sub(totalAmount, "L1_BRG: Amount exceeds chainBalance. This indicates a layer-2 failure.");
// If the TransferRoot was never bonded, distribute the TransferRoot.
TransferBond storage transferBond = transferBonds[transferRootId];
if (transferBond.createdAt == 0) {
_distributeTransferRoot(rootHash, destinationChainId, totalAmount);
}
emit TransferRootConfirmed(originChainId, destinationChainId, rootHash, totalAmount);
}
function _distributeTransferRoot(
bytes32 rootHash,
uint256 chainId,
uint256 totalAmount
)
internal
{
// Set TransferRoot on recipient Bridge
if (chainId == getChainId()) {
// Set L1 TransferRoot
_setTransferRoot(rootHash, totalAmount);
} else {
chainBalance[chainId] = chainBalance[chainId].add(totalAmount);
IMessengerWrapper messengerWrapper = crossDomainMessengerWrappers[chainId];
require(messengerWrapper != IMessengerWrapper(0), "L1_BRG: chainId not supported");
// Set L2 TransferRoot
bytes memory setTransferRootMessage = abi.encodeWithSignature(
"setTransferRoot(bytes32,uint256)",
rootHash,
totalAmount
);
messengerWrapper.sendCrossDomainMessage(setTransferRootMessage);
}
}
/* ========== External TransferRoot Challenges ========== */
/**
* @dev Challenge a TransferRoot believed to be fraudulent
* @param rootHash The Merkle root of the TransferRoot Merkle tree
* @param originalAmount The total amount bonded for this TransferRoot
* @param destinationChainId The id of the destination chain
*/
function challengeTransferBond(bytes32 rootHash, uint256 originalAmount, uint256 destinationChainId) external payable {
bytes32 transferRootId = getTransferRootId(rootHash, originalAmount);
TransferBond storage transferBond = transferBonds[transferRootId];
require(transferRootCommittedAt[destinationChainId][transferRootId] == 0, "L1_BRG: TransferRoot has already been confirmed");
require(transferBond.createdAt != 0, "L1_BRG: TransferRoot has not been bonded");
uint256 challengePeriodEnd = transferBond.createdAt.add(challengePeriod);
require(challengePeriodEnd >= block.timestamp, "L1_BRG: TransferRoot cannot be challenged after challenge period");
require(transferBond.challengeStartTime == 0, "L1_BRG: TransferRoot already challenged");
transferBond.challengeStartTime = block.timestamp;
transferBond.challenger = msg.sender;
// Move amount from timeSlotToAmountBonded to debit
uint256 timeSlot = getTimeSlot(transferBond.createdAt);
uint256 bondAmount = getBondForTransferAmount(originalAmount);
address bonder = transferBond.bonder;
timeSlotToAmountBonded[timeSlot][bonder] = timeSlotToAmountBonded[timeSlot][bonder].sub(bondAmount);
_addDebit(transferBond.bonder, bondAmount);
// Get stake for challenge
uint256 challengeStakeAmount = getChallengeAmountForTransferAmount(originalAmount);
_transferToBridge(msg.sender, challengeStakeAmount);
emit TransferBondChallenged(transferRootId, rootHash, originalAmount);
}
/**
* @dev Resolve a challenge after the `challengeResolutionPeriod` has passed
* @param rootHash The Merkle root of the TransferRoot Merkle tree
* @param originalAmount The total amount originally bonded for this TransferRoot
* @param destinationChainId The id of the destination chain
*/
function resolveChallenge(bytes32 rootHash, uint256 originalAmount, uint256 destinationChainId) external {
bytes32 transferRootId = getTransferRootId(rootHash, originalAmount);
TransferBond storage transferBond = transferBonds[transferRootId];
require(transferBond.challengeStartTime != 0, "L1_BRG: TransferRoot has not been challenged");
require(block.timestamp > transferBond.challengeStartTime.add(challengeResolutionPeriod), "L1_BRG: Challenge period has not ended");
require(transferBond.challengeResolved == false, "L1_BRG: TransferRoot already resolved");
transferBond.challengeResolved = true;
uint256 challengeStakeAmount = getChallengeAmountForTransferAmount(originalAmount);
if (transferRootCommittedAt[destinationChainId][transferRootId] > 0) {
// Invalid challenge
if (transferBond.createdAt > transferRootCommittedAt[destinationChainId][transferRootId].add(minTransferRootBondDelay)) {
// Credit the bonder back with the bond amount plus the challenger's stake
_addCredit(transferBond.bonder, getBondForTransferAmount(originalAmount).add(challengeStakeAmount));
} else {
// If the TransferRoot was bonded before it was committed, the challenger and Bonder
// get their stake back. This discourages Bonders from tricking challengers into
// challenging a valid TransferRoots that haven't yet been committed. It also ensures
// that Bonders are not punished if a TransferRoot is bonded too soon in error.
// Return the challenger's stake
_addCredit(transferBond.challenger, challengeStakeAmount);
// Credit the bonder back with the bond amount
_addCredit(transferBond.bonder, getBondForTransferAmount(originalAmount));
}
} else {
// Valid challenge
// Burn 25% of the challengers stake
_transferFromBridge(address(0xdead), challengeStakeAmount.mul(1).div(4));
// Reward challenger with the remaining 75% of their stake plus 100% of the Bonder's stake
_addCredit(transferBond.challenger, challengeStakeAmount.mul(7).div(4));
}
emit ChallengeResolved(transferRootId, rootHash, originalAmount);
}
/* ========== Override Functions ========== */
function _additionalDebit(address bonder) internal view override returns (uint256) {
uint256 currentTimeSlot = getTimeSlot(block.timestamp);
uint256 bonded = 0;
uint256 numTimeSlots = challengePeriod / TIME_SLOT_SIZE;
for (uint256 i = 0; i < numTimeSlots; i++) {
bonded = bonded.add(timeSlotToAmountBonded[currentTimeSlot - i][bonder]);
}
return bonded;
}
function _requireIsGovernance() internal override {
require(governance == msg.sender, "L1_BRG: Caller is not the owner");
}
/* ========== External Config Management Setters ========== */
function setGovernance(address _newGovernance) external onlyGovernance {
require(_newGovernance != address(0), "L1_BRG: _newGovernance cannot be address(0)");
governance = _newGovernance;
}
function setCrossDomainMessengerWrapper(uint256 chainId, IMessengerWrapper _crossDomainMessengerWrapper) external onlyGovernance {
crossDomainMessengerWrappers[chainId] = _crossDomainMessengerWrapper;
}
function setChainIdDepositsPaused(uint256 chainId, bool isPaused) external onlyGovernance {
isChainIdPaused[chainId] = isPaused;
}
function setChallengePeriod(uint256 _challengePeriod) external onlyGovernance {
require(_challengePeriod % TIME_SLOT_SIZE == 0, "L1_BRG: challengePeriod must be divisible by TIME_SLOT_SIZE");
challengePeriod = _challengePeriod;
}
function setChallengeResolutionPeriod(uint256 _challengeResolutionPeriod) external onlyGovernance {
challengeResolutionPeriod = _challengeResolutionPeriod;
}
function setMinTransferRootBondDelay(uint256 _minTransferRootBondDelay) external onlyGovernance {
minTransferRootBondDelay = _minTransferRootBondDelay;
}
/* ========== Public Getters ========== */
function getBondForTransferAmount(uint256 amount) public pure returns (uint256) {
// Bond covers amount plus a bounty to pay a potential challenger
return amount.add(getChallengeAmountForTransferAmount(amount));
}
function getChallengeAmountForTransferAmount(uint256 amount) public pure returns (uint256) {
// Bond covers amount plus a bounty to pay a potential challenger
return amount.div(CHALLENGE_AMOUNT_DIVISOR);
}
function getTimeSlot(uint256 time) public pure returns (uint256) {
return time / TIME_SLOT_SIZE;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "./Accounting.sol";
import "../libraries/Lib_MerkleTree.sol";
/**
* @dev Bridge extends the accounting system and encapsulates the logic that is shared by both the
* L1 and L2 Bridges. It allows to TransferRoots to be set by parent contracts and for those
* TransferRoots to be withdrawn against. It also allows the bonder to bond and withdraw Transfers
* directly through `bondWithdrawal` and then settle those bonds against their TransferRoot once it
* has been set.
*/
abstract contract Bridge is Accounting {
using Lib_MerkleTree for bytes32;
struct TransferRoot {
uint256 total;
uint256 amountWithdrawn;
uint256 createdAt;
}
/* ========== Events ========== */
event Withdrew(
bytes32 indexed transferId,
address indexed recipient,
uint256 amount,
bytes32 transferNonce
);
event WithdrawalBonded(
bytes32 indexed transferId,
uint256 amount
);
event WithdrawalBondSettled(
address indexed bonder,
bytes32 indexed transferId,
bytes32 indexed rootHash
);
event MultipleWithdrawalsSettled(
address indexed bonder,
bytes32 indexed rootHash,
uint256 totalBondsSettled
);
event TransferRootSet(
bytes32 indexed rootHash,
uint256 totalAmount
);
/* ========== State ========== */
mapping(bytes32 => TransferRoot) private _transferRoots;
mapping(bytes32 => bool) private _spentTransferIds;
mapping(address => mapping(bytes32 => uint256)) private _bondedWithdrawalAmounts;
uint256 constant RESCUE_DELAY = 8 weeks;
constructor(address[] memory bonders) public Accounting(bonders) {}
/* ========== Public Getters ========== */
/**
* @dev Get the hash that represents an individual Transfer.
* @param chainId The id of the destination chain
* @param recipient The address receiving the Transfer
* @param amount The amount being transferred including the `_bonderFee`
* @param transferNonce Used to avoid transferId collisions
* @param bonderFee The amount paid to the address that withdraws the Transfer
* @param amountOutMin The minimum amount received after attempting to swap in the destination
* AMM market. 0 if no swap is intended.
* @param deadline The deadline for swapping in the destination AMM market. 0 if no
* swap is intended.
*/
function getTransferId(
uint256 chainId,
address recipient,
uint256 amount,
bytes32 transferNonce,
uint256 bonderFee,
uint256 amountOutMin,
uint256 deadline
)
public
pure
returns (bytes32)
{
return keccak256(abi.encode(
chainId,
recipient,
amount,
transferNonce,
bonderFee,
amountOutMin,
deadline
));
}
/**
* @notice getChainId can be overridden by subclasses if needed for compatibility or testing purposes.
* @dev Get the current chainId
* @return chainId The current chainId
*/
function getChainId() public virtual view returns (uint256 chainId) {
this; // Silence state mutability warning without generating any additional byte code
assembly {
chainId := chainid()
}
}
/**
* @dev Get the TransferRoot id for a given rootHash and totalAmount
* @param rootHash The Merkle root of the TransferRoot
* @param totalAmount The total of all Transfers in the TransferRoot
* @return The calculated transferRootId
*/
function getTransferRootId(bytes32 rootHash, uint256 totalAmount) public pure returns (bytes32) {
return keccak256(abi.encodePacked(rootHash, totalAmount));
}
/**
* @dev Get the TransferRoot for a given rootHash and totalAmount
* @param rootHash The Merkle root of the TransferRoot
* @param totalAmount The total of all Transfers in the TransferRoot
* @return The TransferRoot with the calculated transferRootId
*/
function getTransferRoot(bytes32 rootHash, uint256 totalAmount) public view returns (TransferRoot memory) {
return _transferRoots[getTransferRootId(rootHash, totalAmount)];
}
/**
* @dev Get the amount bonded for the withdrawal of a transfer
* @param bonder The Bonder of the withdrawal
* @param transferId The Transfer's unique identifier
* @return The amount bonded for a Transfer withdrawal
*/
function getBondedWithdrawalAmount(address bonder, bytes32 transferId) external view returns (uint256) {
return _bondedWithdrawalAmounts[bonder][transferId];
}
/**
* @dev Get the spent status of a transfer ID
* @param transferId The transfer's unique identifier
* @return True if the transferId has been spent
*/
function isTransferIdSpent(bytes32 transferId) external view returns (bool) {
return _spentTransferIds[transferId];
}
/* ========== User/Relayer External Functions ========== */
/**
* @notice Can be called by anyone (recipient or relayer)
* @dev Withdraw a Transfer from its destination bridge
* @param recipient The address receiving the Transfer
* @param amount The amount being transferred including the `_bonderFee`
* @param transferNonce Used to avoid transferId collisions
* @param bonderFee The amount paid to the address that withdraws the Transfer
* @param amountOutMin The minimum amount received after attempting to swap in the destination
* AMM market. 0 if no swap is intended. (only used to calculate `transferId` in this function)
* @param deadline The deadline for swapping in the destination AMM market. 0 if no
* swap is intended. (only used to calculate `transferId` in this function)
* @param rootHash The Merkle root of the TransferRoot
* @param transferRootTotalAmount The total amount being transferred in a TransferRoot
* @param transferIdTreeIndex The index of the transferId in the Merkle tree
* @param siblings The siblings of the transferId in the Merkle tree
* @param totalLeaves The total number of leaves in the Merkle tree
*/
function withdraw(
address recipient,
uint256 amount,
bytes32 transferNonce,
uint256 bonderFee,
uint256 amountOutMin,
uint256 deadline,
bytes32 rootHash,
uint256 transferRootTotalAmount,
uint256 transferIdTreeIndex,
bytes32[] calldata siblings,
uint256 totalLeaves
)
external
nonReentrant
{
bytes32 transferId = getTransferId(
getChainId(),
recipient,
amount,
transferNonce,
bonderFee,
amountOutMin,
deadline
);
require(
rootHash.verify(
transferId,
transferIdTreeIndex,
siblings,
totalLeaves
)
, "BRG: Invalid transfer proof");
bytes32 transferRootId = getTransferRootId(rootHash, transferRootTotalAmount);
_addToAmountWithdrawn(transferRootId, amount);
_fulfillWithdraw(transferId, recipient, amount, uint256(0));
emit Withdrew(transferId, recipient, amount, transferNonce);
}
/**
* @dev Allows the bonder to bond individual withdrawals before their TransferRoot has been committed.
* @param recipient The address receiving the Transfer
* @param amount The amount being transferred including the `_bonderFee`
* @param transferNonce Used to avoid transferId collisions
* @param bonderFee The amount paid to the address that withdraws the Transfer
*/
function bondWithdrawal(
address recipient,
uint256 amount,
bytes32 transferNonce,
uint256 bonderFee
)
external
onlyBonder
requirePositiveBalance
nonReentrant
{
bytes32 transferId = getTransferId(
getChainId(),
recipient,
amount,
transferNonce,
bonderFee,
0,
0
);
_bondWithdrawal(transferId, amount);
_fulfillWithdraw(transferId, recipient, amount, bonderFee);
}
/**
* @dev Refunds the Bonder's stake from a bonded withdrawal and counts that withdrawal against
* its TransferRoot.
* @param bonder The Bonder of the withdrawal
* @param transferId The Transfer's unique identifier
* @param rootHash The Merkle root of the TransferRoot
* @param transferRootTotalAmount The total amount being transferred in a TransferRoot
* @param transferIdTreeIndex The index of the transferId in the Merkle tree
* @param siblings The siblings of the transferId in the Merkle tree
* @param totalLeaves The total number of leaves in the Merkle tree
*/
function settleBondedWithdrawal(
address bonder,
bytes32 transferId,
bytes32 rootHash,
uint256 transferRootTotalAmount,
uint256 transferIdTreeIndex,
bytes32[] calldata siblings,
uint256 totalLeaves
)
external
{
require(
rootHash.verify(
transferId,
transferIdTreeIndex,
siblings,
totalLeaves
)
, "BRG: Invalid transfer proof");
bytes32 transferRootId = getTransferRootId(rootHash, transferRootTotalAmount);
uint256 amount = _bondedWithdrawalAmounts[bonder][transferId];
require(amount > 0, "L2_BRG: transferId has no bond");
_bondedWithdrawalAmounts[bonder][transferId] = 0;
_addToAmountWithdrawn(transferRootId, amount);
_addCredit(bonder, amount);
emit WithdrawalBondSettled(bonder, transferId, rootHash);
}
/**
* @dev Refunds the Bonder for all withdrawals that they bonded in a TransferRoot.
* @param bonder The address of the Bonder being refunded
* @param transferIds All transferIds in the TransferRoot in order
* @param totalAmount The totalAmount of the TransferRoot
*/
function settleBondedWithdrawals(
address bonder,
// transferIds _must_ be calldata or it will be mutated by Lib_MerkleTree.getMerkleRoot
bytes32[] calldata transferIds,
uint256 totalAmount
)
external
{
bytes32 rootHash = Lib_MerkleTree.getMerkleRoot(transferIds);
bytes32 transferRootId = getTransferRootId(rootHash, totalAmount);
uint256 totalBondsSettled = 0;
for(uint256 i = 0; i < transferIds.length; i++) {
uint256 transferBondAmount = _bondedWithdrawalAmounts[bonder][transferIds[i]];
if (transferBondAmount > 0) {
totalBondsSettled = totalBondsSettled.add(transferBondAmount);
_bondedWithdrawalAmounts[bonder][transferIds[i]] = 0;
}
}
_addToAmountWithdrawn(transferRootId, totalBondsSettled);
_addCredit(bonder, totalBondsSettled);
emit MultipleWithdrawalsSettled(bonder, rootHash, totalBondsSettled);
}
/* ========== External TransferRoot Rescue ========== */
/**
* @dev Allows governance to withdraw the remaining amount from a TransferRoot after the rescue delay has passed.
* @param rootHash the Merkle root of the TransferRoot
* @param originalAmount The TransferRoot's recorded total
* @param recipient The address receiving the remaining balance
*/
function rescueTransferRoot(bytes32 rootHash, uint256 originalAmount, address recipient) external onlyGovernance {
bytes32 transferRootId = getTransferRootId(rootHash, originalAmount);
TransferRoot memory transferRoot = getTransferRoot(rootHash, originalAmount);
require(transferRoot.createdAt != 0, "BRG: TransferRoot not found");
assert(transferRoot.total == originalAmount);
uint256 rescueDelayEnd = transferRoot.createdAt.add(RESCUE_DELAY);
require(block.timestamp >= rescueDelayEnd, "BRG: TransferRoot cannot be rescued before the Rescue Delay");
uint256 remainingAmount = transferRoot.total.sub(transferRoot.amountWithdrawn);
_addToAmountWithdrawn(transferRootId, remainingAmount);
_transferFromBridge(recipient, remainingAmount);
}
/* ========== Internal Functions ========== */
function _markTransferSpent(bytes32 transferId) internal {
require(!_spentTransferIds[transferId], "BRG: The transfer has already been withdrawn");
_spentTransferIds[transferId] = true;
}
function _addToAmountWithdrawn(bytes32 transferRootId, uint256 amount) internal {
TransferRoot storage transferRoot = _transferRoots[transferRootId];
require(transferRoot.total > 0, "BRG: Transfer root not found");
uint256 newAmountWithdrawn = transferRoot.amountWithdrawn.add(amount);
require(newAmountWithdrawn <= transferRoot.total, "BRG: Withdrawal exceeds TransferRoot total");
transferRoot.amountWithdrawn = newAmountWithdrawn;
}
function _setTransferRoot(bytes32 rootHash, uint256 totalAmount) internal {
bytes32 transferRootId = getTransferRootId(rootHash, totalAmount);
require(_transferRoots[transferRootId].total == 0, "BRG: Transfer root already set");
require(totalAmount > 0, "BRG: Cannot set TransferRoot totalAmount of 0");
_transferRoots[transferRootId] = TransferRoot(totalAmount, 0, block.timestamp);
emit TransferRootSet(rootHash, totalAmount);
}
function _bondWithdrawal(bytes32 transferId, uint256 amount) internal {
require(_bondedWithdrawalAmounts[msg.sender][transferId] == 0, "BRG: Withdrawal has already been bonded");
_addDebit(msg.sender, amount);
_bondedWithdrawalAmounts[msg.sender][transferId] = amount;
emit WithdrawalBonded(transferId, amount);
}
/* ========== Private Functions ========== */
/// @dev Completes the Transfer, distributes the Bonder fee and marks the Transfer as spent.
function _fulfillWithdraw(
bytes32 transferId,
address recipient,
uint256 amount,
uint256 bonderFee
) private {
_markTransferSpent(transferId);
_transferFromBridge(recipient, amount.sub(bonderFee));
if (bonderFee > 0) {
_transferFromBridge(msg.sender, bonderFee);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <0.8.0;
pragma experimental ABIEncoderV2;
interface IMessengerWrapper {
function sendCrossDomainMessage(bytes memory _calldata) external;
function verifySender(address l1BridgeCaller, bytes memory _data) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @dev Accounting is an abstract contract that encapsulates the most critical logic in the Hop contracts.
* The accounting system works by using two balances that can only increase `_credit` and `_debit`.
* A bonder's available balance is the total credit minus the total debit. The contract exposes
* two external functions that allows a bonder to stake and unstake and exposes two internal
* functions to its child contracts that allow the child contract to add to the credit
* and debit balance. In addition, child contracts can override `_additionalDebit` to account
* for any additional debit balance in an alternative way. Lastly, it exposes a modifier,
* `requirePositiveBalance`, that can be used by child contracts to ensure the bonder does not
* use more than its available stake.
*/
abstract contract Accounting is ReentrancyGuard {
using SafeMath for uint256;
mapping(address => bool) private _isBonder;
mapping(address => uint256) private _credit;
mapping(address => uint256) private _debit;
event Stake (
address indexed account,
uint256 amount
);
event Unstake (
address indexed account,
uint256 amount
);
event BonderAdded (
address indexed newBonder
);
event BonderRemoved (
address indexed previousBonder
);
/* ========== Modifiers ========== */
modifier onlyBonder {
require(_isBonder[msg.sender], "ACT: Caller is not bonder");
_;
}
modifier onlyGovernance {
_requireIsGovernance();
_;
}
/// @dev Used by parent contract to ensure that the Bonder is solvent at the end of the transaction.
modifier requirePositiveBalance {
_;
require(getCredit(msg.sender) >= getDebitAndAdditionalDebit(msg.sender), "ACT: Not enough available credit");
}
/// @dev Sets the Bonder addresses
constructor(address[] memory bonders) public {
for (uint256 i = 0; i < bonders.length; i++) {
require(_isBonder[bonders[i]] == false, "ACT: Cannot add duplicate bonder");
_isBonder[bonders[i]] = true;
emit BonderAdded(bonders[i]);
}
}
/* ========== Virtual functions ========== */
/**
* @dev The following functions are overridden in L1_Bridge and L2_Bridge
*/
function _transferFromBridge(address recipient, uint256 amount) internal virtual;
function _transferToBridge(address from, uint256 amount) internal virtual;
function _requireIsGovernance() internal virtual;
/**
* @dev This function can be optionally overridden by a parent contract to track any additional
* debit balance in an alternative way.
*/
function _additionalDebit(address /*bonder*/) internal view virtual returns (uint256) {
this; // Silence state mutability warning without generating any additional byte code
return 0;
}
/* ========== Public/external getters ========== */
/**
* @dev Check if address is a Bonder
* @param maybeBonder The address being checked
* @return true if address is a Bonder
*/
function getIsBonder(address maybeBonder) public view returns (bool) {
return _isBonder[maybeBonder];
}
/**
* @dev Get the Bonder's credit balance
* @param bonder The owner of the credit balance being checked
* @return The credit balance for the Bonder
*/
function getCredit(address bonder) public view returns (uint256) {
return _credit[bonder];
}
/**
* @dev Gets the debit balance tracked by `_debit` and does not include `_additionalDebit()`
* @param bonder The owner of the debit balance being checked
* @return The debit amount for the Bonder
*/
function getRawDebit(address bonder) external view returns (uint256) {
return _debit[bonder];
}
/**
* @dev Get the Bonder's total debit
* @param bonder The owner of the debit balance being checked
* @return The Bonder's total debit balance
*/
function getDebitAndAdditionalDebit(address bonder) public view returns (uint256) {
return _debit[bonder].add(_additionalDebit(bonder));
}
/* ========== Bonder external functions ========== */
/**
* @dev Allows the Bonder to deposit tokens and increase its credit balance
* @param bonder The address being staked on
* @param amount The amount being staked
*/
function stake(address bonder, uint256 amount) external payable nonReentrant {
require(_isBonder[bonder] == true, "ACT: Address is not bonder");
_transferToBridge(msg.sender, amount);
_addCredit(bonder, amount);
emit Stake(bonder, amount);
}
/**
* @dev Allows the caller to withdraw any available balance and add to their debit balance
* @param amount The amount being unstaked
*/
function unstake(uint256 amount) external requirePositiveBalance nonReentrant {
_addDebit(msg.sender, amount);
_transferFromBridge(msg.sender, amount);
emit Unstake(msg.sender, amount);
}
/**
* @dev Add Bonder to allowlist
* @param bonder The address being added as a Bonder
*/
function addBonder(address bonder) external onlyGovernance {
require(_isBonder[bonder] == false, "ACT: Address is already bonder");
_isBonder[bonder] = true;
emit BonderAdded(bonder);
}
/**
* @dev Remove Bonder from allowlist
* @param bonder The address being removed as a Bonder
*/
function removeBonder(address bonder) external onlyGovernance {
require(_isBonder[bonder] == true, "ACT: Address is not bonder");
_isBonder[bonder] = false;
emit BonderRemoved(bonder);
}
/* ========== Internal functions ========== */
function _addCredit(address bonder, uint256 amount) internal {
_credit[bonder] = _credit[bonder].add(amount);
}
function _addDebit(address bonder, uint256 amount) internal {
_debit[bonder] = _debit[bonder].add(amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/**
* @title Lib_MerkleTree
* @author River Keefer
*/
library Lib_MerkleTree {
/**********************
* Internal Functions *
**********************/
/**
* Calculates a merkle root for a list of 32-byte leaf hashes. WARNING: If the number
* of leaves passed in is not a power of two, it pads out the tree with zero hashes.
* If you do not know the original length of elements for the tree you are verifying,
* then this may allow empty leaves past _elements.length to pass a verification check down the line.
* Note that the _elements argument is modified, therefore it must not be used again afterwards
* @param _elements Array of hashes from which to generate a merkle root.
* @return Merkle root of the leaves, with zero hashes for non-powers-of-two (see above).
*/
function getMerkleRoot(
bytes32[] memory _elements
)
internal
pure
returns (
bytes32
)
{
require(
_elements.length > 0,
"Lib_MerkleTree: Must provide at least one leaf hash."
);
if (_elements.length == 1) {
return _elements[0];
}
uint256[16] memory defaults = [
0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563,
0x633dc4d7da7256660a892f8f1604a44b5432649cc8ec5cb3ced4c4e6ac94dd1d,
0x890740a8eb06ce9be422cb8da5cdafc2b58c0a5e24036c578de2a433c828ff7d,
0x3b8ec09e026fdc305365dfc94e189a81b38c7597b3d941c279f042e8206e0bd8,
0xecd50eee38e386bd62be9bedb990706951b65fe053bd9d8a521af753d139e2da,
0xdefff6d330bb5403f63b14f33b578274160de3a50df4efecf0e0db73bcdd3da5,
0x617bdd11f7c0a11f49db22f629387a12da7596f9d1704d7465177c63d88ec7d7,
0x292c23a9aa1d8bea7e2435e555a4a60e379a5a35f3f452bae60121073fb6eead,
0xe1cea92ed99acdcb045a6726b2f87107e8a61620a232cf4d7d5b5766b3952e10,
0x7ad66c0a68c72cb89e4fb4303841966e4062a76ab97451e3b9fb526a5ceb7f82,
0xe026cc5a4aed3c22a58cbd3d2ac754c9352c5436f638042dca99034e83636516,
0x3d04cffd8b46a874edf5cfae63077de85f849a660426697b06a829c70dd1409c,
0xad676aa337a485e4728a0b240d92b3ef7b3c372d06d189322bfd5f61f1e7203e,
0xa2fca4a49658f9fab7aa63289c91b7c7b6c832a6d0e69334ff5b0a3483d09dab,
0x4ebfd9cd7bca2505f7bef59cc1c12ecc708fff26ae4af19abe852afe9e20c862,
0x2def10d13dd169f550f578bda343d9717a138562e0093b380a1120789d53cf10
];
// Reserve memory space for our hashes.
bytes memory buf = new bytes(64);
// We'll need to keep track of left and right siblings.
bytes32 leftSibling;
bytes32 rightSibling;
// Number of non-empty nodes at the current depth.
uint256 rowSize = _elements.length;
// Current depth, counting from 0 at the leaves
uint256 depth = 0;
// Common sub-expressions
uint256 halfRowSize; // rowSize / 2
bool rowSizeIsOdd; // rowSize % 2 == 1
while (rowSize > 1) {
halfRowSize = rowSize / 2;
rowSizeIsOdd = rowSize % 2 == 1;
for (uint256 i = 0; i < halfRowSize; i++) {
leftSibling = _elements[(2 * i) ];
rightSibling = _elements[(2 * i) + 1];
assembly {
mstore(add(buf, 32), leftSibling )
mstore(add(buf, 64), rightSibling)
}
_elements[i] = keccak256(buf);
}
if (rowSizeIsOdd) {
leftSibling = _elements[rowSize - 1];
rightSibling = bytes32(defaults[depth]);
assembly {
mstore(add(buf, 32), leftSibling)
mstore(add(buf, 64), rightSibling)
}
_elements[halfRowSize] = keccak256(buf);
}
rowSize = halfRowSize + (rowSizeIsOdd ? 1 : 0);
depth++;
}
return _elements[0];
}
/**
* Verifies a merkle branch for the given leaf hash. Assumes the original length
* of leaves generated is a known, correct input, and does not return true for indices
* extending past that index (even if _siblings would be otherwise valid.)
* @param _root The Merkle root to verify against.
* @param _leaf The leaf hash to verify inclusion of.
* @param _index The index in the tree of this leaf.
* @param _siblings Array of sibline nodes in the inclusion proof, starting from depth 0 (bottom of the tree).
* @param _totalLeaves The total number of leaves originally passed into.
* @return Whether or not the merkle branch and leaf passes verification.
*/
function verify(
bytes32 _root,
bytes32 _leaf,
uint256 _index,
bytes32[] memory _siblings,
uint256 _totalLeaves
)
internal
pure
returns (
bool
)
{
require(
_totalLeaves > 0,
"Lib_MerkleTree: Total leaves must be greater than zero."
);
require(
_index < _totalLeaves,
"Lib_MerkleTree: Index out of bounds."
);
require(
_siblings.length == _ceilLog2(_totalLeaves),
"Lib_MerkleTree: Total siblings does not correctly correspond to total leaves."
);
bytes32 computedRoot = _leaf;
for (uint256 i = 0; i < _siblings.length; i++) {
if ((_index & 1) == 1) {
computedRoot = keccak256(
abi.encodePacked(
_siblings[i],
computedRoot
)
);
} else {
computedRoot = keccak256(
abi.encodePacked(
computedRoot,
_siblings[i]
)
);
}
_index >>= 1;
}
return _root == computedRoot;
}
/*********************
* Private Functions *
*********************/
/**
* Calculates the integer ceiling of the log base 2 of an input.
* @param _in Unsigned input to calculate the log.
* @return ceil(log_base_2(_in))
*/
function _ceilLog2(
uint256 _in
)
private
pure
returns (
uint256
)
{
require(
_in > 0,
"Lib_MerkleTree: Cannot compute ceil(log_2) of 0."
);
if (_in == 1) {
return 0;
}
// Find the highest set bit (will be floor(log_2)).
// Borrowed with <3 from https://github.com/ethereum/solidity-examples
uint256 val = _in;
uint256 highest = 0;
for (uint256 i = 128; i >= 1; i >>= 1) {
if (val & (uint(1) << i) - 1 << i != 0) {
highest += i;
val >>= i;
}
}
// Increment by one if this is not a perfect logarithm.
if ((uint(1) << highest) != _in) {
highest += 1;
}
return highest;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a % b;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor () internal {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
File 5 of 7: OptimismMessengerWrapper
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <=0.8.9;
pragma experimental ABIEncoderV2;
interface IMessengerWrapper {
function sendCrossDomainMessage(bytes memory _calldata) external;
function verifySender(address l1BridgeCaller, bytes memory _data) external;
function confirmRoots(
bytes32[] calldata rootHashes,
uint256[] calldata destinationChainIds,
uint256[] calldata totalAmounts,
uint256[] calldata rootCommittedAts
) external;
}
// SPDX-License-Identifier: MIT
// +build ovm
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/**
* @title iOVM_BaseCrossDomainMessenger
*/
interface iOVM_BaseCrossDomainMessenger {
/**********
* Events *
**********/
event SentMessage(bytes message);
event RelayedMessage(bytes32 msgHash);
/**********************
* Contract Variables *
**********************/
function xDomainMessageSender() external view returns (address);
/********************
* Public Functions *
********************/
/**
* Sends a cross domain message to the target messenger.
* @param _target Target contract address.
* @param _message Message to send to the target.
* @param _gasLimit Gas limit for the provided message.
*/
function sendMessage(
address _target,
bytes calldata _message,
uint32 _gasLimit
) external;
function deposit(
address _depositor,
uint256 _amount,
bool _send
) external;
}// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
import { iOVM_BaseCrossDomainMessenger } from "./iOVM_BaseCrossDomainMessenger.sol";
/**
* @title iOVM_L1CrossDomainMessenger
*/
interface iOVM_L1CrossDomainMessenger is iOVM_BaseCrossDomainMessenger {}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <=0.8.9;
pragma experimental ABIEncoderV2;
import "../interfaces/IMessengerWrapper.sol";
contract IL1Bridge {
struct TransferBond {
address bonder;
uint256 createdAt;
uint256 totalAmount;
uint256 challengeStartTime;
address challenger;
bool challengeResolved;
}
uint256 public challengePeriod;
mapping(bytes32 => TransferBond) public transferBonds;
function getIsBonder(address maybeBonder) public view returns (bool) {}
function getTransferRootId(bytes32 rootHash, uint256 totalAmount) public pure returns (bytes32) {}
function confirmTransferRoot(
uint256 originChainId,
bytes32 rootHash,
uint256 destinationChainId,
uint256 totalAmount,
uint256 rootCommittedAt
)
external
{}
}
abstract contract MessengerWrapper is IMessengerWrapper {
address public immutable l1BridgeAddress;
uint256 public immutable l2ChainId;
bool public isRootConfirmation = false;
constructor(address _l1BridgeAddress, uint256 _l2ChainId) internal {
l1BridgeAddress = _l1BridgeAddress;
l2ChainId = _l2ChainId;
}
modifier onlyL1Bridge {
require(msg.sender == l1BridgeAddress, "MW: Sender must be the L1 Bridge");
_;
}
modifier rootConfirmation {
isRootConfirmation = true;
_;
isRootConfirmation = false;
}
/**
* @dev Confirm roots that have bonded on L1 and passed the challenge period with no challenge
* @param rootHashes The root hashes to confirm
* @param destinationChainIds The destinationChainId of the roots to confirm
* @param totalAmounts The totalAmount of the roots to confirm
* @param rootCommittedAts The rootCommittedAt of the roots to confirm
*/
function confirmRoots (
bytes32[] calldata rootHashes,
uint256[] calldata destinationChainIds,
uint256[] calldata totalAmounts,
uint256[] calldata rootCommittedAts
) external override rootConfirmation {
IL1Bridge l1Bridge = IL1Bridge(l1BridgeAddress);
require(l1Bridge.getIsBonder(msg.sender), "MW: Sender must be a bonder");
require(rootHashes.length == totalAmounts.length, "MW: rootHashes and totalAmounts must be the same length");
uint256 challengePeriod = l1Bridge.challengePeriod();
for (uint256 i = 0; i < rootHashes.length; i++) {
bool canConfirm = canConfirmRoot(l1Bridge, rootHashes[i], totalAmounts[i], challengePeriod);
require(canConfirm, "MW: Root cannot be confirmed");
l1Bridge.confirmTransferRoot(
l2ChainId,
rootHashes[i],
destinationChainIds[i],
totalAmounts[i],
rootCommittedAts[i]
);
}
}
function canConfirmRoot (IL1Bridge l1Bridge, bytes32 rootHash, uint256 totalAmount, uint256 challengePeriod) public view returns (bool) {
bytes32 transferRootId = l1Bridge.getTransferRootId(rootHash, totalAmount);
(,uint256 createdAt,,uint256 challengeStartTime,,) = l1Bridge.transferBonds(transferRootId);
uint256 timeSinceBondCreation = block.timestamp - createdAt;
if (
createdAt != 0 &&
challengeStartTime == 0 &&
timeSinceBondCreation > challengePeriod
) {
return true;
}
return false;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/optimism/messengers/iOVM_L1CrossDomainMessenger.sol";
import "./MessengerWrapper.sol";
/**
* @dev A MessengerWrapper for Optimism - https://community.optimism.io/docs/
* @notice Deployed on layer-1
*/
contract OptimismMessengerWrapper is MessengerWrapper, Ownable {
iOVM_L1CrossDomainMessenger public immutable l1MessengerAddress;
address public immutable l2BridgeAddress;
uint256 public defaultL2GasLimit;
mapping (bytes4 => uint256) public l2GasLimitForSignature;
constructor(
address _l1BridgeAddress,
address _l2BridgeAddress,
iOVM_L1CrossDomainMessenger _l1MessengerAddress,
uint256 _l2ChainId,
uint256 _defaultL2GasLimit
)
public
MessengerWrapper(_l1BridgeAddress, _l2ChainId)
{
l2BridgeAddress = _l2BridgeAddress;
l1MessengerAddress = _l1MessengerAddress;
defaultL2GasLimit = _defaultL2GasLimit;
}
/**
* @dev Sends a message to the l2BridgeAddress from layer-1
* @param _calldata The data that l2BridgeAddress will be called with
*/
function sendCrossDomainMessage(bytes memory _calldata) public override onlyL1Bridge {
uint256 l2GasLimit = l2GasLimitForCalldata(_calldata);
l1MessengerAddress.sendMessage(
l2BridgeAddress,
_calldata,
uint32(l2GasLimit)
);
}
function verifySender(address l1BridgeCaller, bytes memory /*_data*/) public override {
if (isRootConfirmation) return;
require(l1BridgeCaller == address(l1MessengerAddress), "OVM_MSG_WPR: Caller is not l1MessengerAddress");
// Verify that cross-domain sender is l2BridgeAddress
require(l1MessengerAddress.xDomainMessageSender() == l2BridgeAddress, "OVM_MSG_WPR: Invalid cross-domain sender");
}
function setDefaultL2GasLimit(uint256 _l2GasLimit) external onlyOwner {
defaultL2GasLimit = _l2GasLimit;
}
function setL2GasLimitForSignature(uint256 _l2GasLimit, bytes4 signature) external onlyOwner {
l2GasLimitForSignature[signature] = _l2GasLimit;
}
// Private functions
function l2GasLimitForCalldata(bytes memory _calldata) private view returns (uint256) {
uint256 l2GasLimit;
if (_calldata.length >= 4) {
bytes4 functionSignature = bytes4(toUint32(_calldata, 0));
l2GasLimit = l2GasLimitForSignature[functionSignature];
}
if (l2GasLimit == 0) {
l2GasLimit = defaultL2GasLimit;
}
return l2GasLimit;
}
// source: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
function toUint32(bytes memory _bytes, uint256 _start) private pure returns (uint32) {
require(_bytes.length >= _start + 4, "OVM_MSG_WPR: out of bounds");
uint32 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x4), _start))
}
return tempUint;
}
}
File 6 of 7: Lib_AddressManager
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* External Imports */
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Lib_AddressManager
*/
contract Lib_AddressManager is Ownable {
/**********
* Events *
**********/
event AddressSet(
string indexed _name,
address _newAddress,
address _oldAddress
);
/*************
* Variables *
*************/
mapping (bytes32 => address) private addresses;
/********************
* Public Functions *
********************/
/**
* Changes the address associated with a particular name.
* @param _name String name to associate an address with.
* @param _address Address to associate with the name.
*/
function setAddress(
string memory _name,
address _address
)
external
onlyOwner
{
bytes32 nameHash = _getNameHash(_name);
address oldAddress = addresses[nameHash];
addresses[nameHash] = _address;
emit AddressSet(
_name,
_address,
oldAddress
);
}
/**
* Retrieves the address associated with a given name.
* @param _name Name to retrieve an address for.
* @return Address associated with the given name.
*/
function getAddress(
string memory _name
)
external
view
returns (
address
)
{
return addresses[_getNameHash(_name)];
}
/**********************
* Internal Functions *
**********************/
/**
* Computes the hash of a name.
* @param _name Name to compute a hash for.
* @return Hash of the given name.
*/
function _getNameHash(
string memory _name
)
internal
pure
returns (
bytes32
)
{
return keccak256(abi.encodePacked(_name));
}
}
File 7 of 7: L1CrossDomainMessenger
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Predeploys } from "../libraries/Predeploys.sol";
import { OptimismPortal } from "./OptimismPortal.sol";
import { CrossDomainMessenger } from "../universal/CrossDomainMessenger.sol";
import { Semver } from "../universal/Semver.sol";
/**
* @custom:proxied
* @title L1CrossDomainMessenger
* @notice The L1CrossDomainMessenger is a message passing interface between L1 and L2 responsible
* for sending and receiving data on the L1 side. Users are encouraged to use this
* interface instead of interacting with lower-level contracts directly.
*/
contract L1CrossDomainMessenger is CrossDomainMessenger, Semver {
/**
* @notice Address of the OptimismPortal.
*/
OptimismPortal public immutable PORTAL;
/**
* @custom:semver 1.4.0
*
* @param _portal Address of the OptimismPortal contract on this network.
*/
constructor(OptimismPortal _portal)
Semver(1, 4, 0)
CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER)
{
PORTAL = _portal;
initialize();
}
/**
* @notice Initializer.
*/
function initialize() public initializer {
__CrossDomainMessenger_init();
}
/**
* @inheritdoc CrossDomainMessenger
*/
function _sendMessage(
address _to,
uint64 _gasLimit,
uint256 _value,
bytes memory _data
) internal override {
PORTAL.depositTransaction{ value: _value }(_to, _value, _gasLimit, false, _data);
}
/**
* @inheritdoc CrossDomainMessenger
*/
function _isOtherMessenger() internal view override returns (bool) {
return msg.sender == address(PORTAL) && PORTAL.l2Sender() == OTHER_MESSENGER;
}
/**
* @inheritdoc CrossDomainMessenger
*/
function _isUnsafeTarget(address _target) internal view override returns (bool) {
return _target == address(this) || _target == address(PORTAL);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { Semver } from "../universal/Semver.sol";
import { Types } from "../libraries/Types.sol";
/**
* @custom:proxied
* @title L2OutputOracle
* @notice The L2OutputOracle contains an array of L2 state outputs, where each output is a
* commitment to the state of the L2 chain. Other contracts like the OptimismPortal use
* these outputs to verify information about the state of L2.
*/
contract L2OutputOracle is Initializable, Semver {
/**
* @notice The interval in L2 blocks at which checkpoints must be submitted. Although this is
* immutable, it can safely be modified by upgrading the implementation contract.
*/
uint256 public immutable SUBMISSION_INTERVAL;
/**
* @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
*/
uint256 public immutable L2_BLOCK_TIME;
/**
* @notice The address of the challenger. Can be updated via upgrade.
*/
address public immutable CHALLENGER;
/**
* @notice The address of the proposer. Can be updated via upgrade.
*/
address public immutable PROPOSER;
/**
* @notice Minimum time (in seconds) that must elapse before a withdrawal can be finalized.
*/
uint256 public immutable FINALIZATION_PERIOD_SECONDS;
/**
* @notice The number of the first L2 block recorded in this contract.
*/
uint256 public startingBlockNumber;
/**
* @notice The timestamp of the first L2 block recorded in this contract.
*/
uint256 public startingTimestamp;
/**
* @notice Array of L2 output proposals.
*/
Types.OutputProposal[] internal l2Outputs;
/**
* @notice Emitted when an output is proposed.
*
* @param outputRoot The output root.
* @param l2OutputIndex The index of the output in the l2Outputs array.
* @param l2BlockNumber The L2 block number of the output root.
* @param l1Timestamp The L1 timestamp when proposed.
*/
event OutputProposed(
bytes32 indexed outputRoot,
uint256 indexed l2OutputIndex,
uint256 indexed l2BlockNumber,
uint256 l1Timestamp
);
/**
* @notice Emitted when outputs are deleted.
*
* @param prevNextOutputIndex Next L2 output index before the deletion.
* @param newNextOutputIndex Next L2 output index after the deletion.
*/
event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
/**
* @custom:semver 1.3.0
*
* @param _submissionInterval Interval in blocks at which checkpoints must be submitted.
* @param _l2BlockTime The time per L2 block, in seconds.
* @param _startingBlockNumber The number of the first L2 block.
* @param _startingTimestamp The timestamp of the first L2 block.
* @param _proposer The address of the proposer.
* @param _challenger The address of the challenger.
*/
constructor(
uint256 _submissionInterval,
uint256 _l2BlockTime,
uint256 _startingBlockNumber,
uint256 _startingTimestamp,
address _proposer,
address _challenger,
uint256 _finalizationPeriodSeconds
) Semver(1, 3, 0) {
require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
require(
_submissionInterval > 0,
"L2OutputOracle: submission interval must be greater than 0"
);
SUBMISSION_INTERVAL = _submissionInterval;
L2_BLOCK_TIME = _l2BlockTime;
PROPOSER = _proposer;
CHALLENGER = _challenger;
FINALIZATION_PERIOD_SECONDS = _finalizationPeriodSeconds;
initialize(_startingBlockNumber, _startingTimestamp);
}
/**
* @notice Initializer.
*
* @param _startingBlockNumber Block number for the first recoded L2 block.
* @param _startingTimestamp Timestamp for the first recoded L2 block.
*/
function initialize(uint256 _startingBlockNumber, uint256 _startingTimestamp)
public
initializer
{
require(
_startingTimestamp <= block.timestamp,
"L2OutputOracle: starting L2 timestamp must be less than current time"
);
startingTimestamp = _startingTimestamp;
startingBlockNumber = _startingBlockNumber;
}
/**
* @notice Deletes all output proposals after and including the proposal that corresponds to
* the given output index. Only the challenger address can delete outputs.
*
* @param _l2OutputIndex Index of the first L2 output to be deleted. All outputs after this
* output will also be deleted.
*/
// solhint-disable-next-line ordering
function deleteL2Outputs(uint256 _l2OutputIndex) external {
require(
msg.sender == CHALLENGER,
"L2OutputOracle: only the challenger address can delete outputs"
);
// Make sure we're not *increasing* the length of the array.
require(
_l2OutputIndex < l2Outputs.length,
"L2OutputOracle: cannot delete outputs after the latest output index"
);
// Do not allow deleting any outputs that have already been finalized.
require(
block.timestamp - l2Outputs[_l2OutputIndex].timestamp < FINALIZATION_PERIOD_SECONDS,
"L2OutputOracle: cannot delete outputs that have already been finalized"
);
uint256 prevNextL2OutputIndex = nextOutputIndex();
// Use assembly to delete the array elements because Solidity doesn't allow it.
assembly {
sstore(l2Outputs.slot, _l2OutputIndex)
}
emit OutputsDeleted(prevNextL2OutputIndex, _l2OutputIndex);
}
/**
* @notice Accepts an outputRoot and the timestamp of the corresponding L2 block. The timestamp
* must be equal to the current value returned by `nextTimestamp()` in order to be
* accepted. This function may only be called by the Proposer.
*
* @param _outputRoot The L2 output of the checkpoint block.
* @param _l2BlockNumber The L2 block number that resulted in _outputRoot.
* @param _l1BlockHash A block hash which must be included in the current chain.
* @param _l1BlockNumber The block number with the specified block hash.
*/
function proposeL2Output(
bytes32 _outputRoot,
uint256 _l2BlockNumber,
bytes32 _l1BlockHash,
uint256 _l1BlockNumber
) external payable {
require(
msg.sender == PROPOSER,
"L2OutputOracle: only the proposer address can propose new outputs"
);
require(
_l2BlockNumber == nextBlockNumber(),
"L2OutputOracle: block number must be equal to next expected block number"
);
require(
computeL2Timestamp(_l2BlockNumber) < block.timestamp,
"L2OutputOracle: cannot propose L2 output in the future"
);
require(
_outputRoot != bytes32(0),
"L2OutputOracle: L2 output proposal cannot be the zero hash"
);
if (_l1BlockHash != bytes32(0)) {
// This check allows the proposer to propose an output based on a given L1 block,
// without fear that it will be reorged out.
// It will also revert if the blockheight provided is more than 256 blocks behind the
// chain tip (as the hash will return as zero). This does open the door to a griefing
// attack in which the proposer's submission is censored until the block is no longer
// retrievable, if the proposer is experiencing this attack it can simply leave out the
// blockhash value, and delay submission until it is confident that the L1 block is
// finalized.
require(
blockhash(_l1BlockNumber) == _l1BlockHash,
"L2OutputOracle: block hash does not match the hash at the expected height"
);
}
emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp);
l2Outputs.push(
Types.OutputProposal({
outputRoot: _outputRoot,
timestamp: uint128(block.timestamp),
l2BlockNumber: uint128(_l2BlockNumber)
})
);
}
/**
* @notice Returns an output by index. Exists because Solidity's array access will return a
* tuple instead of a struct.
*
* @param _l2OutputIndex Index of the output to return.
*
* @return The output at the given index.
*/
function getL2Output(uint256 _l2OutputIndex)
external
view
returns (Types.OutputProposal memory)
{
return l2Outputs[_l2OutputIndex];
}
/**
* @notice Returns the index of the L2 output that checkpoints a given L2 block number. Uses a
* binary search to find the first output greater than or equal to the given block.
*
* @param _l2BlockNumber L2 block number to find a checkpoint for.
*
* @return Index of the first checkpoint that commits to the given L2 block number.
*/
function getL2OutputIndexAfter(uint256 _l2BlockNumber) public view returns (uint256) {
// Make sure an output for this block number has actually been proposed.
require(
_l2BlockNumber <= latestBlockNumber(),
"L2OutputOracle: cannot get output for a block that has not been proposed"
);
// Make sure there's at least one output proposed.
require(
l2Outputs.length > 0,
"L2OutputOracle: cannot get output as no outputs have been proposed yet"
);
// Find the output via binary search, guaranteed to exist.
uint256 lo = 0;
uint256 hi = l2Outputs.length;
while (lo < hi) {
uint256 mid = (lo + hi) / 2;
if (l2Outputs[mid].l2BlockNumber < _l2BlockNumber) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
/**
* @notice Returns the L2 output proposal that checkpoints a given L2 block number. Uses a
* binary search to find the first output greater than or equal to the given block.
*
* @param _l2BlockNumber L2 block number to find a checkpoint for.
*
* @return First checkpoint that commits to the given L2 block number.
*/
function getL2OutputAfter(uint256 _l2BlockNumber)
external
view
returns (Types.OutputProposal memory)
{
return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
}
/**
* @notice Returns the number of outputs that have been proposed. Will revert if no outputs
* have been proposed yet.
*
* @return The number of outputs that have been proposed.
*/
function latestOutputIndex() external view returns (uint256) {
return l2Outputs.length - 1;
}
/**
* @notice Returns the index of the next output to be proposed.
*
* @return The index of the next output to be proposed.
*/
function nextOutputIndex() public view returns (uint256) {
return l2Outputs.length;
}
/**
* @notice Returns the block number of the latest submitted L2 output proposal. If no proposals
* been submitted yet then this function will return the starting block number.
*
* @return Latest submitted L2 block number.
*/
function latestBlockNumber() public view returns (uint256) {
return
l2Outputs.length == 0
? startingBlockNumber
: l2Outputs[l2Outputs.length - 1].l2BlockNumber;
}
/**
* @notice Computes the block number of the next L2 block that needs to be checkpointed.
*
* @return Next L2 block number.
*/
function nextBlockNumber() public view returns (uint256) {
return latestBlockNumber() + SUBMISSION_INTERVAL;
}
/**
* @notice Returns the L2 timestamp corresponding to a given L2 block number.
*
* @param _l2BlockNumber The L2 block number of the target block.
*
* @return L2 timestamp of the given block.
*/
function computeL2Timestamp(uint256 _l2BlockNumber) public view returns (uint256) {
return startingTimestamp + ((_l2BlockNumber - startingBlockNumber) * L2_BLOCK_TIME);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { L2OutputOracle } from "./L2OutputOracle.sol";
import { SystemConfig } from "./SystemConfig.sol";
import { Constants } from "../libraries/Constants.sol";
import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol";
import { SecureMerkleTrie } from "../libraries/trie/SecureMerkleTrie.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
import { ResourceMetering } from "./ResourceMetering.sol";
import { Semver } from "../universal/Semver.sol";
/**
* @custom:proxied
* @title OptimismPortal
* @notice The OptimismPortal is a low-level contract responsible for passing messages between L1
* and L2. Messages sent directly to the OptimismPortal have no form of replayability.
* Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
*/
contract OptimismPortal is Initializable, ResourceMetering, Semver {
/**
* @notice Represents a proven withdrawal.
*
* @custom:field outputRoot Root of the L2 output this was proven against.
* @custom:field timestamp Timestamp at whcih the withdrawal was proven.
* @custom:field l2OutputIndex Index of the output this was proven against.
*/
struct ProvenWithdrawal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2OutputIndex;
}
/**
* @notice Version of the deposit event.
*/
uint256 internal constant DEPOSIT_VERSION = 0;
/**
* @notice The L2 gas limit set when eth is deposited using the receive() function.
*/
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/**
* @notice Address of the L2OutputOracle contract.
*/
L2OutputOracle public immutable L2_ORACLE;
/**
* @notice Address of the SystemConfig contract.
*/
SystemConfig public immutable SYSTEM_CONFIG;
/**
* @notice Address that has the ability to pause and unpause withdrawals.
*/
address public immutable GUARDIAN;
/**
* @notice Address of the L2 account which initiated a withdrawal in this transaction. If the
* of this variable is the default L2 sender address, then we are NOT inside of a call
* to finalizeWithdrawalTransaction.
*/
address public l2Sender;
/**
* @notice A list of withdrawal hashes which have been successfully finalized.
*/
mapping(bytes32 => bool) public finalizedWithdrawals;
/**
* @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data.
*/
mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
/**
* @notice Determines if cross domain messaging is paused. When set to true,
* withdrawals are paused. This may be removed in the future.
*/
bool public paused;
/**
* @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event
* are read by the rollup node and used to derive deposit transactions on L2.
*
* @param from Address that triggered the deposit transaction.
* @param to Address that the deposit transaction is directed to.
* @param version Version of this deposit transaction event.
* @param opaqueData ABI encoded deposit data to be parsed off-chain.
*/
event TransactionDeposited(
address indexed from,
address indexed to,
uint256 indexed version,
bytes opaqueData
);
/**
* @notice Emitted when a withdrawal transaction is proven.
*
* @param withdrawalHash Hash of the withdrawal transaction.
*/
event WithdrawalProven(
bytes32 indexed withdrawalHash,
address indexed from,
address indexed to
);
/**
* @notice Emitted when a withdrawal transaction is finalized.
*
* @param withdrawalHash Hash of the withdrawal transaction.
* @param success Whether the withdrawal transaction was successful.
*/
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
/**
* @notice Emitted when the pause is triggered.
*
* @param account Address of the account triggering the pause.
*/
event Paused(address account);
/**
* @notice Emitted when the pause is lifted.
*
* @param account Address of the account triggering the unpause.
*/
event Unpaused(address account);
/**
* @notice Reverts when paused.
*/
modifier whenNotPaused() {
require(paused == false, "OptimismPortal: paused");
_;
}
/**
* @custom:semver 1.6.0
*
* @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals.
* @param _paused Sets the contract's pausability state.
* @param _config Address of the SystemConfig contract.
*/
constructor(
L2OutputOracle _l2Oracle,
address _guardian,
bool _paused,
SystemConfig _config
) Semver(1, 6, 0) {
L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian;
SYSTEM_CONFIG = _config;
initialize(_paused);
}
/**
* @notice Initializer.
*/
function initialize(bool _paused) public initializer {
l2Sender = Constants.DEFAULT_L2_SENDER;
paused = _paused;
__ResourceMetering_init();
}
/**
* @notice Pause deposits and withdrawals.
*/
function pause() external {
require(msg.sender == GUARDIAN, "OptimismPortal: only guardian can pause");
paused = true;
emit Paused(msg.sender);
}
/**
* @notice Unpause deposits and withdrawals.
*/
function unpause() external {
require(msg.sender == GUARDIAN, "OptimismPortal: only guardian can unpause");
paused = false;
emit Unpaused(msg.sender);
}
/**
* @notice Computes the minimum gas limit for a deposit. The minimum gas limit
* linearly increases based on the size of the calldata. This is to prevent
* users from creating L2 resource usage without paying for it. This function
* can be used when interacting with the portal to ensure forwards compatibility.
*
*/
function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
return _byteCount * 16 + 21000;
}
/**
* @notice Accepts value so that users can send ETH directly to this contract and have the
* funds be deposited to their address on L2. This is intended as a convenience
* function for EOAs. Contracts should call the depositTransaction() function directly
* otherwise any deposited funds will be lost due to address aliasing.
*/
// solhint-disable-next-line ordering
receive() external payable {
depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes(""));
}
/**
* @notice Accepts ETH value without triggering a deposit to L2. This function mainly exists
* for the sake of the migration between the legacy Optimism system and Bedrock.
*/
function donateETH() external payable {
// Intentionally empty.
}
/**
* @notice Getter for the resource config. Used internally by the ResourceMetering
* contract. The SystemConfig is the source of truth for the resource config.
*
* @return ResourceMetering.ResourceConfig
*/
function _resourceConfig()
internal
view
override
returns (ResourceMetering.ResourceConfig memory)
{
return SYSTEM_CONFIG.resourceConfig();
}
/**
* @notice Proves a withdrawal transaction.
*
* @param _tx Withdrawal transaction to finalize.
* @param _l2OutputIndex L2 output index to prove against.
* @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
* @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
*/
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
) external whenNotPaused {
// Prevent users from creating a deposit transaction where this address is the message
// sender on L2. Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`.
require(
_tx.target != address(this),
"OptimismPortal: you cannot send messages to the portal contract"
);
// Get the output root and load onto the stack to prevent multiple mloads. This will
// revert if there is no output root for the given block number.
bytes32 outputRoot = L2_ORACLE.getL2Output(_l2OutputIndex).outputRoot;
// Verify that the output root can be generated with the elements in the proof.
require(
outputRoot == Hashing.hashOutputRootProof(_outputRootProof),
"OptimismPortal: invalid output root proof"
);
// Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// We generally want to prevent users from proving the same withdrawal multiple times
// because each successive proof will update the timestamp. A malicious user can take
// advantage of this to prevent other users from finalizing their withdrawal. However,
// since withdrawals are proven before an output root is finalized, we need to allow users
// to re-prove their withdrawal only in the case that the output root for their specified
// output index has been updated.
require(
provenWithdrawal.timestamp == 0 ||
L2_ORACLE.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot !=
provenWithdrawal.outputRoot,
"OptimismPortal: withdrawal hash has already been proven"
);
// Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
// Refer to the Solidity documentation for more information on how storage layouts are
// computed for mappings.
bytes32 storageKey = keccak256(
abi.encode(
withdrawalHash,
uint256(0) // The withdrawals mapping is at the first slot in the layout.
)
);
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
// bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
// be relayed on L1.
require(
SecureMerkleTrie.verifyInclusionProof(
abi.encode(storageKey),
hex"01",
_withdrawalProof,
_outputRootProof.messagePasserStorageRoot
),
"OptimismPortal: invalid withdrawal inclusion proof"
);
// Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
// `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
// proven once unless it is submitted again with a different outputRoot.
provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
outputRoot: outputRoot,
timestamp: uint128(block.timestamp),
l2OutputIndex: uint128(_l2OutputIndex)
});
// Emit a `WithdrawalProven` event.
emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
}
/**
* @notice Finalizes a withdrawal transaction.
*
* @param _tx Withdrawal transaction to finalize.
*/
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx)
external
whenNotPaused
{
// Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard.
require(
l2Sender == Constants.DEFAULT_L2_SENDER,
"OptimismPortal: can only trigger one withdrawal per transaction"
);
// Grab the proven withdrawal from the `provenWithdrawals` map.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(
provenWithdrawal.timestamp != 0,
"OptimismPortal: withdrawal has not been proven yet"
);
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp >= L2_ORACLE.startingTimestamp(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// A proven withdrawal must wait at least the finalization period before it can be
// finalized. This waiting period can elapse in parallel with the waiting period for the
// output the withdrawal was proven against. In effect, this means that the minimum
// withdrawal time is proposal submission time + finalization period.
require(
_isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
"OptimismPortal: proven withdrawal finalization period has not elapsed"
);
// Grab the OutputProposal from the L2OutputOracle, will revert if the output that
// corresponds to the given index has not been proposed yet.
Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(
provenWithdrawal.l2OutputIndex
);
// Check that the output root that was used to prove the withdrawal is the same as the
// current output root for the given output index. An output root may change if it is
// deleted by the challenger address and then re-proposed.
require(
proposal.outputRoot == provenWithdrawal.outputRoot,
"OptimismPortal: output root proven is not the same as current output root"
);
// Check that the output proposal has also been finalized.
require(
_isFinalizationPeriodElapsed(proposal.timestamp),
"OptimismPortal: output proposal finalization period has not elapsed"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(
finalizedWithdrawals[withdrawalHash] == false,
"OptimismPortal: withdrawal has already been finalized"
);
// Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true;
// Set the l2Sender so contracts know who triggered this withdrawal on L2.
l2Sender = _tx.sender;
// Trigger the call to the target contract. We use a custom low level method
// SafeCall.callWithMinGas to ensure two key properties
// 1. Target contracts cannot force this call to run out of gas by returning a very large
// amount of data (and this is OK because we don't care about the returndata here).
// 2. The amount of gas provided to the execution context of the target is at least the
// gas limit specified by the user. If there is not enough gas in the current context
// to accomplish this, `callWithMinGas` will revert.
bool success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
// Reset the l2Sender back to the default value.
l2Sender = Constants.DEFAULT_L2_SENDER;
// All withdrawals are immediately finalized. Replayability can
// be achieved through contracts built on top of this contract
emit WithdrawalFinalized(withdrawalHash, success);
// Reverting here is useful for determining the exact gas cost to successfully execute the
// sub call to the target contract if the minimum gas limit specified by the user would not
// be sufficient to execute the sub call.
if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
revert("OptimismPortal: withdrawal failed");
}
}
/**
* @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in
* deriving deposit transactions. Note that if a deposit is made by a contract, its
* address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider
* using the CrossDomainMessenger contracts for a simpler developer experience.
*
* @param _to Target address on L2.
* @param _value ETH value to send to the recipient.
* @param _gasLimit Minimum L2 gas limit (can be greater than or equal to this value).
* @param _isCreation Whether or not the transaction is a contract creation.
* @param _data Data to trigger the recipient with.
*/
function depositTransaction(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
) public payable metered(_gasLimit) {
// Just to be safe, make sure that people specify address(0) as the target when doing
// contract creations.
if (_isCreation) {
require(
_to == address(0),
"OptimismPortal: must send to address(0) when creating a contract"
);
}
// Prevent depositing transactions that have too small of a gas limit. Users should pay
// more for more resource usage.
require(
_gasLimit >= minimumGasLimit(uint64(_data.length)),
"OptimismPortal: gas limit too small"
);
// Prevent the creation of deposit transactions that have too much calldata. This gives an
// upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
// that the transaction can fit into the p2p network policy of 128kb even though deposit
// transactions are not gossipped over the p2p network.
require(_data.length <= 120_000, "OptimismPortal: data too large");
// Transform the from-address to its alias if the caller is a contract.
address from = msg.sender;
if (msg.sender != tx.origin) {
from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
}
// Compute the opaque data that will be emitted as part of the TransactionDeposited event.
// We use opaque data so that we can update the TransactionDeposited event in the future
// without breaking the current interface.
bytes memory opaqueData = abi.encodePacked(
msg.value,
_value,
_gasLimit,
_isCreation,
_data
);
// Emit a TransactionDeposited event so that the rollup node can derive a deposit
// transaction for this deposit.
emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
}
/**
* @notice Determine if a given output is finalized. Reverts if the call to
* L2_ORACLE.getL2Output reverts. Returns a boolean otherwise.
*
* @param _l2OutputIndex Index of the L2 output to check.
*
* @return Whether or not the output is finalized.
*/
function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) {
return _isFinalizationPeriodElapsed(L2_ORACLE.getL2Output(_l2OutputIndex).timestamp);
}
/**
* @notice Determines whether the finalization period has elapsed w/r/t a given timestamp.
*
* @param _timestamp Timestamp to check.
*
* @return Whether or not the finalization period has elapsed.
*/
function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) {
return block.timestamp > _timestamp + L2_ORACLE.FINALIZATION_PERIOD_SECONDS();
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { Burn } from "../libraries/Burn.sol";
import { Arithmetic } from "../libraries/Arithmetic.sol";
/**
* @custom:upgradeable
* @title ResourceMetering
* @notice ResourceMetering implements an EIP-1559 style resource metering system where pricing
* updates automatically based on current demand.
*/
abstract contract ResourceMetering is Initializable {
/**
* @notice Represents the various parameters that control the way in which resources are
* metered. Corresponds to the EIP-1559 resource metering system.
*
* @custom:field prevBaseFee Base fee from the previous block(s).
* @custom:field prevBoughtGas Amount of gas bought so far in the current block.
* @custom:field prevBlockNum Last block number that the base fee was updated.
*/
struct ResourceParams {
uint128 prevBaseFee;
uint64 prevBoughtGas;
uint64 prevBlockNum;
}
/**
* @notice Represents the configuration for the EIP-1559 based curve for the deposit gas
* market. These values should be set with care as it is possible to set them in
* a way that breaks the deposit gas market. The target resource limit is defined as
* maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a
* single word. There is additional space for additions in the future.
*
* @custom:field maxResourceLimit Represents the maximum amount of deposit gas that
* can be purchased per block.
* @custom:field elasticityMultiplier Determines the target resource limit along with
* the resource limit.
* @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block.
* @custom:field minimumBaseFee The min deposit base fee, it is clamped to this
* value.
* @custom:field systemTxMaxGas The amount of gas supplied to the system
* transaction. This should be set to the same number
* that the op-node sets as the gas limit for the
* system transaction.
* @custom:field maximumBaseFee The max deposit base fee, it is clamped to this
* value.
*/
struct ResourceConfig {
uint32 maxResourceLimit;
uint8 elasticityMultiplier;
uint8 baseFeeMaxChangeDenominator;
uint32 minimumBaseFee;
uint32 systemTxMaxGas;
uint128 maximumBaseFee;
}
/**
* @notice EIP-1559 style gas parameters.
*/
ResourceParams public params;
/**
* @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades.
*/
uint256[48] private __gap;
/**
* @notice Meters access to a function based an amount of a requested resource.
*
* @param _amount Amount of the resource requested.
*/
modifier metered(uint64 _amount) {
// Record initial gas amount so we can refund for it later.
uint256 initialGas = gasleft();
// Run the underlying function.
_;
// Run the metering function.
_metered(_amount, initialGas);
}
/**
* @notice An internal function that holds all of the logic for metering a resource.
*
* @param _amount Amount of the resource requested.
* @param _initialGas The amount of gas before any modifier execution.
*/
function _metered(uint64 _amount, uint256 _initialGas) internal {
// Update block number and base fee if necessary.
uint256 blockDiff = block.number - params.prevBlockNum;
ResourceConfig memory config = _resourceConfig();
int256 targetResourceLimit = int256(uint256(config.maxResourceLimit)) /
int256(uint256(config.elasticityMultiplier));
if (blockDiff > 0) {
// Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
// at which deposits can be created and therefore limit the potential for deposits to
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) /
(targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator)));
// Update base fee by adding the base fee delta and clamp the resulting value between
// min and max.
int256 newBaseFee = Arithmetic.clamp({
_value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
_min: int256(uint256(config.minimumBaseFee)),
_max: int256(uint256(config.maximumBaseFee))
});
// If we skipped more than one block, we also need to account for every empty block.
// Empty block means there was no demand for deposits in that block, so we should
// reflect this lack of demand in the fee.
if (blockDiff > 1) {
// Update the base fee by repeatedly applying the exponent 1-(1/change_denominator)
// blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value
// between min and max.
newBaseFee = Arithmetic.clamp({
_value: Arithmetic.cdexp({
_coefficient: newBaseFee,
_denominator: int256(uint256(config.baseFeeMaxChangeDenominator)),
_exponent: int256(blockDiff - 1)
}),
_min: int256(uint256(config.minimumBaseFee)),
_max: int256(uint256(config.maximumBaseFee))
});
}
// Update new base fee, reset bought gas, and update block number.
params.prevBaseFee = uint128(uint256(newBaseFee));
params.prevBoughtGas = 0;
params.prevBlockNum = uint64(block.number);
}
// Make sure we can actually buy the resource amount requested by the user.
params.prevBoughtGas += _amount;
require(
int256(uint256(params.prevBoughtGas)) <= int256(uint256(config.maxResourceLimit)),
"ResourceMetering: cannot buy more gas than available gas limit"
);
// Determine the amount of ETH to be paid.
uint256 resourceCost = uint256(_amount) * uint256(params.prevBaseFee);
// We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount
// into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid
// division by zero for L1s that don't support 1559 or to avoid excessive gas burns during
// periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei
// during any 1 day period in the last 5 years, so should be fine.
uint256 gasCost = resourceCost / Math.max(block.basefee, 1 gwei);
// Give the user a refund based on the amount of gas they used to do all of the work up to
// this point. Since we're at the end of the modifier, this should be pretty accurate. Acts
// effectively like a dynamic stipend (with a minimum value).
uint256 usedGas = _initialGas - gasleft();
if (gasCost > usedGas) {
Burn.gas(gasCost - usedGas);
}
}
/**
* @notice Virtual function that returns the resource config. Contracts that inherit this
* contract must implement this function.
*
* @return ResourceConfig
*/
function _resourceConfig() internal virtual returns (ResourceConfig memory);
/**
* @notice Sets initial resource parameter values. This function must either be called by the
* initializer function of an upgradeable child contract.
*/
// solhint-disable-next-line func-name-mixedcase
function __ResourceMetering_init() internal onlyInitializing {
params = ResourceParams({
prevBaseFee: 1 gwei,
prevBoughtGas: 0,
prevBlockNum: uint64(block.number)
});
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {
OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Semver } from "../universal/Semver.sol";
import { ResourceMetering } from "./ResourceMetering.sol";
/**
* @title SystemConfig
* @notice The SystemConfig contract is used to manage configuration of an Optimism network. All
* configuration is stored on L1 and picked up by L2 as part of the derviation of the L2
* chain.
*/
contract SystemConfig is OwnableUpgradeable, Semver {
/**
* @notice Enum representing different types of updates.
*
* @custom:value BATCHER Represents an update to the batcher hash.
* @custom:value GAS_CONFIG Represents an update to txn fee config on L2.
* @custom:value GAS_LIMIT Represents an update to gas limit on L2.
* @custom:value UNSAFE_BLOCK_SIGNER Represents an update to the signer key for unsafe
* block distrubution.
*/
enum UpdateType {
BATCHER,
GAS_CONFIG,
GAS_LIMIT,
UNSAFE_BLOCK_SIGNER
}
/**
* @notice Version identifier, used for upgrades.
*/
uint256 public constant VERSION = 0;
/**
* @notice Storage slot that the unsafe block signer is stored at. Storing it at this
* deterministic storage slot allows for decoupling the storage layout from the way
* that `solc` lays out storage. The `op-node` uses a storage proof to fetch this value.
*/
bytes32 public constant UNSAFE_BLOCK_SIGNER_SLOT = keccak256("systemconfig.unsafeblocksigner");
/**
* @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation.
*/
uint256 public overhead;
/**
* @notice Dynamic L2 gas overhead. Used as part of the L2 fee calculation.
*/
uint256 public scalar;
/**
* @notice Identifier for the batcher. For version 1 of this configuration, this is represented
* as an address left-padded with zeros to 32 bytes.
*/
bytes32 public batcherHash;
/**
* @notice L2 block gas limit.
*/
uint64 public gasLimit;
/**
* @notice The configuration for the deposit fee market. Used by the OptimismPortal
* to meter the cost of buying L2 gas on L1. Set as internal and wrapped with a getter
* so that the struct is returned instead of a tuple.
*/
ResourceMetering.ResourceConfig internal _resourceConfig;
/**
* @notice Emitted when configuration is updated
*
* @param version SystemConfig version.
* @param updateType Type of update.
* @param data Encoded update data.
*/
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
/**
* @custom:semver 1.3.0
*
* @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value.
* @param _scalar Initial scalar value.
* @param _batcherHash Initial batcher hash.
* @param _gasLimit Initial gas limit.
* @param _unsafeBlockSigner Initial unsafe block signer address.
* @param _config Initial resource config.
*/
constructor(
address _owner,
uint256 _overhead,
uint256 _scalar,
bytes32 _batcherHash,
uint64 _gasLimit,
address _unsafeBlockSigner,
ResourceMetering.ResourceConfig memory _config
) Semver(1, 3, 0) {
initialize({
_owner: _owner,
_overhead: _overhead,
_scalar: _scalar,
_batcherHash: _batcherHash,
_gasLimit: _gasLimit,
_unsafeBlockSigner: _unsafeBlockSigner,
_config: _config
});
}
/**
* @notice Initializer. The resource config must be set before the
* require check.
*
* @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value.
* @param _scalar Initial scalar value.
* @param _batcherHash Initial batcher hash.
* @param _gasLimit Initial gas limit.
* @param _unsafeBlockSigner Initial unsafe block signer address.
* @param _config Initial ResourceConfig.
*/
function initialize(
address _owner,
uint256 _overhead,
uint256 _scalar,
bytes32 _batcherHash,
uint64 _gasLimit,
address _unsafeBlockSigner,
ResourceMetering.ResourceConfig memory _config
) public initializer {
__Ownable_init();
transferOwnership(_owner);
overhead = _overhead;
scalar = _scalar;
batcherHash = _batcherHash;
gasLimit = _gasLimit;
_setUnsafeBlockSigner(_unsafeBlockSigner);
_setResourceConfig(_config);
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
}
/**
* @notice Returns the minimum L2 gas limit that can be safely set for the system to
* operate. The L2 gas limit must be larger than or equal to the amount of
* gas that is allocated for deposits per block plus the amount of gas that
* is allocated for the system transaction.
* This function is used to determine if changes to parameters are safe.
*
* @return uint64
*/
function minimumGasLimit() public view returns (uint64) {
return uint64(_resourceConfig.maxResourceLimit) + uint64(_resourceConfig.systemTxMaxGas);
}
/**
* @notice High level getter for the unsafe block signer address. Unsafe blocks can be
* propagated across the p2p network if they are signed by the key corresponding to
* this address.
*
* @return Address of the unsafe block signer.
*/
// solhint-disable-next-line ordering
function unsafeBlockSigner() external view returns (address) {
address addr;
bytes32 slot = UNSAFE_BLOCK_SIGNER_SLOT;
assembly {
addr := sload(slot)
}
return addr;
}
/**
* @notice Updates the unsafe block signer address.
*
* @param _unsafeBlockSigner New unsafe block signer address.
*/
function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner {
_setUnsafeBlockSigner(_unsafeBlockSigner);
bytes memory data = abi.encode(_unsafeBlockSigner);
emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data);
}
/**
* @notice Updates the batcher hash.
*
* @param _batcherHash New batcher hash.
*/
function setBatcherHash(bytes32 _batcherHash) external onlyOwner {
batcherHash = _batcherHash;
bytes memory data = abi.encode(_batcherHash);
emit ConfigUpdate(VERSION, UpdateType.BATCHER, data);
}
/**
* @notice Updates gas config.
*
* @param _overhead New overhead value.
* @param _scalar New scalar value.
*/
function setGasConfig(uint256 _overhead, uint256 _scalar) external onlyOwner {
overhead = _overhead;
scalar = _scalar;
bytes memory data = abi.encode(_overhead, _scalar);
emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data);
}
/**
* @notice Updates the L2 gas limit.
*
* @param _gasLimit New gas limit.
*/
function setGasLimit(uint64 _gasLimit) external onlyOwner {
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
gasLimit = _gasLimit;
bytes memory data = abi.encode(_gasLimit);
emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data);
}
/**
* @notice Low level setter for the unsafe block signer address. This function exists to
* deduplicate code around storing the unsafeBlockSigner address in storage.
*
* @param _unsafeBlockSigner New unsafeBlockSigner value.
*/
function _setUnsafeBlockSigner(address _unsafeBlockSigner) internal {
bytes32 slot = UNSAFE_BLOCK_SIGNER_SLOT;
assembly {
sstore(slot, _unsafeBlockSigner)
}
}
/**
* @notice A getter for the resource config. Ensures that the struct is
* returned instead of a tuple.
*
* @return ResourceConfig
*/
function resourceConfig() external view returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig;
}
/**
* @notice An external setter for the resource config. In the future, this
* method may emit an event that the `op-node` picks up for when the
* resource config is changed.
*
* @param _config The new resource config values.
*/
function setResourceConfig(ResourceMetering.ResourceConfig memory _config) external onlyOwner {
_setResourceConfig(_config);
}
/**
* @notice An internal setter for the resource config. Ensures that the
* config is sane before storing it by checking for invariants.
*
* @param _config The new resource config.
*/
function _setResourceConfig(ResourceMetering.ResourceConfig memory _config) internal {
// Min base fee must be less than or equal to max base fee.
require(
_config.minimumBaseFee <= _config.maximumBaseFee,
"SystemConfig: min base fee must be less than max base"
);
// Base fee change denominator must be greater than 1.
require(
_config.baseFeeMaxChangeDenominator > 1,
"SystemConfig: denominator must be larger than 1"
);
// Max resource limit plus system tx gas must be less than or equal to the L2 gas limit.
// The gas limit must be increased before these values can be increased.
require(
_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit,
"SystemConfig: gas limit too low"
);
// Elasticity multiplier must be greater than 0.
require(
_config.elasticityMultiplier > 0,
"SystemConfig: elasticity multiplier cannot be 0"
);
// No precision loss when computing target resource limit.
require(
((_config.maxResourceLimit / _config.elasticityMultiplier) *
_config.elasticityMultiplier) == _config.maxResourceLimit,
"SystemConfig: precision loss with target resource limit"
);
_resourceConfig = _config;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol";
import { FixedPointMathLib } from "@rari-capital/solmate/src/utils/FixedPointMathLib.sol";
/**
* @title Arithmetic
* @notice Even more math than before.
*/
library Arithmetic {
/**
* @notice Clamps a value between a minimum and maximum.
*
* @param _value The value to clamp.
* @param _min The minimum value.
* @param _max The maximum value.
*
* @return The clamped value.
*/
function clamp(
int256 _value,
int256 _min,
int256 _max
) internal pure returns (int256) {
return SignedMath.min(SignedMath.max(_value, _min), _max);
}
/**
* @notice (c)oefficient (d)enominator (exp)onentiation function.
* Returns the result of: c * (1 - 1/d)^exp.
*
* @param _coefficient Coefficient of the function.
* @param _denominator Fractional denominator.
* @param _exponent Power function exponent.
*
* @return Result of c * (1 - 1/d)^exp.
*/
function cdexp(
int256 _coefficient,
int256 _denominator,
int256 _exponent
) internal pure returns (int256) {
return
(_coefficient *
(FixedPointMathLib.powWad(1e18 - (1e18 / _denominator), _exponent * 1e18))) / 1e18;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/**
* @title Burn
* @notice Utilities for burning stuff.
*/
library Burn {
/**
* Burns a given amount of ETH.
*
* @param _amount Amount of ETH to burn.
*/
function eth(uint256 _amount) internal {
new Burner{ value: _amount }();
}
/**
* Burns a given amount of gas.
*
* @param _amount Amount of gas to burn.
*/
function gas(uint256 _amount) internal view {
uint256 i = 0;
uint256 initialGas = gasleft();
while (initialGas - gasleft() < _amount) {
++i;
}
}
}
/**
* @title Burner
* @notice Burner self-destructs on creation and sends all ETH to itself, removing all ETH given to
* the contract from the circulating supply. Self-destructing is the only way to remove ETH
* from the circulating supply.
*/
contract Burner {
constructor() payable {
selfdestruct(payable(address(this)));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Bytes
* @notice Bytes is a library for manipulating byte arrays.
*/
library Bytes {
/**
* @custom:attribution https://github.com/GNSPS/solidity-bytes-utils
* @notice Slices a byte array with a given starting index and length. Returns a new byte array
* as opposed to a pointer to the original array. Will throw if trying to slice more
* bytes than exist in the array.
*
* @param _bytes Byte array to slice.
* @param _start Starting index of the slice.
* @param _length Length of the slice.
*
* @return Slice of the input byte array.
*/
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
) internal pure returns (bytes memory) {
unchecked {
require(_length + 31 >= _length, "slice_overflow");
require(_start + _length >= _start, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");
}
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
/**
* @notice Slices a byte array with a given starting index up to the end of the original byte
* array. Returns a new array rathern than a pointer to the original.
*
* @param _bytes Byte array to slice.
* @param _start Starting index of the slice.
*
* @return Slice of the input byte array.
*/
function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) {
if (_start >= _bytes.length) {
return bytes("");
}
return slice(_bytes, _start, _bytes.length - _start);
}
/**
* @notice Converts a byte array into a nibble array by splitting each byte into two nibbles.
* Resulting nibble array will be exactly twice as long as the input byte array.
*
* @param _bytes Input byte array to convert.
*
* @return Resulting nibble array.
*/
function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
uint256 bytesLength = _bytes.length;
bytes memory nibbles = new bytes(bytesLength * 2);
bytes1 b;
for (uint256 i = 0; i < bytesLength; ) {
b = _bytes[i];
nibbles[i * 2] = b >> 4;
nibbles[i * 2 + 1] = b & 0x0f;
unchecked {
++i;
}
}
return nibbles;
}
/**
* @notice Compares two byte arrays by comparing their keccak256 hashes.
*
* @param _bytes First byte array to compare.
* @param _other Second byte array to compare.
*
* @return True if the two byte arrays are equal, false otherwise.
*/
function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) {
return keccak256(_bytes) == keccak256(_other);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { ResourceMetering } from "../L1/ResourceMetering.sol";
/**
* @title Constants
* @notice Constants is a library for storing constants. Simple! Don't put everything in here, just
* the stuff used in multiple contracts. Constants that only apply to a single contract
* should be defined in that contract instead.
*/
library Constants {
/**
* @notice Special address to be used as the tx origin for gas estimation calls in the
* OptimismPortal and CrossDomainMessenger calls. You only need to use this address if
* the minimum gas limit specified by the user is not actually enough to execute the
* given message and you're attempting to estimate the actual necessary gas limit. We
* use address(1) because it's the ecrecover precompile and therefore guaranteed to
* never have any code on any EVM chain.
*/
address internal constant ESTIMATION_ADDRESS = address(1);
/**
* @notice Value used for the L2 sender storage slot in both the OptimismPortal and the
* CrossDomainMessenger contracts before an actual sender is set. This value is
* non-zero to reduce the gas cost of message passing transactions.
*/
address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD;
/**
* @notice Returns the default values for the ResourceConfig. These are the recommended values
* for a production network.
*/
function DEFAULT_RESOURCE_CONFIG()
internal
pure
returns (ResourceMetering.ResourceConfig memory)
{
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Types } from "./Types.sol";
import { Hashing } from "./Hashing.sol";
import { RLPWriter } from "./rlp/RLPWriter.sol";
/**
* @title Encoding
* @notice Encoding handles Optimism's various different encoding schemes.
*/
library Encoding {
/**
* @notice RLP encodes the L2 transaction that would be generated when a given deposit is sent
* to the L2 system. Useful for searching for a deposit in the L2 system. The
* transaction is prefixed with 0x7e to identify its EIP-2718 type.
*
* @param _tx User deposit transaction to encode.
*
* @return RLP encoded L2 deposit transaction.
*/
function encodeDepositTransaction(Types.UserDepositTransaction memory _tx)
internal
pure
returns (bytes memory)
{
bytes32 source = Hashing.hashDepositSource(_tx.l1BlockHash, _tx.logIndex);
bytes[] memory raw = new bytes[](8);
raw[0] = RLPWriter.writeBytes(abi.encodePacked(source));
raw[1] = RLPWriter.writeAddress(_tx.from);
raw[2] = _tx.isCreation ? RLPWriter.writeBytes("") : RLPWriter.writeAddress(_tx.to);
raw[3] = RLPWriter.writeUint(_tx.mint);
raw[4] = RLPWriter.writeUint(_tx.value);
raw[5] = RLPWriter.writeUint(uint256(_tx.gasLimit));
raw[6] = RLPWriter.writeBool(false);
raw[7] = RLPWriter.writeBytes(_tx.data);
return abi.encodePacked(uint8(0x7e), RLPWriter.writeList(raw));
}
/**
* @notice Encodes the cross domain message based on the version that is encoded into the
* message nonce.
*
* @param _nonce Message nonce with version encoded into the first two bytes.
* @param _sender Address of the sender of the message.
* @param _target Address of the target of the message.
* @param _value ETH value to send to the target.
* @param _gasLimit Gas limit to use for the message.
* @param _data Data to send with the message.
*
* @return Encoded cross domain message.
*/
function encodeCrossDomainMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) internal pure returns (bytes memory) {
(, uint16 version) = decodeVersionedNonce(_nonce);
if (version == 0) {
return encodeCrossDomainMessageV0(_target, _sender, _data, _nonce);
} else if (version == 1) {
return encodeCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data);
} else {
revert("Encoding: unknown cross domain message version");
}
}
/**
* @notice Encodes a cross domain message based on the V0 (legacy) encoding.
*
* @param _target Address of the target of the message.
* @param _sender Address of the sender of the message.
* @param _data Data to send with the message.
* @param _nonce Message nonce.
*
* @return Encoded cross domain message.
*/
function encodeCrossDomainMessageV0(
address _target,
address _sender,
bytes memory _data,
uint256 _nonce
) internal pure returns (bytes memory) {
return
abi.encodeWithSignature(
"relayMessage(address,address,bytes,uint256)",
_target,
_sender,
_data,
_nonce
);
}
/**
* @notice Encodes a cross domain message based on the V1 (current) encoding.
*
* @param _nonce Message nonce.
* @param _sender Address of the sender of the message.
* @param _target Address of the target of the message.
* @param _value ETH value to send to the target.
* @param _gasLimit Gas limit to use for the message.
* @param _data Data to send with the message.
*
* @return Encoded cross domain message.
*/
function encodeCrossDomainMessageV1(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) internal pure returns (bytes memory) {
return
abi.encodeWithSignature(
"relayMessage(uint256,address,address,uint256,uint256,bytes)",
_nonce,
_sender,
_target,
_value,
_gasLimit,
_data
);
}
/**
* @notice Adds a version number into the first two bytes of a message nonce.
*
* @param _nonce Message nonce to encode into.
* @param _version Version number to encode into the message nonce.
*
* @return Message nonce with version encoded into the first two bytes.
*/
function encodeVersionedNonce(uint240 _nonce, uint16 _version) internal pure returns (uint256) {
uint256 nonce;
assembly {
nonce := or(shl(240, _version), _nonce)
}
return nonce;
}
/**
* @notice Pulls the version out of a version-encoded nonce.
*
* @param _nonce Message nonce with version encoded into the first two bytes.
*
* @return Nonce without encoded version.
* @return Version of the message.
*/
function decodeVersionedNonce(uint256 _nonce) internal pure returns (uint240, uint16) {
uint240 nonce;
uint16 version;
assembly {
nonce := and(_nonce, 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
version := shr(240, _nonce)
}
return (nonce, version);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Types } from "./Types.sol";
import { Encoding } from "./Encoding.sol";
/**
* @title Hashing
* @notice Hashing handles Optimism's various different hashing schemes.
*/
library Hashing {
/**
* @notice Computes the hash of the RLP encoded L2 transaction that would be generated when a
* given deposit is sent to the L2 system. Useful for searching for a deposit in the L2
* system.
*
* @param _tx User deposit transaction to hash.
*
* @return Hash of the RLP encoded L2 deposit transaction.
*/
function hashDepositTransaction(Types.UserDepositTransaction memory _tx)
internal
pure
returns (bytes32)
{
return keccak256(Encoding.encodeDepositTransaction(_tx));
}
/**
* @notice Computes the deposit transaction's "source hash", a value that guarantees the hash
* of the L2 transaction that corresponds to a deposit is unique and is
* deterministically generated from L1 transaction data.
*
* @param _l1BlockHash Hash of the L1 block where the deposit was included.
* @param _logIndex The index of the log that created the deposit transaction.
*
* @return Hash of the deposit transaction's "source hash".
*/
function hashDepositSource(bytes32 _l1BlockHash, uint256 _logIndex)
internal
pure
returns (bytes32)
{
bytes32 depositId = keccak256(abi.encode(_l1BlockHash, _logIndex));
return keccak256(abi.encode(bytes32(0), depositId));
}
/**
* @notice Hashes the cross domain message based on the version that is encoded into the
* message nonce.
*
* @param _nonce Message nonce with version encoded into the first two bytes.
* @param _sender Address of the sender of the message.
* @param _target Address of the target of the message.
* @param _value ETH value to send to the target.
* @param _gasLimit Gas limit to use for the message.
* @param _data Data to send with the message.
*
* @return Hashed cross domain message.
*/
function hashCrossDomainMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) internal pure returns (bytes32) {
(, uint16 version) = Encoding.decodeVersionedNonce(_nonce);
if (version == 0) {
return hashCrossDomainMessageV0(_target, _sender, _data, _nonce);
} else if (version == 1) {
return hashCrossDomainMessageV1(_nonce, _sender, _target, _value, _gasLimit, _data);
} else {
revert("Hashing: unknown cross domain message version");
}
}
/**
* @notice Hashes a cross domain message based on the V0 (legacy) encoding.
*
* @param _target Address of the target of the message.
* @param _sender Address of the sender of the message.
* @param _data Data to send with the message.
* @param _nonce Message nonce.
*
* @return Hashed cross domain message.
*/
function hashCrossDomainMessageV0(
address _target,
address _sender,
bytes memory _data,
uint256 _nonce
) internal pure returns (bytes32) {
return keccak256(Encoding.encodeCrossDomainMessageV0(_target, _sender, _data, _nonce));
}
/**
* @notice Hashes a cross domain message based on the V1 (current) encoding.
*
* @param _nonce Message nonce.
* @param _sender Address of the sender of the message.
* @param _target Address of the target of the message.
* @param _value ETH value to send to the target.
* @param _gasLimit Gas limit to use for the message.
* @param _data Data to send with the message.
*
* @return Hashed cross domain message.
*/
function hashCrossDomainMessageV1(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) internal pure returns (bytes32) {
return
keccak256(
Encoding.encodeCrossDomainMessageV1(
_nonce,
_sender,
_target,
_value,
_gasLimit,
_data
)
);
}
/**
* @notice Derives the withdrawal hash according to the encoding in the L2 Withdrawer contract
*
* @param _tx Withdrawal transaction to hash.
*
* @return Hashed withdrawal transaction.
*/
function hashWithdrawal(Types.WithdrawalTransaction memory _tx)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encode(_tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data)
);
}
/**
* @notice Hashes the various elements of an output root proof into an output root hash which
* can be used to check if the proof is valid.
*
* @param _outputRootProof Output root proof which should hash to an output root.
*
* @return Hashed output root proof.
*/
function hashOutputRootProof(Types.OutputRootProof memory _outputRootProof)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
_outputRootProof.version,
_outputRootProof.stateRoot,
_outputRootProof.messagePasserStorageRoot,
_outputRootProof.latestBlockhash
)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Predeploys
* @notice Contains constant addresses for contracts that are pre-deployed to the L2 system.
*/
library Predeploys {
/**
* @notice Address of the L2ToL1MessagePasser predeploy.
*/
address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016;
/**
* @notice Address of the L2CrossDomainMessenger predeploy.
*/
address internal constant L2_CROSS_DOMAIN_MESSENGER =
0x4200000000000000000000000000000000000007;
/**
* @notice Address of the L2StandardBridge predeploy.
*/
address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010;
/**
* @notice Address of the L2ERC721Bridge predeploy.
*/
address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014;
/**
* @notice Address of the SequencerFeeWallet predeploy.
*/
address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011;
/**
* @notice Address of the OptimismMintableERC20Factory predeploy.
*/
address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY =
0x4200000000000000000000000000000000000012;
/**
* @notice Address of the OptimismMintableERC721Factory predeploy.
*/
address internal constant OPTIMISM_MINTABLE_ERC721_FACTORY =
0x4200000000000000000000000000000000000017;
/**
* @notice Address of the L1Block predeploy.
*/
address internal constant L1_BLOCK_ATTRIBUTES = 0x4200000000000000000000000000000000000015;
/**
* @notice Address of the GasPriceOracle predeploy. Includes fee information
* and helpers for computing the L1 portion of the transaction fee.
*/
address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F;
/**
* @custom:legacy
* @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger
* or access tx.origin (or msg.sender) in a L1 to L2 transaction instead.
*/
address internal constant L1_MESSAGE_SENDER = 0x4200000000000000000000000000000000000001;
/**
* @custom:legacy
* @notice Address of the DeployerWhitelist predeploy. No longer active.
*/
address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002;
/**
* @custom:legacy
* @notice Address of the LegacyERC20ETH predeploy. Deprecated. Balances are migrated to the
* state trie as of the Bedrock upgrade. Contract has been locked and write functions
* can no longer be accessed.
*/
address internal constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000;
/**
* @custom:legacy
* @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy
* instead, which exposes more information about the L1 state.
*/
address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013;
/**
* @custom:legacy
* @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated
* L2ToL1MessagePasser contract instead.
*/
address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000;
/**
* @notice Address of the ProxyAdmin predeploy.
*/
address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018;
/**
* @notice Address of the BaseFeeVault predeploy.
*/
address internal constant BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019;
/**
* @notice Address of the L1FeeVault predeploy.
*/
address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A;
/**
* @notice Address of the GovernanceToken predeploy.
*/
address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/**
* @title SafeCall
* @notice Perform low level safe calls
*/
library SafeCall {
/**
* @notice Performs a low level call without copying any returndata.
* @dev Passes no calldata to the call context.
*
* @param _target Address to call
* @param _gas Amount of gas to pass to the call
* @param _value Amount of value to pass to the call
*/
function send(
address _target,
uint256 _gas,
uint256 _value
) internal returns (bool) {
bool _success;
assembly {
_success := call(
_gas, // gas
_target, // recipient
_value, // ether value
0, // inloc
0, // inlen
0, // outloc
0 // outlen
)
}
return _success;
}
/**
* @notice Perform a low level call without copying any returndata
*
* @param _target Address to call
* @param _gas Amount of gas to pass to the call
* @param _value Amount of value to pass to the call
* @param _calldata Calldata to pass to the call
*/
function call(
address _target,
uint256 _gas,
uint256 _value,
bytes memory _calldata
) internal returns (bool) {
bool _success;
assembly {
_success := call(
_gas, // gas
_target, // recipient
_value, // ether value
add(_calldata, 32), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
}
return _success;
}
/**
* @notice Helper function to determine if there is sufficient gas remaining within the context
* to guarantee that the minimum gas requirement for a call will be met as well as
* optionally reserving a specified amount of gas for after the call has concluded.
* @param _minGas The minimum amount of gas that may be passed to the target context.
* @param _reservedGas Optional amount of gas to reserve for the caller after the execution
* of the target context.
* @return `true` if there is enough gas remaining to safely supply `_minGas` to the target
* context as well as reserve `_reservedGas` for the caller after the execution of
* the target context.
* @dev !!!!! FOOTGUN ALERT !!!!!
* 1.) The 40_000 base buffer is to account for the worst case of the dynamic cost of the
* `CALL` opcode's `address_access_cost`, `positive_value_cost`, and
* `value_to_empty_account_cost` factors with an added buffer of 5,700 gas. It is
* still possible to self-rekt by initiating a withdrawal with a minimum gas limit
* that does not account for the `memory_expansion_cost` & `code_execution_cost`
* factors of the dynamic cost of the `CALL` opcode.
* 2.) This function should *directly* precede the external call if possible. There is an
* added buffer to account for gas consumed between this check and the call, but it
* is only 5,700 gas.
* 3.) Because EIP-150 ensures that a maximum of 63/64ths of the remaining gas in the call
* frame may be passed to a subcontext, we need to ensure that the gas will not be
* truncated.
* 4.) Use wisely. This function is not a silver bullet.
*/
function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) {
bool _hasMinGas;
assembly {
// Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas)
_hasMinGas := iszero(
lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63)))
)
}
return _hasMinGas;
}
/**
* @notice Perform a low level call without copying any returndata. This function
* will revert if the call cannot be performed with the specified minimum
* gas.
*
* @param _target Address to call
* @param _minGas The minimum amount of gas that may be passed to the call
* @param _value Amount of value to pass to the call
* @param _calldata Calldata to pass to the call
*/
function callWithMinGas(
address _target,
uint256 _minGas,
uint256 _value,
bytes memory _calldata
) internal returns (bool) {
bool _success;
bool _hasMinGas = hasMinGas(_minGas, 0);
assembly {
// Assertion: gasleft() >= (_minGas * 64) / 63 + 40_000
if iszero(_hasMinGas) {
// Store the "Error(string)" selector in scratch space.
mstore(0, 0x08c379a0)
// Store the pointer to the string length in scratch space.
mstore(32, 32)
// Store the string.
//
// SAFETY:
// - We pad the beginning of the string with two zero bytes as well as the
// length (24) to ensure that we override the free memory pointer at offset
// 0x40. This is necessary because the free memory pointer is likely to
// be greater than 1 byte when this function is called, but it is incredibly
// unlikely that it will be greater than 3 bytes. As for the data within
// 0x60, it is ensured that it is 0 due to 0x60 being the zero offset.
// - It's fine to clobber the free memory pointer, we're reverting.
mstore(88, 0x0000185361666543616c6c3a204e6f7420656e6f75676820676173)
// Revert with 'Error("SafeCall: Not enough gas")'
revert(28, 100)
}
// The call will be supplied at least ((_minGas * 64) / 63) gas due to the
// above assertion. This ensures that, in all circumstances (except for when the
// `_minGas` does not account for the `memory_expansion_cost` and `code_execution_cost`
// factors of the dynamic cost of the `CALL` opcode), the call will receive at least
// the minimum amount of gas specified.
_success := call(
gas(), // gas
_target, // recipient
_value, // ether value
add(_calldata, 32), // inloc
mload(_calldata), // inlen
0x00, // outloc
0x00 // outlen
)
}
return _success;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Types
* @notice Contains various types used throughout the Optimism contract system.
*/
library Types {
/**
* @notice OutputProposal represents a commitment to the L2 state. The timestamp is the L1
* timestamp that the output root is posted. This timestamp is used to verify that the
* finalization period has passed since the output root was submitted.
*
* @custom:field outputRoot Hash of the L2 output.
* @custom:field timestamp Timestamp of the L1 block that the output root was submitted in.
* @custom:field l2BlockNumber L2 block number that the output corresponds to.
*/
struct OutputProposal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2BlockNumber;
}
/**
* @notice Struct representing the elements that are hashed together to generate an output root
* which itself represents a snapshot of the L2 state.
*
* @custom:field version Version of the output root.
* @custom:field stateRoot Root of the state trie at the block of this output.
* @custom:field messagePasserStorageRoot Root of the message passer storage trie.
* @custom:field latestBlockhash Hash of the block this output was generated from.
*/
struct OutputRootProof {
bytes32 version;
bytes32 stateRoot;
bytes32 messagePasserStorageRoot;
bytes32 latestBlockhash;
}
/**
* @notice Struct representing a deposit transaction (L1 => L2 transaction) created by an end
* user (as opposed to a system deposit transaction generated by the system).
*
* @custom:field from Address of the sender of the transaction.
* @custom:field to Address of the recipient of the transaction.
* @custom:field isCreation True if the transaction is a contract creation.
* @custom:field value Value to send to the recipient.
* @custom:field mint Amount of ETH to mint.
* @custom:field gasLimit Gas limit of the transaction.
* @custom:field data Data of the transaction.
* @custom:field l1BlockHash Hash of the block the transaction was submitted in.
* @custom:field logIndex Index of the log in the block the transaction was submitted in.
*/
struct UserDepositTransaction {
address from;
address to;
bool isCreation;
uint256 value;
uint256 mint;
uint64 gasLimit;
bytes data;
bytes32 l1BlockHash;
uint256 logIndex;
}
/**
* @notice Struct representing a withdrawal transaction.
*
* @custom:field nonce Nonce of the withdrawal transaction
* @custom:field sender Address of the sender of the transaction.
* @custom:field target Address of the recipient of the transaction.
* @custom:field value Value to send to the recipient.
* @custom:field gasLimit Gas limit of the transaction.
* @custom:field data Data of the transaction.
*/
struct WithdrawalTransaction {
uint256 nonce;
address sender;
address target;
uint256 value;
uint256 gasLimit;
bytes data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @custom:attribution https://github.com/hamdiallam/Solidity-RLP
* @title RLPReader
* @notice RLPReader is a library for parsing RLP-encoded byte arrays into Solidity types. Adapted
* from Solidity-RLP (https://github.com/hamdiallam/Solidity-RLP) by Hamdi Allam with
* various tweaks to improve readability.
*/
library RLPReader {
/**
* Custom pointer type to avoid confusion between pointers and uint256s.
*/
type MemoryPointer is uint256;
/**
* @notice RLP item types.
*
* @custom:value DATA_ITEM Represents an RLP data item (NOT a list).
* @custom:value LIST_ITEM Represents an RLP list item.
*/
enum RLPItemType {
DATA_ITEM,
LIST_ITEM
}
/**
* @notice Struct representing an RLP item.
*
* @custom:field length Length of the RLP item.
* @custom:field ptr Pointer to the RLP item in memory.
*/
struct RLPItem {
uint256 length;
MemoryPointer ptr;
}
/**
* @notice Max list length that this library will accept.
*/
uint256 internal constant MAX_LIST_LENGTH = 32;
/**
* @notice Converts bytes to a reference to memory position and length.
*
* @param _in Input bytes to convert.
*
* @return Output memory reference.
*/
function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory) {
// Empty arrays are not RLP items.
require(
_in.length > 0,
"RLPReader: length of an RLP item must be greater than zero to be decodable"
);
MemoryPointer ptr;
assembly {
ptr := add(_in, 32)
}
return RLPItem({ length: _in.length, ptr: ptr });
}
/**
* @notice Reads an RLP list value into a list of RLP items.
*
* @param _in RLP list value.
*
* @return Decoded RLP list items.
*/
function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory) {
(uint256 listOffset, uint256 listLength, RLPItemType itemType) = _decodeLength(_in);
require(
itemType == RLPItemType.LIST_ITEM,
"RLPReader: decoded item type for list is not a list item"
);
require(
listOffset + listLength == _in.length,
"RLPReader: list item has an invalid data remainder"
);
// Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by
// writing to the length. Since we can't know the number of RLP items without looping over
// the entire input, we'd have to loop twice to accurately size this array. It's easier to
// simply set a reasonable maximum list length and decrease the size before we finish.
RLPItem[] memory out = new RLPItem[](MAX_LIST_LENGTH);
uint256 itemCount = 0;
uint256 offset = listOffset;
while (offset < _in.length) {
(uint256 itemOffset, uint256 itemLength, ) = _decodeLength(
RLPItem({
length: _in.length - offset,
ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset)
})
);
// We don't need to check itemCount < out.length explicitly because Solidity already
// handles this check on our behalf, we'd just be wasting gas.
out[itemCount] = RLPItem({
length: itemLength + itemOffset,
ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset)
});
itemCount += 1;
offset += itemOffset + itemLength;
}
// Decrease the array size to match the actual item count.
assembly {
mstore(out, itemCount)
}
return out;
}
/**
* @notice Reads an RLP list value into a list of RLP items.
*
* @param _in RLP list value.
*
* @return Decoded RLP list items.
*/
function readList(bytes memory _in) internal pure returns (RLPItem[] memory) {
return readList(toRLPItem(_in));
}
/**
* @notice Reads an RLP bytes value into bytes.
*
* @param _in RLP bytes value.
*
* @return Decoded bytes.
*/
function readBytes(RLPItem memory _in) internal pure returns (bytes memory) {
(uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in);
require(
itemType == RLPItemType.DATA_ITEM,
"RLPReader: decoded item type for bytes is not a data item"
);
require(
_in.length == itemOffset + itemLength,
"RLPReader: bytes value contains an invalid remainder"
);
return _copy(_in.ptr, itemOffset, itemLength);
}
/**
* @notice Reads an RLP bytes value into bytes.
*
* @param _in RLP bytes value.
*
* @return Decoded bytes.
*/
function readBytes(bytes memory _in) internal pure returns (bytes memory) {
return readBytes(toRLPItem(_in));
}
/**
* @notice Reads the raw bytes of an RLP item.
*
* @param _in RLP item to read.
*
* @return Raw RLP bytes.
*/
function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory) {
return _copy(_in.ptr, 0, _in.length);
}
/**
* @notice Decodes the length of an RLP item.
*
* @param _in RLP item to decode.
*
* @return Offset of the encoded data.
* @return Length of the encoded data.
* @return RLP item type (LIST_ITEM or DATA_ITEM).
*/
function _decodeLength(RLPItem memory _in)
private
pure
returns (
uint256,
uint256,
RLPItemType
)
{
// Short-circuit if there's nothing to decode, note that we perform this check when
// the user creates an RLP item via toRLPItem, but it's always possible for them to bypass
// that function and create an RLP item directly. So we need to check this anyway.
require(
_in.length > 0,
"RLPReader: length of an RLP item must be greater than zero to be decodable"
);
MemoryPointer ptr = _in.ptr;
uint256 prefix;
assembly {
prefix := byte(0, mload(ptr))
}
if (prefix <= 0x7f) {
// Single byte.
return (0, 1, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xb7) {
// Short string.
// slither-disable-next-line variable-scope
uint256 strLen = prefix - 0x80;
require(
_in.length > strLen,
"RLPReader: length of content must be greater than string length (short string)"
);
bytes1 firstByteOfContent;
assembly {
firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
}
require(
strLen != 1 || firstByteOfContent >= 0x80,
"RLPReader: invalid prefix, single byte < 0x80 are not prefixed (short string)"
);
return (1, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xbf) {
// Long string.
uint256 lenOfStrLen = prefix - 0xb7;
require(
_in.length > lenOfStrLen,
"RLPReader: length of content must be > than length of string length (long string)"
);
bytes1 firstByteOfContent;
assembly {
firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
}
require(
firstByteOfContent != 0x00,
"RLPReader: length of content must not have any leading zeros (long string)"
);
uint256 strLen;
assembly {
strLen := shr(sub(256, mul(8, lenOfStrLen)), mload(add(ptr, 1)))
}
require(
strLen > 55,
"RLPReader: length of content must be greater than 55 bytes (long string)"
);
require(
_in.length > lenOfStrLen + strLen,
"RLPReader: length of content must be greater than total length (long string)"
);
return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM);
} else if (prefix <= 0xf7) {
// Short list.
// slither-disable-next-line variable-scope
uint256 listLen = prefix - 0xc0;
require(
_in.length > listLen,
"RLPReader: length of content must be greater than list length (short list)"
);
return (1, listLen, RLPItemType.LIST_ITEM);
} else {
// Long list.
uint256 lenOfListLen = prefix - 0xf7;
require(
_in.length > lenOfListLen,
"RLPReader: length of content must be > than length of list length (long list)"
);
bytes1 firstByteOfContent;
assembly {
firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff))
}
require(
firstByteOfContent != 0x00,
"RLPReader: length of content must not have any leading zeros (long list)"
);
uint256 listLen;
assembly {
listLen := shr(sub(256, mul(8, lenOfListLen)), mload(add(ptr, 1)))
}
require(
listLen > 55,
"RLPReader: length of content must be greater than 55 bytes (long list)"
);
require(
_in.length > lenOfListLen + listLen,
"RLPReader: length of content must be greater than total length (long list)"
);
return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM);
}
}
/**
* @notice Copies the bytes from a memory location.
*
* @param _src Pointer to the location to read from.
* @param _offset Offset to start reading from.
* @param _length Number of bytes to read.
*
* @return Copied bytes.
*/
function _copy(
MemoryPointer _src,
uint256 _offset,
uint256 _length
) private pure returns (bytes memory) {
bytes memory out = new bytes(_length);
if (_length == 0) {
return out;
}
// Mostly based on Solidity's copy_memory_to_memory:
// solhint-disable max-line-length
// https://github.com/ethereum/solidity/blob/34dd30d71b4da730488be72ff6af7083cf2a91f6/libsolidity/codegen/YulUtilFunctions.cpp#L102-L114
uint256 src = MemoryPointer.unwrap(_src) + _offset;
assembly {
let dest := add(out, 32)
let i := 0
for {
} lt(i, _length) {
i := add(i, 32)
} {
mstore(add(dest, i), mload(add(src, i)))
}
if gt(i, _length) {
mstore(add(dest, _length), 0)
}
}
return out;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @custom:attribution https://github.com/bakaoh/solidity-rlp-encode
* @title RLPWriter
* @author RLPWriter is a library for encoding Solidity types to RLP bytes. Adapted from Bakaoh's
* RLPEncode library (https://github.com/bakaoh/solidity-rlp-encode) with minor
* modifications to improve legibility.
*/
library RLPWriter {
/**
* @notice RLP encodes a byte string.
*
* @param _in The byte string to encode.
*
* @return The RLP encoded string in bytes.
*/
function writeBytes(bytes memory _in) internal pure returns (bytes memory) {
bytes memory encoded;
if (_in.length == 1 && uint8(_in[0]) < 128) {
encoded = _in;
} else {
encoded = abi.encodePacked(_writeLength(_in.length, 128), _in);
}
return encoded;
}
/**
* @notice RLP encodes a list of RLP encoded byte byte strings.
*
* @param _in The list of RLP encoded byte strings.
*
* @return The RLP encoded list of items in bytes.
*/
function writeList(bytes[] memory _in) internal pure returns (bytes memory) {
bytes memory list = _flatten(_in);
return abi.encodePacked(_writeLength(list.length, 192), list);
}
/**
* @notice RLP encodes a string.
*
* @param _in The string to encode.
*
* @return The RLP encoded string in bytes.
*/
function writeString(string memory _in) internal pure returns (bytes memory) {
return writeBytes(bytes(_in));
}
/**
* @notice RLP encodes an address.
*
* @param _in The address to encode.
*
* @return The RLP encoded address in bytes.
*/
function writeAddress(address _in) internal pure returns (bytes memory) {
return writeBytes(abi.encodePacked(_in));
}
/**
* @notice RLP encodes a uint.
*
* @param _in The uint256 to encode.
*
* @return The RLP encoded uint256 in bytes.
*/
function writeUint(uint256 _in) internal pure returns (bytes memory) {
return writeBytes(_toBinary(_in));
}
/**
* @notice RLP encodes a bool.
*
* @param _in The bool to encode.
*
* @return The RLP encoded bool in bytes.
*/
function writeBool(bool _in) internal pure returns (bytes memory) {
bytes memory encoded = new bytes(1);
encoded[0] = (_in ? bytes1(0x01) : bytes1(0x80));
return encoded;
}
/**
* @notice Encode the first byte and then the `len` in binary form if `length` is more than 55.
*
* @param _len The length of the string or the payload.
* @param _offset 128 if item is string, 192 if item is list.
*
* @return RLP encoded bytes.
*/
function _writeLength(uint256 _len, uint256 _offset) private pure returns (bytes memory) {
bytes memory encoded;
if (_len < 56) {
encoded = new bytes(1);
encoded[0] = bytes1(uint8(_len) + uint8(_offset));
} else {
uint256 lenLen;
uint256 i = 1;
while (_len / i != 0) {
lenLen++;
i *= 256;
}
encoded = new bytes(lenLen + 1);
encoded[0] = bytes1(uint8(lenLen) + uint8(_offset) + 55);
for (i = 1; i <= lenLen; i++) {
encoded[i] = bytes1(uint8((_len / (256**(lenLen - i))) % 256));
}
}
return encoded;
}
/**
* @notice Encode integer in big endian binary form with no leading zeroes.
*
* @param _x The integer to encode.
*
* @return RLP encoded bytes.
*/
function _toBinary(uint256 _x) private pure returns (bytes memory) {
bytes memory b = abi.encodePacked(_x);
uint256 i = 0;
for (; i < 32; i++) {
if (b[i] != 0) {
break;
}
}
bytes memory res = new bytes(32 - i);
for (uint256 j = 0; j < res.length; j++) {
res[j] = b[i++];
}
return res;
}
/**
* @custom:attribution https://github.com/Arachnid/solidity-stringutils
* @notice Copies a piece of memory to another location.
*
* @param _dest Destination location.
* @param _src Source location.
* @param _len Length of memory to copy.
*/
function _memcpy(
uint256 _dest,
uint256 _src,
uint256 _len
) private pure {
uint256 dest = _dest;
uint256 src = _src;
uint256 len = _len;
for (; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
uint256 mask;
unchecked {
mask = 256**(32 - len) - 1;
}
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
/**
* @custom:attribution https://github.com/sammayo/solidity-rlp-encoder
* @notice Flattens a list of byte strings into one byte string.
*
* @param _list List of byte strings to flatten.
*
* @return The flattened byte string.
*/
function _flatten(bytes[] memory _list) private pure returns (bytes memory) {
if (_list.length == 0) {
return new bytes(0);
}
uint256 len;
uint256 i = 0;
for (; i < _list.length; i++) {
len += _list[i].length;
}
bytes memory flattened = new bytes(len);
uint256 flattenedPtr;
assembly {
flattenedPtr := add(flattened, 0x20)
}
for (i = 0; i < _list.length; i++) {
bytes memory item = _list[i];
uint256 listPtr;
assembly {
listPtr := add(item, 0x20)
}
_memcpy(flattenedPtr, listPtr, item.length);
flattenedPtr += _list[i].length;
}
return flattened;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Bytes } from "../Bytes.sol";
import { RLPReader } from "../rlp/RLPReader.sol";
/**
* @title MerkleTrie
* @notice MerkleTrie is a small library for verifying standard Ethereum Merkle-Patricia trie
* inclusion proofs. By default, this library assumes a hexary trie. One can change the
* trie radix constant to support other trie radixes.
*/
library MerkleTrie {
/**
* @notice Struct representing a node in the trie.
*
* @custom:field encoded The RLP-encoded node.
* @custom:field decoded The RLP-decoded node.
*/
struct TrieNode {
bytes encoded;
RLPReader.RLPItem[] decoded;
}
/**
* @notice Determines the number of elements per branch node.
*/
uint256 internal constant TREE_RADIX = 16;
/**
* @notice Branch nodes have TREE_RADIX elements and one value element.
*/
uint256 internal constant BRANCH_NODE_LENGTH = TREE_RADIX + 1;
/**
* @notice Leaf nodes and extension nodes have two elements, a `path` and a `value`.
*/
uint256 internal constant LEAF_OR_EXTENSION_NODE_LENGTH = 2;
/**
* @notice Prefix for even-nibbled extension node paths.
*/
uint8 internal constant PREFIX_EXTENSION_EVEN = 0;
/**
* @notice Prefix for odd-nibbled extension node paths.
*/
uint8 internal constant PREFIX_EXTENSION_ODD = 1;
/**
* @notice Prefix for even-nibbled leaf node paths.
*/
uint8 internal constant PREFIX_LEAF_EVEN = 2;
/**
* @notice Prefix for odd-nibbled leaf node paths.
*/
uint8 internal constant PREFIX_LEAF_ODD = 3;
/**
* @notice Verifies a proof that a given key/value pair is present in the trie.
*
* @param _key Key of the node to search for, as a hex string.
* @param _value Value of the node to search for, as a hex string.
* @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle
* trees, this proof is executed top-down and consists of a list of RLP-encoded
* nodes that make a path down to the target node.
* @param _root Known root of the Merkle trie. Used to verify that the included proof is
* correctly constructed.
*
* @return Whether or not the proof is valid.
*/
function verifyInclusionProof(
bytes memory _key,
bytes memory _value,
bytes[] memory _proof,
bytes32 _root
) internal pure returns (bool) {
return Bytes.equal(_value, get(_key, _proof, _root));
}
/**
* @notice Retrieves the value associated with a given key.
*
* @param _key Key to search for, as hex bytes.
* @param _proof Merkle trie inclusion proof for the key.
* @param _root Known root of the Merkle trie.
*
* @return Value of the key if it exists.
*/
function get(
bytes memory _key,
bytes[] memory _proof,
bytes32 _root
) internal pure returns (bytes memory) {
require(_key.length > 0, "MerkleTrie: empty key");
TrieNode[] memory proof = _parseProof(_proof);
bytes memory key = Bytes.toNibbles(_key);
bytes memory currentNodeID = abi.encodePacked(_root);
uint256 currentKeyIndex = 0;
// Proof is top-down, so we start at the first element (root).
for (uint256 i = 0; i < proof.length; i++) {
TrieNode memory currentNode = proof[i];
// Key index should never exceed total key length or we'll be out of bounds.
require(
currentKeyIndex <= key.length,
"MerkleTrie: key index exceeds total key length"
);
if (currentKeyIndex == 0) {
// First proof element is always the root node.
require(
Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID),
"MerkleTrie: invalid root hash"
);
} else if (currentNode.encoded.length >= 32) {
// Nodes 32 bytes or larger are hashed inside branch nodes.
require(
Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID),
"MerkleTrie: invalid large internal hash"
);
} else {
// Nodes smaller than 32 bytes aren't hashed.
require(
Bytes.equal(currentNode.encoded, currentNodeID),
"MerkleTrie: invalid internal node hash"
);
}
if (currentNode.decoded.length == BRANCH_NODE_LENGTH) {
if (currentKeyIndex == key.length) {
// Value is the last element of the decoded list (for branch nodes). There's
// some ambiguity in the Merkle trie specification because bytes(0) is a
// valid value to place into the trie, but for branch nodes bytes(0) can exist
// even when the value wasn't explicitly placed there. Geth treats a value of
// bytes(0) as "key does not exist" and so we do the same.
bytes memory value = RLPReader.readBytes(currentNode.decoded[TREE_RADIX]);
require(
value.length > 0,
"MerkleTrie: value length must be greater than zero (branch)"
);
// Extra proof elements are not allowed.
require(
i == proof.length - 1,
"MerkleTrie: value node must be last node in proof (branch)"
);
return value;
} else {
// We're not at the end of the key yet.
// Figure out what the next node ID should be and continue.
uint8 branchKey = uint8(key[currentKeyIndex]);
RLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey];
currentNodeID = _getNodeID(nextNode);
currentKeyIndex += 1;
}
} else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) {
bytes memory path = _getNodePath(currentNode);
uint8 prefix = uint8(path[0]);
uint8 offset = 2 - (prefix % 2);
bytes memory pathRemainder = Bytes.slice(path, offset);
bytes memory keyRemainder = Bytes.slice(key, currentKeyIndex);
uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder);
// Whether this is a leaf node or an extension node, the path remainder MUST be a
// prefix of the key remainder (or be equal to the key remainder) or the proof is
// considered invalid.
require(
pathRemainder.length == sharedNibbleLength,
"MerkleTrie: path remainder must share all nibbles with key"
);
if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) {
// Prefix of 2 or 3 means this is a leaf node. For the leaf node to be valid,
// the key remainder must be exactly equal to the path remainder. We already
// did the necessary byte comparison, so it's more efficient here to check that
// the key remainder length equals the shared nibble length, which implies
// equality with the path remainder (since we already did the same check with
// the path remainder and the shared nibble length).
require(
keyRemainder.length == sharedNibbleLength,
"MerkleTrie: key remainder must be identical to path remainder"
);
// Our Merkle Trie is designed specifically for the purposes of the Ethereum
// state trie. Empty values are not allowed in the state trie, so we can safely
// say that if the value is empty, the key should not exist and the proof is
// invalid.
bytes memory value = RLPReader.readBytes(currentNode.decoded[1]);
require(
value.length > 0,
"MerkleTrie: value length must be greater than zero (leaf)"
);
// Extra proof elements are not allowed.
require(
i == proof.length - 1,
"MerkleTrie: value node must be last node in proof (leaf)"
);
return value;
} else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) {
// Prefix of 0 or 1 means this is an extension node. We move onto the next node
// in the proof and increment the key index by the length of the path remainder
// which is equal to the shared nibble length.
currentNodeID = _getNodeID(currentNode.decoded[1]);
currentKeyIndex += sharedNibbleLength;
} else {
revert("MerkleTrie: received a node with an unknown prefix");
}
} else {
revert("MerkleTrie: received an unparseable node");
}
}
revert("MerkleTrie: ran out of proof elements");
}
/**
* @notice Parses an array of proof elements into a new array that contains both the original
* encoded element and the RLP-decoded element.
*
* @param _proof Array of proof elements to parse.
*
* @return Proof parsed into easily accessible structs.
*/
function _parseProof(bytes[] memory _proof) private pure returns (TrieNode[] memory) {
uint256 length = _proof.length;
TrieNode[] memory proof = new TrieNode[](length);
for (uint256 i = 0; i < length; ) {
proof[i] = TrieNode({ encoded: _proof[i], decoded: RLPReader.readList(_proof[i]) });
unchecked {
++i;
}
}
return proof;
}
/**
* @notice Picks out the ID for a node. Node ID is referred to as the "hash" within the
* specification, but nodes < 32 bytes are not actually hashed.
*
* @param _node Node to pull an ID for.
*
* @return ID for the node, depending on the size of its contents.
*/
function _getNodeID(RLPReader.RLPItem memory _node) private pure returns (bytes memory) {
return _node.length < 32 ? RLPReader.readRawBytes(_node) : RLPReader.readBytes(_node);
}
/**
* @notice Gets the path for a leaf or extension node.
*
* @param _node Node to get a path for.
*
* @return Node path, converted to an array of nibbles.
*/
function _getNodePath(TrieNode memory _node) private pure returns (bytes memory) {
return Bytes.toNibbles(RLPReader.readBytes(_node.decoded[0]));
}
/**
* @notice Utility; determines the number of nibbles shared between two nibble arrays.
*
* @param _a First nibble array.
* @param _b Second nibble array.
*
* @return Number of shared nibbles.
*/
function _getSharedNibbleLength(bytes memory _a, bytes memory _b)
private
pure
returns (uint256)
{
uint256 shared;
uint256 max = (_a.length < _b.length) ? _a.length : _b.length;
for (; shared < max && _a[shared] == _b[shared]; ) {
unchecked {
++shared;
}
}
return shared;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/* Library Imports */
import { MerkleTrie } from "./MerkleTrie.sol";
/**
* @title SecureMerkleTrie
* @notice SecureMerkleTrie is a thin wrapper around the MerkleTrie library that hashes the input
* keys. Ethereum's state trie hashes input keys before storing them.
*/
library SecureMerkleTrie {
/**
* @notice Verifies a proof that a given key/value pair is present in the Merkle trie.
*
* @param _key Key of the node to search for, as a hex string.
* @param _value Value of the node to search for, as a hex string.
* @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle
* trees, this proof is executed top-down and consists of a list of RLP-encoded
* nodes that make a path down to the target node.
* @param _root Known root of the Merkle trie. Used to verify that the included proof is
* correctly constructed.
*
* @return Whether or not the proof is valid.
*/
function verifyInclusionProof(
bytes memory _key,
bytes memory _value,
bytes[] memory _proof,
bytes32 _root
) internal pure returns (bool) {
bytes memory key = _getSecureKey(_key);
return MerkleTrie.verifyInclusionProof(key, _value, _proof, _root);
}
/**
* @notice Retrieves the value associated with a given key.
*
* @param _key Key to search for, as hex bytes.
* @param _proof Merkle trie inclusion proof for the key.
* @param _root Known root of the Merkle trie.
*
* @return Value of the key if it exists.
*/
function get(
bytes memory _key,
bytes[] memory _proof,
bytes32 _root
) internal pure returns (bytes memory) {
bytes memory key = _getSecureKey(_key);
return MerkleTrie.get(key, _proof, _root);
}
/**
* @notice Computes the hashed version of the input key.
*
* @param _key Key to hash.
*
* @return Hashed version of the key.
*/
function _getSecureKey(bytes memory _key) private pure returns (bytes memory) {
return abi.encodePacked(keccak256(_key));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { Hashing } from "../libraries/Hashing.sol";
import { Encoding } from "../libraries/Encoding.sol";
import { Constants } from "../libraries/Constants.sol";
/**
* @custom:legacy
* @title CrossDomainMessengerLegacySpacer0
* @notice Contract only exists to add a spacer to the CrossDomainMessenger where the
* libAddressManager variable used to exist. Must be the first contract in the inheritance
* tree of the CrossDomainMessenger.
*/
contract CrossDomainMessengerLegacySpacer0 {
/**
* @custom:legacy
* @custom:spacer libAddressManager
* @notice Spacer for backwards compatibility.
*/
address private spacer_0_0_20;
}
/**
* @custom:legacy
* @title CrossDomainMessengerLegacySpacer1
* @notice Contract only exists to add a spacer to the CrossDomainMessenger where the
* PausableUpgradable and OwnableUpgradeable variables used to exist. Must be
* the third contract in the inheritance tree of the CrossDomainMessenger.
*/
contract CrossDomainMessengerLegacySpacer1 {
/**
* @custom:legacy
* @custom:spacer ContextUpgradable's __gap
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* ContextUpgradable.
*
*/
uint256[50] private spacer_1_0_1600;
/**
* @custom:legacy
* @custom:spacer OwnableUpgradeable's _owner
* @notice Spacer for backwards compatibility.
* Come from OpenZeppelin OwnableUpgradeable.
*/
address private spacer_51_0_20;
/**
* @custom:legacy
* @custom:spacer OwnableUpgradeable's __gap
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* OwnableUpgradeable.
*/
uint256[49] private spacer_52_0_1568;
/**
* @custom:legacy
* @custom:spacer PausableUpgradable's _paused
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* PausableUpgradable.
*/
bool private spacer_101_0_1;
/**
* @custom:legacy
* @custom:spacer PausableUpgradable's __gap
* @notice Spacer for backwards compatibility. Comes from OpenZeppelin
* PausableUpgradable.
*/
uint256[49] private spacer_102_0_1568;
/**
* @custom:legacy
* @custom:spacer ReentrancyGuardUpgradeable's `_status` field.
* @notice Spacer for backwards compatibility.
*/
uint256 private spacer_151_0_32;
/**
* @custom:legacy
* @custom:spacer ReentrancyGuardUpgradeable's __gap
* @notice Spacer for backwards compatibility.
*/
uint256[49] private spacer_152_0_1568;
/**
* @custom:legacy
* @custom:spacer blockedMessages
* @notice Spacer for backwards compatibility.
*/
mapping(bytes32 => bool) private spacer_201_0_32;
/**
* @custom:legacy
* @custom:spacer relayedMessages
* @notice Spacer for backwards compatibility.
*/
mapping(bytes32 => bool) private spacer_202_0_32;
}
/**
* @custom:upgradeable
* @title CrossDomainMessenger
* @notice CrossDomainMessenger is a base contract that provides the core logic for the L1 and L2
* cross-chain messenger contracts. It's designed to be a universal interface that only
* needs to be extended slightly to provide low-level message passing functionality on each
* chain it's deployed on. Currently only designed for message passing between two paired
* chains and does not support one-to-many interactions.
*
* Any changes to this contract MUST result in a semver bump for contracts that inherit it.
*/
abstract contract CrossDomainMessenger is
CrossDomainMessengerLegacySpacer0,
Initializable,
CrossDomainMessengerLegacySpacer1
{
/**
* @notice Current message version identifier.
*/
uint16 public constant MESSAGE_VERSION = 1;
/**
* @notice Constant overhead added to the base gas for a message.
*/
uint64 public constant RELAY_CONSTANT_OVERHEAD = 200_000;
/**
* @notice Numerator for dynamic overhead added to the base gas for a message.
*/
uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR = 64;
/**
* @notice Denominator for dynamic overhead added to the base gas for a message.
*/
uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = 63;
/**
* @notice Extra gas added to base gas for each byte of calldata in a message.
*/
uint64 public constant MIN_GAS_CALLDATA_OVERHEAD = 16;
/**
* @notice Gas reserved for performing the external call in `relayMessage`.
*/
uint64 public constant RELAY_CALL_OVERHEAD = 40_000;
/**
* @notice Gas reserved for finalizing the execution of `relayMessage` after the safe call.
*/
uint64 public constant RELAY_RESERVED_GAS = 40_000;
/**
* @notice Gas reserved for the execution between the `hasMinGas` check and the external
* call in `relayMessage`.
*/
uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000;
/**
* @notice Address of the paired CrossDomainMessenger contract on the other chain.
*/
address public immutable OTHER_MESSENGER;
/**
* @notice Mapping of message hashes to boolean receipt values. Note that a message will only
* be present in this mapping if it has successfully been relayed on this chain, and
* can therefore not be relayed again.
*/
mapping(bytes32 => bool) public successfulMessages;
/**
* @notice Address of the sender of the currently executing message on the other chain. If the
* value of this variable is the default value (0x00000000...dead) then no message is
* currently being executed. Use the xDomainMessageSender getter which will throw an
* error if this is the case.
*/
address internal xDomainMsgSender;
/**
* @notice Nonce for the next message to be sent, without the message version applied. Use the
* messageNonce getter which will insert the message version into the nonce to give you
* the actual nonce to be used for the message.
*/
uint240 internal msgNonce;
/**
* @notice Mapping of message hashes to a boolean if and only if the message has failed to be
* executed at least once. A message will not be present in this mapping if it
* successfully executed on the first attempt.
*/
mapping(bytes32 => bool) public failedMessages;
/**
* @notice Reserve extra slots in the storage layout for future upgrades.
* A gap size of 41 was chosen here, so that the first slot used in a child contract
* would be a multiple of 50.
*/
uint256[42] private __gap;
/**
* @notice Emitted whenever a message is sent to the other chain.
*
* @param target Address of the recipient of the message.
* @param sender Address of the sender of the message.
* @param message Message to trigger the recipient address with.
* @param messageNonce Unique nonce attached to the message.
* @param gasLimit Minimum gas limit that the message can be executed with.
*/
event SentMessage(
address indexed target,
address sender,
bytes message,
uint256 messageNonce,
uint256 gasLimit
);
/**
* @notice Additional event data to emit, required as of Bedrock. Cannot be merged with the
* SentMessage event without breaking the ABI of this contract, this is good enough.
*
* @param sender Address of the sender of the message.
* @param value ETH value sent along with the message to the recipient.
*/
event SentMessageExtension1(address indexed sender, uint256 value);
/**
* @notice Emitted whenever a message is successfully relayed on this chain.
*
* @param msgHash Hash of the message that was relayed.
*/
event RelayedMessage(bytes32 indexed msgHash);
/**
* @notice Emitted whenever a message fails to be relayed on this chain.
*
* @param msgHash Hash of the message that failed to be relayed.
*/
event FailedRelayedMessage(bytes32 indexed msgHash);
/**
* @param _otherMessenger Address of the messenger on the paired chain.
*/
constructor(address _otherMessenger) {
OTHER_MESSENGER = _otherMessenger;
}
/**
* @notice Sends a message to some target address on the other chain. Note that if the call
* always reverts, then the message will be unrelayable, and any ETH sent will be
* permanently locked. The same will occur if the target on the other chain is
* considered unsafe (see the _isUnsafeTarget() function).
*
* @param _target Target contract or wallet address.
* @param _message Message to trigger the target address with.
* @param _minGasLimit Minimum gas limit that the message can be executed with.
*/
function sendMessage(
address _target,
bytes calldata _message,
uint32 _minGasLimit
) external payable {
// Triggers a message to the other messenger. Note that the amount of gas provided to the
// message is the amount of gas requested by the user PLUS the base gas value. We want to
// guarantee the property that the call to the target contract will always have at least
// the minimum gas limit specified by the user.
_sendMessage(
OTHER_MESSENGER,
baseGas(_message, _minGasLimit),
msg.value,
abi.encodeWithSelector(
this.relayMessage.selector,
messageNonce(),
msg.sender,
_target,
msg.value,
_minGasLimit,
_message
)
);
emit SentMessage(_target, msg.sender, _message, messageNonce(), _minGasLimit);
emit SentMessageExtension1(msg.sender, msg.value);
unchecked {
++msgNonce;
}
}
/**
* @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only
* be executed via cross-chain call from the other messenger OR if the message was
* already received once and is currently being replayed.
*
* @param _nonce Nonce of the message being relayed.
* @param _sender Address of the user who sent the message.
* @param _target Address that the message is targeted at.
* @param _value ETH value to send with the message.
* @param _minGasLimit Minimum amount of gas that the message can be executed with.
* @param _message Message to send to the target.
*/
function relayMessage(
uint256 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _minGasLimit,
bytes calldata _message
) external payable {
(, uint16 version) = Encoding.decodeVersionedNonce(_nonce);
require(
version < 2,
"CrossDomainMessenger: only version 0 or 1 messages are supported at this time"
);
// If the message is version 0, then it's a migrated legacy withdrawal. We therefore need
// to check that the legacy version of the message has not already been relayed.
if (version == 0) {
bytes32 oldHash = Hashing.hashCrossDomainMessageV0(_target, _sender, _message, _nonce);
require(
successfulMessages[oldHash] == false,
"CrossDomainMessenger: legacy withdrawal already relayed"
);
}
// We use the v1 message hash as the unique identifier for the message because it commits
// to the value and minimum gas limit of the message.
bytes32 versionedHash = Hashing.hashCrossDomainMessageV1(
_nonce,
_sender,
_target,
_value,
_minGasLimit,
_message
);
if (_isOtherMessenger()) {
// These properties should always hold when the message is first submitted (as
// opposed to being replayed).
assert(msg.value == _value);
assert(!failedMessages[versionedHash]);
} else {
require(
msg.value == 0,
"CrossDomainMessenger: value must be zero unless message is from a system address"
);
require(
failedMessages[versionedHash],
"CrossDomainMessenger: message cannot be replayed"
);
}
require(
_isUnsafeTarget(_target) == false,
"CrossDomainMessenger: cannot send message to blocked system address"
);
require(
successfulMessages[versionedHash] == false,
"CrossDomainMessenger: message has already been relayed"
);
// If there is not enough gas left to perform the external call and finish the execution,
// return early and assign the message to the failedMessages mapping.
// We are asserting that we have enough gas to:
// 1. Call the target contract (_minGasLimit + RELAY_CALL_OVERHEAD + RELAY_GAS_CHECK_BUFFER)
// 1.a. The RELAY_CALL_OVERHEAD is included in `hasMinGas`.
// 2. Finish the execution after the external call (RELAY_RESERVED_GAS).
//
// If `xDomainMsgSender` is not the default L2 sender, this function
// is being re-entered. This marks the message as failed to allow it to be replayed.
if (
!SafeCall.hasMinGas(_minGasLimit, RELAY_RESERVED_GAS + RELAY_GAS_CHECK_BUFFER) ||
xDomainMsgSender != Constants.DEFAULT_L2_SENDER
) {
failedMessages[versionedHash] = true;
emit FailedRelayedMessage(versionedHash);
// Revert in this case if the transaction was triggered by the estimation address. This
// should only be possible during gas estimation or we have bigger problems. Reverting
// here will make the behavior of gas estimation change such that the gas limit
// computed will be the amount required to relay the message, even if that amount is
// greater than the minimum gas limit specified by the user.
if (tx.origin == Constants.ESTIMATION_ADDRESS) {
revert("CrossDomainMessenger: failed to relay message");
}
return;
}
xDomainMsgSender = _sender;
bool success = SafeCall.call(_target, gasleft() - RELAY_RESERVED_GAS, _value, _message);
xDomainMsgSender = Constants.DEFAULT_L2_SENDER;
if (success) {
successfulMessages[versionedHash] = true;
emit RelayedMessage(versionedHash);
} else {
failedMessages[versionedHash] = true;
emit FailedRelayedMessage(versionedHash);
// Revert in this case if the transaction was triggered by the estimation address. This
// should only be possible during gas estimation or we have bigger problems. Reverting
// here will make the behavior of gas estimation change such that the gas limit
// computed will be the amount required to relay the message, even if that amount is
// greater than the minimum gas limit specified by the user.
if (tx.origin == Constants.ESTIMATION_ADDRESS) {
revert("CrossDomainMessenger: failed to relay message");
}
}
}
/**
* @notice Retrieves the address of the contract or wallet that initiated the currently
* executing message on the other chain. Will throw an error if there is no message
* currently being executed. Allows the recipient of a call to see who triggered it.
*
* @return Address of the sender of the currently executing message on the other chain.
*/
function xDomainMessageSender() external view returns (address) {
require(
xDomainMsgSender != Constants.DEFAULT_L2_SENDER,
"CrossDomainMessenger: xDomainMessageSender is not set"
);
return xDomainMsgSender;
}
/**
* @notice Retrieves the next message nonce. Message version will be added to the upper two
* bytes of the message nonce. Message version allows us to treat messages as having
* different structures.
*
* @return Nonce of the next message to be sent, with added message version.
*/
function messageNonce() public view returns (uint256) {
return Encoding.encodeVersionedNonce(msgNonce, MESSAGE_VERSION);
}
/**
* @notice Computes the amount of gas required to guarantee that a given message will be
* received on the other chain without running out of gas. Guaranteeing that a message
* will not run out of gas is important because this ensures that a message can always
* be replayed on the other chain if it fails to execute completely.
*
* @param _message Message to compute the amount of required gas for.
* @param _minGasLimit Minimum desired gas limit when message goes to target.
*
* @return Amount of gas required to guarantee message receipt.
*/
function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) {
return
// Constant overhead
RELAY_CONSTANT_OVERHEAD +
// Calldata overhead
(uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) +
// Dynamic overhead (EIP-150)
((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) /
MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) +
// Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas
// factors. (Conservative)
RELAY_CALL_OVERHEAD +
// Relay reserved gas (to ensure execution of `relayMessage` completes after the
// subcontext finishes executing) (Conservative)
RELAY_RESERVED_GAS +
// Gas reserved for the execution between the `hasMinGas` check and the `CALL`
// opcode. (Conservative)
RELAY_GAS_CHECK_BUFFER;
}
/**
* @notice Intializer.
*/
// solhint-disable-next-line func-name-mixedcase
function __CrossDomainMessenger_init() internal onlyInitializing {
xDomainMsgSender = Constants.DEFAULT_L2_SENDER;
}
/**
* @notice Sends a low-level message to the other messenger. Needs to be implemented by child
* contracts because the logic for this depends on the network where the messenger is
* being deployed.
*
* @param _to Recipient of the message on the other chain.
* @param _gasLimit Minimum gas limit the message can be executed with.
* @param _value Amount of ETH to send with the message.
* @param _data Message data.
*/
function _sendMessage(
address _to,
uint64 _gasLimit,
uint256 _value,
bytes memory _data
) internal virtual;
/**
* @notice Checks whether the message is coming from the other messenger. Implemented by child
* contracts because the logic for this depends on the network where the messenger is
* being deployed.
*
* @return Whether the message is coming from the other messenger.
*/
function _isOtherMessenger() internal view virtual returns (bool);
/**
* @notice Checks whether a given call target is a system address that could cause the
* messenger to peform an unsafe action. This is NOT a mechanism for blocking user
* addresses. This is ONLY used to prevent the execution of messages to specific
* system addresses that could cause security issues, e.g., having the
* CrossDomainMessenger send messages to itself.
*
* @param _target Address of the contract to check.
*
* @return Whether or not the address is an unsafe system address.
*/
function _isUnsafeTarget(address _target) internal view virtual returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/**
* @title Semver
* @notice Semver is a simple contract for managing contract versions.
*/
contract Semver {
/**
* @notice Contract version number (major).
*/
uint256 private immutable MAJOR_VERSION;
/**
* @notice Contract version number (minor).
*/
uint256 private immutable MINOR_VERSION;
/**
* @notice Contract version number (patch).
*/
uint256 private immutable PATCH_VERSION;
/**
* @param _major Version number (major).
* @param _minor Version number (minor).
* @param _patch Version number (patch).
*/
constructor(
uint256 _major,
uint256 _minor,
uint256 _patch
) {
MAJOR_VERSION = _major;
MINOR_VERSION = _minor;
PATCH_VERSION = _patch;
}
/**
* @notice Returns the full semver contract version.
*
* @return Semver contract version as a string.
*/
function version() public view returns (string memory) {
return
string(
abi.encodePacked(
Strings.toString(MAJOR_VERSION),
".",
Strings.toString(MINOR_VERSION),
".",
Strings.toString(PATCH_VERSION)
)
);
}
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2019-2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity ^0.8.0;
library AddressAliasHelper {
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
/// @notice Utility function that converts the address in the L1 that submitted a tx to
/// the inbox to the msg.sender viewed in the L2
/// @param l1Address the address in the L1 that triggered the tx to L2
/// @return l2Address L2 address as viewed in msg.sender
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + offset);
}
}
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
unchecked {
l1Address = address(uint160(l2Address) - offset);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
* initialization step. This is essential to configure modules that are added through upgrades and that require
* initialization.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. It the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`.
// We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
// This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
// Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a
// good first aproximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1;
uint256 x = a;
if (x >> 128 > 0) {
x >>= 128;
result <<= 64;
}
if (x >> 64 > 0) {
x >>= 64;
result <<= 32;
}
if (x >> 32 > 0) {
x >>= 32;
result <<= 16;
}
if (x >> 16 > 0) {
x >>= 16;
result <<= 8;
}
if (x >> 8 > 0) {
x >>= 8;
result <<= 4;
}
if (x >> 4 > 0) {
x >>= 4;
result <<= 2;
}
if (x >> 2 > 0) {
result <<= 1;
}
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
uint256 result = sqrt(a);
if (rounding == Rounding.Up && result * result < a) {
result += 1;
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
* initialization step. This is essential to configure modules that are added through upgrades and that require
* initialization.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Equivalent to x to the power of y because x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)
return expWad((lnWad(x) * y) / int256(WAD)); // Using ln(x) means x must be greater than 0.
}
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is < 0.5 we return zero. This happens when
// x <= floor(log(0.5e18) * 1e18) ~ -42e18
if (x <= -42139678854452767551) return 0;
// When the result is > (2**255 - 1) / 1e18 we can not represent it as an
// int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
if (x >= 135305999368893231589) revert("EXP_OVERFLOW");
// x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5**18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96;
x = x - k * 54916777467707473351141471128;
// k is in the range [-61, 195].
// Evaluate using a (6, 7)-term rational approximation.
// p is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave p in 2**192 basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already 2**96 too large.
r := sdiv(p, q)
}
// r should be in the range (0.09, 0.25) * 2**96.
// We now need to multiply r by:
// * the scale factor s = ~6.031367120.
// * the 2**k factor from the range reduction.
// * the 1e18 / 2**96 factor for base conversion.
// We do this all at once, with an intermediate result in 2**213
// basis, so the final right shift is always by a positive amount.
r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k));
}
}
function lnWad(int256 x) internal pure returns (int256 r) {
unchecked {
require(x > 0, "UNDEFINED");
// We want to convert x from 10**18 fixed point to 2**96 fixed point.
// We do this by multiplying by 2**96 / 10**18. But since
// ln(x * C) = ln(x) + ln(C), we can simply do nothing here
// and add ln(2**96 / 10**18) at the end.
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
int256 k = int256(log2(uint256(x))) - 96;
x <<= uint256(159 - k);
x = int256(uint256(x) >> 159);
// Evaluate using a (8, 8)-term rational approximation.
// p is made monic, we will multiply by a scale factor later.
int256 p = x + 3273285459638523848632254066296;
p = ((p * x) >> 96) + 24828157081833163892658089445524;
p = ((p * x) >> 96) + 43456485725739037958740375743393;
p = ((p * x) >> 96) - 11111509109440967052023855526967;
p = ((p * x) >> 96) - 45023709667254063763336534515857;
p = ((p * x) >> 96) - 14706773417378608786704636184526;
p = p * x - (795164235651350426258249787498 << 96);
// We leave p in 2**192 basis so we don't need to scale it back up for the division.
// q is monic by convention.
int256 q = x + 5573035233440673466300451813936;
q = ((q * x) >> 96) + 71694874799317883764090561454958;
q = ((q * x) >> 96) + 283447036172924575727196451306956;
q = ((q * x) >> 96) + 401686690394027663651624208769553;
q = ((q * x) >> 96) + 204048457590392012362485061816622;
q = ((q * x) >> 96) + 31853899698501571402653359427138;
q = ((q * x) >> 96) + 909429971244387300277376558375;
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already 2**96 too large.
r := sdiv(p, q)
}
// r is in the range (0, 0.125) * 2**96
// Finalization, we need to:
// * multiply by the scale factor s = 5.549…
// * add ln(2**96 / 10**18)
// * add k * ln(2)
// * multiply by 10**18 / 2**96 = 5**18 >> 78
// mul s * 5e18 * 2**96, base is now 5**18 * 2**192
r *= 1677202110996718588342820967067443963516166;
// add ln(2) * k * 5e18 * 2**192
r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k;
// add ln(2**96 / 10**18) * 5e18 * 2**192
r += 600920179829731861736702779321621459595472258049074101567377883020018308;
// base conversion: mul 2**18 / 2**192
r >>= 174;
}
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function log2(uint256 x) internal pure returns (uint256 r) {
require(x > 0, "UNDEFINED");
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
r := or(r, shl(2, lt(0xf, shr(r, x))))
r := or(r, shl(1, lt(0x3, shr(r, x))))
r := or(r, lt(0x1, shr(r, x)))
}
}
}