// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
import "./Ownable.sol";
/**
* @title AccessControl
* @dev This abstract contract implements access control mechanism based on roles.
* Each role can have one or more addresses associated with it, which are granted
* permission to execute functions with the onlyRole modifier.
*/
abstract contract AccessControl is Ownable {
/**
* @dev A mapping of roles to a mapping of addresses to boolean values indicating whether or not they have the role.
*/
mapping(bytes32 => mapping(address => bool)) private _permits;
/**
* @dev Emitted when a role is granted to an address.
*/
event RoleGranted(bytes32 indexed role, address indexed grantee);
/**
* @dev Emitted when a role is revoked from an address.
*/
event RoleRevoked(bytes32 indexed role, address indexed revokee);
/**
* @dev Error message thrown when an address does not have permission to execute a function with onlyRole modifier.
*/
error NoPermit(bytes32 role);
/**
* @dev Constructor that sets the owner of the contract.
*/
constructor(address owner_) Ownable(owner_) {}
/**
* @dev Modifier that restricts access to addresses having roles
* Throws an error if the caller do not have permit
*/
modifier onlyRole(bytes32 role) {
if (!_permits[role][msg.sender]) revert NoPermit(role);
_;
}
/**
* @dev Checks and reverts if an address do not have a specific role.
* @param role_ The role to check.
* @param address_ The address to check.
*/
function _checkRole(bytes32 role_, address address_) internal virtual {
if (!_hasRole(role_, address_)) revert NoPermit(role_);
}
/**
* @dev Grants a role to a given address.
* @param role_ The role to grant.
* @param grantee_ The address to grant the role to.
* Emits a RoleGranted event.
* Can only be called by the owner of the contract.
*/
function grantRole(
bytes32 role_,
address grantee_
) external virtual onlyOwner {
_grantRole(role_, grantee_);
}
/**
* @dev Revokes a role from a given address.
* @param role_ The role to revoke.
* @param revokee_ The address to revoke the role from.
* Emits a RoleRevoked event.
* Can only be called by the owner of the contract.
*/
function revokeRole(
bytes32 role_,
address revokee_
) external virtual onlyOwner {
_revokeRole(role_, revokee_);
}
/**
* @dev Internal function to grant a role to a given address.
* @param role_ The role to grant.
* @param grantee_ The address to grant the role to.
* Emits a RoleGranted event.
*/
function _grantRole(bytes32 role_, address grantee_) internal {
_permits[role_][grantee_] = true;
emit RoleGranted(role_, grantee_);
}
/**
* @dev Internal function to revoke a role from a given address.
* @param role_ The role to revoke.
* @param revokee_ The address to revoke the role from.
* Emits a RoleRevoked event.
*/
function _revokeRole(bytes32 role_, address revokee_) internal {
_permits[role_][revokee_] = false;
emit RoleRevoked(role_, revokee_);
}
/**
* @dev Checks whether an address has a specific role.
* @param role_ The role to check.
* @param address_ The address to check.
* @return A boolean value indicating whether or not the address has the role.
*/
function hasRole(
bytes32 role_,
address address_
) external view returns (bool) {
return _hasRole(role_, address_);
}
function _hasRole(
bytes32 role_,
address address_
) internal view returns (bool) {
return _permits[role_][address_];
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
import "./AccessControl.sol";
/**
* @title AccessControlExtended
* @dev This contract extends the functionality of the AccessControl contract by adding
* the ability to grant and revoke roles based on a combination of role name and a chain slug.
* It also provides batch operations for granting and revoking roles.
*/
contract AccessControlExtended is AccessControl {
/**
* @dev Constructor that sets the owner of the contract.
*/
constructor(address owner_) AccessControl(owner_) {}
/**
* @dev thrown when array lengths are not equal
*/
error UnequalArrayLengths();
/**
* @dev Checks if an address has the role.
* @param roleName_ The name of the role.
* @param chainSlug_ The chain slug associated with the role.
* @param address_ The address to be granted the role.
*/
function _checkRoleWithSlug(
bytes32 roleName_,
uint256 chainSlug_,
address address_
) internal virtual {
bytes32 roleHash = keccak256(abi.encode(roleName_, chainSlug_));
if (!_hasRole(roleHash, address_)) revert NoPermit(roleHash);
}
/**
* @dev Grants a role to an address based on the role name and chain slug.
* @param roleName_ The name of the role.
* @param chainSlug_ The chain slug associated with the role.
* @param grantee_ The address to be granted the role.
*/
function grantRoleWithSlug(
bytes32 roleName_,
uint32 chainSlug_,
address grantee_
) external virtual onlyOwner {
_grantRoleWithSlug(roleName_, chainSlug_, grantee_);
}
/**
* @dev Grants multiple roles to multiple addresses in batch.
* @param roleNames_ The names of the roles to grant.
* @param slugs_ The slugs for chain specific roles. For roles which are not chain-specific, we can use slug = 0
* @param grantees_ The addresses to be granted the roles.
*/
function grantBatchRole(
bytes32[] calldata roleNames_,
uint32[] calldata slugs_,
address[] calldata grantees_
) external virtual onlyOwner {
if (
roleNames_.length != grantees_.length ||
roleNames_.length != slugs_.length
) revert UnequalArrayLengths();
uint256 totalRoles = roleNames_.length;
for (uint256 index = 0; index < totalRoles; ) {
if (slugs_[index] > 0)
_grantRoleWithSlug(
roleNames_[index],
slugs_[index],
grantees_[index]
);
else _grantRole(roleNames_[index], grantees_[index]);
// inputs are controlled by owner
unchecked {
++index;
}
}
}
/**
* @dev Revokes multiple roles from multiple addresses in batch.
* @param roleNames_ The names of the roles to revoke.
* @param slugs_ The slugs for chain specific roles. For roles which are not chain-specific, we can use slug = 0
* @param grantees_ The addresses to be revoked the roles.
*/
function revokeBatchRole(
bytes32[] calldata roleNames_,
uint32[] calldata slugs_,
address[] calldata grantees_
) external virtual onlyOwner {
if (
roleNames_.length != grantees_.length ||
roleNames_.length != slugs_.length
) revert UnequalArrayLengths();
uint256 totalRoles = roleNames_.length;
for (uint256 index = 0; index < totalRoles; ) {
if (slugs_[index] > 0)
_revokeRoleWithSlug(
roleNames_[index],
slugs_[index],
grantees_[index]
);
else _revokeRole(roleNames_[index], grantees_[index]);
// inputs are controlled by owner
unchecked {
++index;
}
}
}
function _grantRoleWithSlug(
bytes32 roleName_,
uint32 chainSlug_,
address grantee_
) internal {
_grantRole(keccak256(abi.encode(roleName_, chainSlug_)), grantee_);
}
/**
* @dev Checks if an address has a role based on the role name and chain slug.
* @param roleName_ The name of the role.
* @param chainSlug_ The chain slug associated with the role.
* @param address_ The address to check for the role.
* @return A boolean indicating whether the address has the specified role.
*/
function hasRoleWithSlug(
bytes32 roleName_,
uint32 chainSlug_,
address address_
) external view returns (bool) {
return _hasRoleWithSlug(roleName_, chainSlug_, address_);
}
function _hasRoleWithSlug(
bytes32 roleName_,
uint32 chainSlug_,
address address_
) internal view returns (bool) {
return _hasRole(keccak256(abi.encode(roleName_, chainSlug_)), address_);
}
/**
* @dev Revokes roles from an address
* @param roleName_ The names of the roles to revoke.
* @param chainSlug_ The chain slug associated with the role.
* @param grantee_ The addresses to be revoked the roles.
*/
function revokeRoleWithSlug(
bytes32 roleName_,
uint32 chainSlug_,
address grantee_
) external virtual onlyOwner {
_revokeRoleWithSlug(roleName_, chainSlug_, grantee_);
}
function _revokeRoleWithSlug(
bytes32 roleName_,
uint32 chainSlug_,
address revokee_
) internal {
_revokeRole(keccak256(abi.encode(roleName_, chainSlug_)), revokee_);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
// contains role hashes used in socket dl for various different operations
// used to rescue funds
bytes32 constant RESCUE_ROLE = keccak256("RESCUE_ROLE");
// used to withdraw fees
bytes32 constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE");
// used to trip switchboards
bytes32 constant TRIP_ROLE = keccak256("TRIP_ROLE");
// used to un trip switchboards
bytes32 constant UN_TRIP_ROLE = keccak256("UN_TRIP_ROLE");
// used by governance
bytes32 constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
//used by executors which executes message at destination
bytes32 constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
// used by transmitters who seal and propose packets in socket
bytes32 constant TRANSMITTER_ROLE = keccak256("TRANSMITTER_ROLE");
// used by switchboard watchers who work against transmitters
bytes32 constant WATCHER_ROLE = keccak256("WATCHER_ROLE");
// used by fee updaters responsible for updating fees at switchboards, transmit manager and execution manager
bytes32 constant FEES_UPDATER_ROLE = keccak256("FEES_UPDATER_ROLE");
// 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);
}
}
pragma solidity ^0.8.0;
import {RLPReader} from "./RLPReader.sol";
library ExitPayloadReader {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
uint8 constant WORD_SIZE = 32;
struct ExitPayload {
RLPReader.RLPItem[] data;
}
struct Receipt {
RLPReader.RLPItem[] data;
bytes raw;
uint256 logIndex;
}
struct Log {
RLPReader.RLPItem data;
RLPReader.RLPItem[] list;
}
struct LogTopics {
RLPReader.RLPItem[] data;
}
// copy paste of private copy() from RLPReader to avoid changing of existing contracts
function copy(
uint256 src,
uint256 dest,
uint256 len
) private pure {
if (len == 0) return;
// copy as many word sizes as possible
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
}
if (len == 0) return;
// left over bytes. Mask is used to remove unwanted bytes from the word
uint256 mask = 256**(WORD_SIZE - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask)) // zero out src
let destpart := and(mload(dest), mask) // retrieve the bytes
mstore(dest, or(destpart, srcpart))
}
}
function toExitPayload(bytes memory data) internal pure returns (ExitPayload memory) {
RLPReader.RLPItem[] memory payloadData = data.toRlpItem().toList();
return ExitPayload(payloadData);
}
function getHeaderNumber(ExitPayload memory payload) internal pure returns (uint256) {
return payload.data[0].toUint();
}
function getBlockProof(ExitPayload memory payload) internal pure returns (bytes memory) {
return payload.data[1].toBytes();
}
function getBlockNumber(ExitPayload memory payload) internal pure returns (uint256) {
return payload.data[2].toUint();
}
function getBlockTime(ExitPayload memory payload) internal pure returns (uint256) {
return payload.data[3].toUint();
}
function getTxRoot(ExitPayload memory payload) internal pure returns (bytes32) {
return bytes32(payload.data[4].toUint());
}
function getReceiptRoot(ExitPayload memory payload) internal pure returns (bytes32) {
return bytes32(payload.data[5].toUint());
}
function getReceipt(ExitPayload memory payload) internal pure returns (Receipt memory receipt) {
receipt.raw = payload.data[6].toBytes();
RLPReader.RLPItem memory receiptItem = receipt.raw.toRlpItem();
if (receiptItem.isList()) {
// legacy tx
receipt.data = receiptItem.toList();
} else {
// pop first byte before parsing receipt
bytes memory typedBytes = receipt.raw;
bytes memory result = new bytes(typedBytes.length - 1);
uint256 srcPtr;
uint256 destPtr;
assembly {
srcPtr := add(33, typedBytes)
destPtr := add(0x20, result)
}
copy(srcPtr, destPtr, result.length);
receipt.data = result.toRlpItem().toList();
}
receipt.logIndex = getReceiptLogIndex(payload);
return receipt;
}
function getReceiptProof(ExitPayload memory payload) internal pure returns (bytes memory) {
return payload.data[7].toBytes();
}
function getBranchMaskAsBytes(ExitPayload memory payload) internal pure returns (bytes memory) {
return payload.data[8].toBytes();
}
function getBranchMaskAsUint(ExitPayload memory payload) internal pure returns (uint256) {
return payload.data[8].toUint();
}
function getReceiptLogIndex(ExitPayload memory payload) internal pure returns (uint256) {
return payload.data[9].toUint();
}
// Receipt methods
function toBytes(Receipt memory receipt) internal pure returns (bytes memory) {
return receipt.raw;
}
function getLog(Receipt memory receipt) internal pure returns (Log memory) {
RLPReader.RLPItem memory logData = receipt.data[3].toList()[receipt.logIndex];
return Log(logData, logData.toList());
}
// Log methods
function getEmitter(Log memory log) internal pure returns (address) {
return RLPReader.toAddress(log.list[0]);
}
function getTopics(Log memory log) internal pure returns (LogTopics memory) {
return LogTopics(log.list[1].toList());
}
function getData(Log memory log) internal pure returns (bytes memory) {
return log.list[2].toBytes();
}
function toRlpBytes(Log memory log) internal pure returns (bytes memory) {
return log.data.toRlpBytes();
}
// LogTopics methods
function getField(LogTopics memory topics, uint256 index) internal pure returns (RLPReader.RLPItem memory) {
return topics.data[index];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {RLPReader} from "../lib/RLPReader.sol";
import {MerklePatriciaProof} from "../lib/MerklePatriciaProof.sol";
import {Merkle} from "../lib/Merkle.sol";
import "../lib/ExitPayloadReader.sol";
interface IFxStateSender {
function sendMessageToChild(address _receiver, bytes calldata _data) external;
}
contract ICheckpointManager {
struct HeaderBlock {
bytes32 root;
uint256 start;
uint256 end;
uint256 createdAt;
address proposer;
}
/**
* @notice mapping of checkpoint header numbers to block details
* @dev These checkpoints are submited by plasma contracts
*/
mapping(uint256 => HeaderBlock) public headerBlocks;
}
abstract contract FxBaseRootTunnel {
using RLPReader for RLPReader.RLPItem;
using Merkle for bytes32;
using ExitPayloadReader for bytes;
using ExitPayloadReader for ExitPayloadReader.ExitPayload;
using ExitPayloadReader for ExitPayloadReader.Log;
using ExitPayloadReader for ExitPayloadReader.LogTopics;
using ExitPayloadReader for ExitPayloadReader.Receipt;
// keccak256(MessageSent(bytes))
bytes32 public constant SEND_MESSAGE_EVENT_SIG = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036;
// state sender contract
IFxStateSender public fxRoot;
// root chain manager
ICheckpointManager public checkpointManager;
// child tunnel contract which receives and sends messages
address public fxChildTunnel;
// storage to avoid duplicate exits
mapping(bytes32 => bool) public processedExits;
constructor(address _checkpointManager, address _fxRoot) {
checkpointManager = ICheckpointManager(_checkpointManager);
fxRoot = IFxStateSender(_fxRoot);
}
// set fxChildTunnel if not set already
function setFxChildTunnel(address _fxChildTunnel) public virtual {
require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET");
fxChildTunnel = _fxChildTunnel;
}
/**
* @notice Send bytes message to Child Tunnel
* @param message bytes message that will be sent to Child Tunnel
* some message examples -
* abi.encode(tokenId);
* abi.encode(tokenId, tokenMetadata);
* abi.encode(messageType, messageData);
*/
function _sendMessageToChild(bytes memory message) internal {
fxRoot.sendMessageToChild(fxChildTunnel, message);
}
function _validateAndExtractMessage(bytes memory inputData) internal returns (bytes memory) {
ExitPayloadReader.ExitPayload memory payload = inputData.toExitPayload();
bytes memory branchMaskBytes = payload.getBranchMaskAsBytes();
uint256 blockNumber = payload.getBlockNumber();
// checking if exit has already been processed
// unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex)
bytes32 exitHash = keccak256(
abi.encodePacked(
blockNumber,
// first 2 nibbles are dropped while generating nibble array
// this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only)
// so converting to nibble array and then hashing it
MerklePatriciaProof._getNibbleArray(branchMaskBytes),
payload.getReceiptLogIndex()
)
);
require(processedExits[exitHash] == false, "FxRootTunnel: EXIT_ALREADY_PROCESSED");
processedExits[exitHash] = true;
ExitPayloadReader.Receipt memory receipt = payload.getReceipt();
ExitPayloadReader.Log memory log = receipt.getLog();
// check child tunnel
require(fxChildTunnel == log.getEmitter(), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL");
bytes32 receiptRoot = payload.getReceiptRoot();
// verify receipt inclusion
require(
MerklePatriciaProof.verify(receipt.toBytes(), branchMaskBytes, payload.getReceiptProof(), receiptRoot),
"FxRootTunnel: INVALID_RECEIPT_PROOF"
);
// verify checkpoint inclusion
_checkBlockMembershipInCheckpoint(
blockNumber,
payload.getBlockTime(),
payload.getTxRoot(),
receiptRoot,
payload.getHeaderNumber(),
payload.getBlockProof()
);
ExitPayloadReader.LogTopics memory topics = log.getTopics();
require(
bytes32(topics.getField(0).toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig
"FxRootTunnel: INVALID_SIGNATURE"
);
// received message data
bytes memory message = abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get message
return message;
}
function _checkBlockMembershipInCheckpoint(
uint256 blockNumber,
uint256 blockTime,
bytes32 txRoot,
bytes32 receiptRoot,
uint256 headerNumber,
bytes memory blockProof
) private view {
(bytes32 headerRoot, uint256 startBlock, , uint256 createdAt, ) = checkpointManager.headerBlocks(headerNumber);
require(
keccak256(abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)).checkMembership(
blockNumber - startBlock,
headerRoot,
blockProof
),
"FxRootTunnel: INVALID_HEADER"
);
}
/**
* @notice receive message from L2 to L1, validated by proof
* @dev This function verifies if the transaction actually happened on child chain
*
* @param inputData RLP encoded data of the reference tx containing following list of fields
* 0 - headerNumber - Checkpoint header block number containing the reference tx
* 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root
* 2 - blockNumber - Block number containing the reference tx on child chain
* 3 - blockTime - Reference tx block time
* 4 - txRoot - Transactions root of block
* 5 - receiptRoot - Receipts root of block
* 6 - receipt - Receipt of the reference transaction
* 7 - receiptProof - Merkle proof of the reference receipt
* 8 - branchMask - 32 bits denoting the path of receipt in merkle tree
* 9 - receiptLogIndex - Log Index to read from the receipt
*/
function receiveMessage(bytes memory inputData) public virtual {
bytes memory message = _validateAndExtractMessage(inputData);
_processMessageFromChild(message);
}
/**
* @notice Process message received from Child Tunnel
* @dev function needs to be implemented to handle message as per requirement
* This is called by receiveMessage function.
* Since it is called via a system call, any event will not be emitted during its execution.
* @param message bytes message that was sent from Child Tunnel
*/
function _processMessageFromChild(bytes memory message) internal virtual;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
/**
* @title ICapacitor
* @dev Interface for a Capacitor contract that stores and manages messages in packets
*/
interface ICapacitor {
/**
* @notice adds the packed message to a packet
* @dev this should be only executable by socket
* @param packedMessage the message packed with payload, fees and config
*/
function addPackedMessage(bytes32 packedMessage) external;
/**
* @notice returns the latest packet details which needs to be sealed
* @return root root hash of the latest packet which is not yet sealed
* @return packetCount latest packet id which is not yet sealed
*/
function getNextPacketToBeSealed()
external
view
returns (bytes32 root, uint64 packetCount);
/**
* @notice returns the root of packet for given id
* @param id the id assigned to packet
* @return root root hash corresponding to given id
*/
function getRootByCount(uint64 id) external view returns (bytes32 root);
/**
* @notice returns the maxPacketLength
* @return maxPacketLength of the capacitor
*/
function getMaxPacketLength()
external
view
returns (uint256 maxPacketLength);
/**
* @notice seals the packet
* @dev indicates the packet is ready to be shipped and no more messages can be added now.
* @dev this should be called by socket only
* @param batchSize_ used with packet batching capacitors
* @return root root hash of the packet
* @return packetCount id of the packed sealed
*/
function sealPacket(
uint256 batchSize_
) external returns (bytes32 root, uint64 packetCount);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
/**
* @title Execution Manager Interface
* @dev This interface defines the functions for managing and executing transactions on external chains
* @dev It is also responsible for collecting all the socket fees, which can then be pulled by others
*/
interface IExecutionManager {
/**
* @notice Returns the executor of the packed message and whether the executor is authorized
* @param packedMessage The message packed with payload, fees and config
* @param sig The signature of the message
* @return The address of the executor and a boolean indicating if the executor is authorized
*/
function isExecutor(
bytes32 packedMessage,
bytes memory sig
) external view returns (address, bool);
/**
* @notice Pays the fees for executing a transaction on the external chain
* @dev This function is payable and assumes the socket is going to send correct amount of fees.
* @param minMsgGasLimit_ The minimum gas limit for the transaction
* @param payloadSize_ The payload size in bytes
* @param executionParams_ Extra params for execution
* @param transmissionParams_ Extra params for transmission
* @param siblingChainSlug_ Sibling chain identifier
* @param switchboardFees_ fee charged by switchboard for processing transaction
* @param verificationOverheadFees_ fee charged for verifying transaction
* @param transmitManager_ The transmitManager address
* @param switchboard_ The switchboard address
* @param maxPacketLength_ The maxPacketLength for the capacitor
*/
function payAndCheckFees(
uint256 minMsgGasLimit_,
uint256 payloadSize_,
bytes32 executionParams_,
bytes32 transmissionParams_,
uint32 siblingChainSlug_,
uint128 switchboardFees_,
uint128 verificationOverheadFees_,
address transmitManager_,
address switchboard_,
uint256 maxPacketLength_
) external payable returns (uint128, uint128);
/**
* @notice Returns the minimum fees required for executing a transaction on the external chain
* @param minMsgGasLimit_ minMsgGasLimit_
* @param siblingChainSlug_ The destination slug
* @return The minimum fees required for executing the transaction
*/
function getMinFees(
uint256 minMsgGasLimit_,
uint256 payloadSize_,
bytes32 executionParams_,
uint32 siblingChainSlug_
) external view returns (uint128);
/**
* @notice function for getting the minimum fees required for executing and transmitting a cross-chain transaction
* @dev this function is called at source to calculate the execution cost.
* @param payloadSize_ byte length of payload. Currently only used to check max length, later on will be used for fees calculation.
* @param executionParams_ Can be used for providing extra information. Currently used for msgValue
* @param siblingChainSlug_ Sibling chain identifier
* @return minExecutionFee : Minimum fees required for executing the transaction
*/
function getExecutionTransmissionMinFees(
uint256 minMsgGasLimit_,
uint256 payloadSize_,
bytes32 executionParams_,
bytes32 transmissionParams_,
uint32 siblingChainSlug_,
address transmitManager_
) external view returns (uint128, uint128);
/**
* @notice Updates the execution fees for an executor and message ID
* @param executor The executor address
* @param executionFees The execution fees to update
* @param msgId The ID of the message
*/
function updateExecutionFees(
address executor,
uint128 executionFees,
bytes32 msgId
) external;
/**
* @notice updates the transmission fee
* @param remoteChainSlug_ sibling chain identifier
* @param transmitMinFees_ transmission fees collected
*/
function setTransmissionMinFees(
uint32 remoteChainSlug_,
uint128 transmitMinFees_
) external;
/**
* @notice sets the minimum execution fees required for executing at `siblingChainSlug_`
* @dev this function currently sets the price for a constant msg gas limit and payload size
* @param nonce_ incremental id to prevent signature replay
* @param siblingChainSlug_ sibling chain identifier
* @param executionFees_ total fees where price in destination native token is converted to source native tokens
* @param signature_ signature of fee updater
*/
function setExecutionFees(
uint256 nonce_,
uint32 siblingChainSlug_,
uint128 executionFees_,
bytes calldata signature_
) external;
/**
* @notice sets the min limit for msg value for `siblingChainSlug_`
* @param nonce_ incremental id to prevent signature replay
* @param siblingChainSlug_ sibling chain identifier
* @param msgValueMinThreshold_ min msg value
* @param signature_ signature of fee updater
*/
function setMsgValueMinThreshold(
uint256 nonce_,
uint32 siblingChainSlug_,
uint256 msgValueMinThreshold_,
bytes calldata signature_
) external;
/**
* @notice sets the max limit for msg value for `siblingChainSlug_`
* @param nonce_ incremental id to prevent signature replay
* @param siblingChainSlug_ sibling chain identifier
* @param msgValueMaxThreshold_ max msg value
* @param signature_ signature of fee updater
*/
function setMsgValueMaxThreshold(
uint256 nonce_,
uint32 siblingChainSlug_,
uint256 msgValueMaxThreshold_,
bytes calldata signature_
) external;
/**
* @notice sets the relative token price for `siblingChainSlug_`
* @dev this function is expected to be called frequently to match the original prices
* @param nonce_ incremental id to prevent signature replay
* @param siblingChainSlug_ sibling chain identifier
* @param relativeNativeTokenPrice_ relative price
* @param signature_ signature of fee updater
*/
function setRelativeNativeTokenPrice(
uint256 nonce_,
uint32 siblingChainSlug_,
uint256 relativeNativeTokenPrice_,
bytes calldata signature_
) external;
/**
* @notice called by socket while executing message to validate if the msg value provided is enough
* @param executionParams_ a bytes32 string where first byte gives param type (if value is 0 or not)
* and remaining bytes give the msg value needed
* @param msgValue_ msg.value to be sent with inbound
*/
function verifyParams(
bytes32 executionParams_,
uint256 msgValue_
) external view;
/**
* @notice withdraws switchboard fees from contract
* @param siblingChainSlug_ withdraw fees corresponding to this slug
* @param amount_ withdraw amount
*/
function withdrawSwitchboardFees(
uint32 siblingChainSlug_,
address switchboard_,
uint128 amount_
) external;
/**
* @dev this function gets the transmitManager address from the socket contract. If it is ever upgraded in socket,
* @dev remove the fees from executionManager first, and then upgrade address at socket.
* @notice withdraws transmission fees from contract
* @param siblingChainSlug_ withdraw fees corresponding to this slug
* @param amount_ withdraw amount
*/
function withdrawTransmissionFees(
uint32 siblingChainSlug_,
uint128 amount_
) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
/**
* @title Signature Verifier
* @notice Verifies the signatures and returns the address of signer recovered from the input signature or digest.
*/
interface ISignatureVerifier {
/**
* @notice returns the address of signer recovered from input signature and digest
*/
function recoverSigner(
bytes32 digest_,
bytes memory signature_
) external pure returns (address signer);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
import "./ITransmitManager.sol";
import "./IExecutionManager.sol";
/**
* @title ISocket
* @notice An interface for a cross-chain communication contract
* @dev This interface provides methods for transmitting and executing messages between chains,
* connecting a plug to a remote chain and setting up switchboards for the message transmission
* This interface also emits events for important operations such as message transmission, execution status,
* and plug connection
*/
interface ISocket {
/**
* @notice A struct containing fees required for message transmission and execution
* @param transmissionFees fees needed for transmission
* @param switchboardFees fees needed by switchboard
* @param executionFee fees needed for execution
*/
struct Fees {
uint128 transmissionFees;
uint128 executionFee;
uint128 switchboardFees;
}
/**
* @title MessageDetails
* @dev This struct defines the details of a message to be executed in a Decapacitor contract.
*/
struct MessageDetails {
// A unique identifier for the message.
bytes32 msgId;
// The fee to be paid for executing the message.
uint256 executionFee;
// The min amount of gas that can be used to execute the message.
uint256 minMsgGasLimit;
// The extra params which might provide msg value and additional info needed for message exec
bytes32 executionParams;
// The payload data to be executed in the message.
bytes payload;
}
/**
* @title ExecutionDetails
* @dev This struct defines the execution details
*/
struct ExecutionDetails {
// packet id
bytes32 packetId;
// proposal count
uint256 proposalCount;
// gas limit needed to execute inbound
uint256 executionGasLimit;
// proof data required by the Decapacitor contract to verify the message's authenticity
bytes decapacitorProof;
// signature of executor
bytes signature;
}
/**
* @notice emits the status of message after inbound call
* @param msgId msg id which is executed
*/
event ExecutionSuccess(bytes32 msgId);
/**
* @notice emits the config set by a plug for a remoteChainSlug
* @param plug address of plug on current chain
* @param siblingChainSlug sibling chain slug
* @param siblingPlug address of plug on sibling chain
* @param inboundSwitchboard inbound switchboard (select from registered options)
* @param outboundSwitchboard outbound switchboard (select from registered options)
* @param capacitor capacitor selected based on outbound switchboard
* @param decapacitor decapacitor selected based on inbound switchboard
*/
event PlugConnected(
address plug,
uint32 siblingChainSlug,
address siblingPlug,
address inboundSwitchboard,
address outboundSwitchboard,
address capacitor,
address decapacitor
);
/**
* @notice registers a message
* @dev Packs the message and includes it in a packet with capacitor
* @param remoteChainSlug_ the remote chain slug
* @param minMsgGasLimit_ the gas limit needed to execute the payload on remote
* @param payload_ the data which is needed by plug at inbound call on remote
*/
function outbound(
uint32 remoteChainSlug_,
uint256 minMsgGasLimit_,
bytes32 executionParams_,
bytes32 transmissionParams_,
bytes calldata payload_
) external payable returns (bytes32 msgId);
/**
* @notice executes a message
* @param executionDetails_ the packet details, proof and signature needed for message execution
* @param messageDetails_ the message details
*/
function execute(
ISocket.ExecutionDetails calldata executionDetails_,
ISocket.MessageDetails calldata messageDetails_
) external payable;
/**
* @notice seals data in capacitor for specific batchSize
* @param batchSize_ size of batch to be sealed
* @param capacitorAddress_ address of capacitor
* @param signature_ signed Data needed for verification
*/
function seal(
uint256 batchSize_,
address capacitorAddress_,
bytes calldata signature_
) external payable;
/**
* @notice proposes a packet
* @param packetId_ packet id
* @param root_ root data
* @param switchboard_ The address of switchboard for which this packet is proposed
* @param signature_ signed Data needed for verification
*/
function proposeForSwitchboard(
bytes32 packetId_,
bytes32 root_,
address switchboard_,
bytes calldata signature_
) external payable;
/**
* @notice sets the config specific to the plug
* @param siblingChainSlug_ the sibling chain slug
* @param siblingPlug_ address of plug present at sibling chain to call inbound
* @param inboundSwitchboard_ the address of switchboard to use for receiving messages
* @param outboundSwitchboard_ the address of switchboard to use for sending messages
*/
function connect(
uint32 siblingChainSlug_,
address siblingPlug_,
address inboundSwitchboard_,
address outboundSwitchboard_
) external;
/**
* @notice deploy capacitor and decapacitor for a switchboard with a specified max packet length, sibling chain slug, and capacitor type.
* @param siblingChainSlug_ The slug of the sibling chain that the switchboard is registered with.
* @param maxPacketLength_ The maximum length of a packet allowed by the switchboard.
* @param capacitorType_ The type of capacitor that the switchboard uses.
* @param siblingSwitchboard_ The switchboard address deployed on `siblingChainSlug_`
*/
function registerSwitchboardForSibling(
uint32 siblingChainSlug_,
uint256 maxPacketLength_,
uint256 capacitorType_,
address siblingSwitchboard_
) external returns (address capacitor, address decapacitor);
/**
* @notice Emits the sibling switchboard for given `siblingChainSlug_`.
* @dev This function is expected to be only called by switchboard.
* @dev the event emitted is tracked by transmitters to decide which switchboard a packet should be proposed on
* @param siblingChainSlug_ The slug of the sibling chain
* @param siblingSwitchboard_ The switchboard address deployed on `siblingChainSlug_`
*/
function useSiblingSwitchboard(
uint32 siblingChainSlug_,
address siblingSwitchboard_
) external;
/**
* @notice Retrieves the packet id roots for a specified packet id.
* @param packetId_ The packet id for which to retrieve the root.
* @param proposalCount_ The proposal id for packetId_ for which to retrieve the root.
* @param switchboard_ The address of switchboard for which this packet is proposed
* @return The packet id roots for the specified packet id.
*/
function packetIdRoots(
bytes32 packetId_,
uint256 proposalCount_,
address switchboard_
) external view returns (bytes32);
/**
* @notice Retrieves the latest proposalCount for a packet id.
* @return The proposal count for the specified packet id.
*/
function proposalCount(bytes32 packetId_) external view returns (uint256);
/**
* @notice Retrieves the minimum fees required for a message with a specified gas limit and destination chain.
* @param minMsgGasLimit_ The gas limit of the message.
* @param remoteChainSlug_ The slug of the destination chain for the message.
* @param plug_ The address of the plug through which the message is sent.
* @return totalFees The minimum fees required for the specified message.
*/
function getMinFees(
uint256 minMsgGasLimit_,
uint256 payloadSize_,
bytes32 executionParams_,
bytes32 transmissionParams_,
uint32 remoteChainSlug_,
address plug_
) external view returns (uint256 totalFees);
/// return instance of transmit manager
function transmitManager__() external view returns (ITransmitManager);
/// return instance of execution manager
function executionManager__() external view returns (IExecutionManager);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
/**
* @title ISwitchboard
* @dev The interface for a switchboard contract that is responsible for verification of packets between
* different blockchain networks.
*/
interface ISwitchboard {
/**
* @notice Registers itself in Socket for given `siblingChainSlug_`.
* @dev This function is expected to be only called by admin as it handles the capacitor config for given chain
* @param siblingChainSlug_ The slug of the sibling chain to register switchboard with.
* @param maxPacketLength_ The maximum length of a packet allowed by the switchboard.
* @param capacitorType_ The type of capacitor that the switchboard uses.
* @param initialPacketCount_ The packet count at the time of registering switchboard. Packets with packet count below this won't be allowed
* @param siblingSwitchboard_ The switchboard address deployed on `siblingChainSlug_`
*/
function registerSiblingSlug(
uint32 siblingChainSlug_,
uint256 maxPacketLength_,
uint256 capacitorType_,
uint256 initialPacketCount_,
address siblingSwitchboard_
) external;
/**
* @notice Updates the sibling switchboard for given `siblingChainSlug_`.
* @dev This function is expected to be only called by admin
* @param siblingChainSlug_ The slug of the sibling chain to register switchboard with.
* @param siblingSwitchboard_ The switchboard address deployed on `siblingChainSlug_`
*/
function updateSibling(
uint32 siblingChainSlug_,
address siblingSwitchboard_
) external;
/**
* @notice Checks if a packet can be allowed to go through the switchboard.
* @param root the packet root.
* @param packetId The unique identifier for the packet.
* @param proposalCount The unique identifier for a proposal for the packet.
* @param srcChainSlug The unique identifier for the source chain of the packet.
* @param proposeTime The time when the packet was proposed.
* @return A boolean indicating whether the packet is allowed to go through the switchboard or not.
*/
function allowPacket(
bytes32 root,
bytes32 packetId,
uint256 proposalCount,
uint32 srcChainSlug,
uint256 proposeTime
) external view returns (bool);
/**
* @notice Retrieves the minimum fees required for the destination chain to process the packet.
* @param dstChainSlug the unique identifier for the destination chain of the packet.
* @return switchboardFee the switchboard fee required for the destination chain to process the packet.
* @return verificationOverheadFees the verification fee required for the destination chain to process the packet.
*/
function getMinFees(
uint32 dstChainSlug
)
external
view
returns (uint128 switchboardFee, uint128 verificationOverheadFees);
/**
* @notice Receives the fees for processing of packet.
* @param siblingChainSlug_ the chain slug of the sibling chain.
*/
function receiveFees(uint32 siblingChainSlug_) external payable;
/**
* @notice Sets the minimum fees required for the destination chain to process the packet.
* @param nonce_ the nonce of fee Updater to avoid replay.
* @param dstChainSlug_ the unique identifier for the destination chain.
* @param switchboardFees_ the switchboard fee required for the destination chain to process the packet.
* @param verificationOverheadFees_ the verification fee required for the destination chain to process the packet.
* @param signature_ the signature of the request.
* @dev not important to override in all switchboards
*/
function setFees(
uint256 nonce_,
uint32 dstChainSlug_,
uint128 switchboardFees_,
uint128 verificationOverheadFees_,
bytes calldata signature_
) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
/**
* @title ITransmitManager
* @dev The interface for a transmit manager contract
*/
interface ITransmitManager {
/**
* @notice Checks if a given transmitter is authorized to send transactions to the destination chain.
* @param siblingSlug The unique identifier for the sibling chain.
* @param digest The digest of the message being signed.
* @param signature The signature of the message being signed.
* @return The address of the transmitter and a boolean indicating whether the transmitter is authorized or not.
*/
function checkTransmitter(
uint32 siblingSlug,
bytes32 digest,
bytes calldata signature
) external view returns (address, bool);
/**
* @notice sets the transmission fee needed to transmit message to given `siblingSlug_`
* @dev recovered address should add have feeUpdater role for `siblingSlug_`
* @param nonce_ The incremental nonce to prevent signature replay
* @param siblingSlug_ sibling id for which fee updater is registered
* @param transmissionFees_ digest which is signed by transmitter
* @param signature_ signature
*/
function setTransmissionFees(
uint256 nonce_,
uint32 siblingSlug_,
uint128 transmissionFees_,
bytes calldata signature_
) external;
/**
* @notice receives fees from Execution manager
* @dev this function can be used to keep track of fees received for each slug
* @param siblingSlug_ sibling id for which fee updater is registered
*/
function receiveFees(uint32 siblingSlug_) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library Merkle {
function checkMembership(
bytes32 leaf,
uint256 index,
bytes32 rootHash,
bytes memory proof
) internal pure returns (bool) {
require(proof.length % 32 == 0, "Invalid proof length");
uint256 proofHeight = proof.length / 32;
// Proof of size n means, height of the tree is n+1.
// In a tree of height n+1, max #leafs possible is 2 ^ n
require(index < 2**proofHeight, "Leaf index is too big");
bytes32 proofElement;
bytes32 computedHash = leaf;
for (uint256 i = 32; i <= proof.length; i += 32) {
assembly {
proofElement := mload(add(proof, i))
}
if (index % 2 == 0) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
index = index / 2;
}
return computedHash == rootHash;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {RLPReader} from "./RLPReader.sol";
library MerklePatriciaProof {
/*
* @dev Verifies a merkle patricia proof.
* @param value The terminating value in the trie.
* @param encodedPath The path in the trie leading to value.
* @param rlpParentNodes The rlp encoded stack of nodes.
* @param root The root hash of the trie.
* @return The boolean validity of the proof.
*/
function verify(
bytes memory value,
bytes memory encodedPath,
bytes memory rlpParentNodes,
bytes32 root
) internal pure returns (bool) {
RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes);
RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item);
bytes memory currentNode;
RLPReader.RLPItem[] memory currentNodeList;
bytes32 nodeKey = root;
uint256 pathPtr = 0;
bytes memory path = _getNibbleArray(encodedPath);
if (path.length == 0) {
return false;
}
for (uint256 i = 0; i < parentNodes.length; i++) {
if (pathPtr > path.length) {
return false;
}
currentNode = RLPReader.toRlpBytes(parentNodes[i]);
if (nodeKey != keccak256(currentNode)) {
return false;
}
currentNodeList = RLPReader.toList(parentNodes[i]);
if (currentNodeList.length == 17) {
if (pathPtr == path.length) {
if (keccak256(RLPReader.toBytes(currentNodeList[16])) == keccak256(value)) {
return true;
} else {
return false;
}
}
uint8 nextPathNibble = uint8(path[pathPtr]);
if (nextPathNibble > 16) {
return false;
}
nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[nextPathNibble]));
pathPtr += 1;
} else if (currentNodeList.length == 2) {
uint256 traversed = _nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr);
if (pathPtr + traversed == path.length) {
//leaf node
if (keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value)) {
return true;
} else {
return false;
}
}
//extension node
if (traversed == 0) {
return false;
}
pathPtr += traversed;
nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1]));
} else {
return false;
}
}
}
function _nibblesToTraverse(
bytes memory encodedPartialPath,
bytes memory path,
uint256 pathPtr
) private pure returns (uint256) {
uint256 len = 0;
// encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath
// and slicedPath have elements that are each one hex character (1 nibble)
bytes memory partialPath = _getNibbleArray(encodedPartialPath);
bytes memory slicedPath = new bytes(partialPath.length);
// pathPtr counts nibbles in path
// partialPath.length is a number of nibbles
for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) {
bytes1 pathNibble = path[i];
slicedPath[i - pathPtr] = pathNibble;
}
if (keccak256(partialPath) == keccak256(slicedPath)) {
len = partialPath.length;
} else {
len = 0;
}
return len;
}
// bytes b must be hp encoded
function _getNibbleArray(bytes memory b) internal pure returns (bytes memory) {
bytes memory nibbles = "";
if (b.length > 0) {
uint8 offset;
uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b));
if (hpNibble == 1 || hpNibble == 3) {
nibbles = new bytes(b.length * 2 - 1);
bytes1 oddNibble = _getNthNibbleOfBytes(1, b);
nibbles[0] = oddNibble;
offset = 1;
} else {
nibbles = new bytes(b.length * 2 - 2);
offset = 0;
}
for (uint256 i = offset; i < nibbles.length; i++) {
nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b);
}
}
return nibbles;
}
function _getNthNibbleOfBytes(uint256 n, bytes memory str) private pure returns (bytes1) {
return bytes1(n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
import "../../interfaces/ISocket.sol";
import "../../interfaces/ISwitchboard.sol";
import "../../interfaces/ICapacitor.sol";
import "../../interfaces/ISignatureVerifier.sol";
import "../../interfaces/IExecutionManager.sol";
import "../../libraries/RescueFundsLib.sol";
import "../../utils/AccessControlExtended.sol";
import {GOVERNANCE_ROLE, RESCUE_ROLE, WITHDRAW_ROLE, TRIP_ROLE, UN_TRIP_ROLE, FEES_UPDATER_ROLE} from "../../utils/AccessRoles.sol";
import {TRIP_NATIVE_SIG_IDENTIFIER, UN_TRIP_NATIVE_SIG_IDENTIFIER, FEES_UPDATE_SIG_IDENTIFIER} from "../../utils/SigIdentifiers.sol";
/**
@title Native Switchboard Base Contract
@notice This contract serves as the base for the implementation of a switchboard for native cross-chain communication.
It provides the necessary functionalities to allow packets to be sent and received between chains and ensures proper handling
of fees, gas limits, and packet validation.
@dev This contract has access-controlled functions and connects to a capacitor contract that holds packets for the native bridge.
*/
abstract contract NativeSwitchboardBase is ISwitchboard, AccessControlExtended {
ISignatureVerifier public immutable signatureVerifier__;
ISocket public immutable socket__;
ICapacitor public capacitor__;
uint32 public immutable chainSlug;
/**
* @dev Flag that indicates if the global fuse is tripped, meaning no more packets can be sent.
*/
bool public isGlobalTipped;
/**
* @dev Flag that indicates if the switchboard is registered and its capacitor has been assigned.
*/
bool public isInitialized;
// This is to prevent attacks with sending messages for chain before the switchboard is registered for them.
uint256 initialPacketCount;
/**
* @dev Address of the remote native switchboard.
*/
address public remoteNativeSwitchboard;
// Per packet fees used to compensate operator to send packets via native bridge.
uint128 public switchboardFees;
// Per message fees paid to executor for verification overhead.
uint128 public verificationOverheadFees;
/**
* @dev Stores the roots received from native bridge.
*/
mapping(bytes32 => bytes32) public packetIdToRoot;
/**
* @dev incrementing nonce used for signatures of fee updater, tripper, untripper
*/
mapping(address => uint256) public nextNonce;
/**
* @dev Event emitted when the switchboard trip status changes
*/
event GlobalTripChanged(bool isGlobalTipped);
/**
* @dev This event is emitted when this switchboard wants to connect with its sibling on other chain.
* @param remoteNativeSwitchboard address of switchboard on sibling chain.
*/
event UpdatedRemoteNativeSwitchboard(address remoteNativeSwitchboard);
/**
* @dev Event emitted when a packet root relay via native bridge is initialised
* @param packetId The packet ID.
*/
event InitiatedNativeConfirmation(bytes32 packetId);
/**
* @dev Event emitted when a root is received via native bridge.
* @param packetId The unique identifier of the packet.
* @param root The root hash of the packet.
*/
event RootReceived(bytes32 packetId, bytes32 root);
/**
* @dev Emitted when a fees is set for switchboard
* @param switchboardFees switchboardFees
* @param verificationOverheadFees verificationOverheadFees
*/
event SwitchboardFeesSet(
uint256 switchboardFees,
uint256 verificationOverheadFees
);
/**
* @dev Error thrown when the fees provided are not enough to execute the transaction.
*/
error FeesNotEnough();
/**
* @dev Error thrown when the contract has already been initialized.
*/
error AlreadyInitialized();
/**
* @dev Error thrown when the root receive transaction is not sent by a valid sender. i.e. native bridge contract
*/
error InvalidSender();
/**
* @dev Error thrown when a root hash cannot be found for the given packet ID.
*/
error NoRootFound();
/**
* @dev Error thrown when the nonce of the signature is invalid.
*/
error InvalidNonce();
// Error thrown if fees are received from non execution manager.
error OnlyExecutionManager();
/**
* @dev Modifier to ensure that a function can only be called by the remote switchboard.
*/
modifier onlyRemoteSwitchboard() virtual;
/**
* @dev Constructor function for the Native switchboard contract.
* @param socket_ The address of socket.
* @param chainSlug_ The identifier of the chain the contract is deployed on.
* @param signatureVerifier_ signatureVerifier instance
*/
constructor(
address socket_,
uint32 chainSlug_,
ISignatureVerifier signatureVerifier_
) {
socket__ = ISocket(socket_);
chainSlug = chainSlug_;
signatureVerifier__ = signatureVerifier_;
}
/**
* @notice retrieves the root for a given packet ID from capacitor
* @param packetId_ packet ID
* @return root root associated with the given packet ID
* @dev Reverts with 'NoRootFound' error if no root is found for the given packet ID
*/
function _getRoot(bytes32 packetId_) internal view returns (bytes32 root) {
uint64 capacitorPacketCount = uint64(uint256(packetId_));
root = capacitor__.getRootByCount(capacitorPacketCount);
if (root == bytes32(0)) revert NoRootFound();
}
/**
* @notice records the root for a given packet ID sent by a remote switchboard via native bridge
* @dev this function is not used by polygon native bridge, it works by calling a different function.
* @param packetId_ packet ID
* @param root_ root for the given packet ID
*/
function receivePacket(
bytes32 packetId_,
bytes32 root_
) external onlyRemoteSwitchboard {
packetIdToRoot[packetId_] = root_;
emit RootReceived(packetId_, root_);
}
/**
* @inheritdoc ISwitchboard
*/
function allowPacket(
bytes32 root_,
bytes32 packetId_,
uint256,
uint32,
uint256
) external view override returns (bool) {
uint64 packetCount = uint64(uint256(packetId_));
if (isGlobalTipped) return false;
if (packetCount < initialPacketCount) return false;
if (packetIdToRoot[packetId_] != root_) return false;
return true;
}
/**
* @dev Get the minimum fees for a cross-chain transaction.
* @return switchboardFee_ The fee charged by the switchboard for the transaction.
* @return verificationFee_ The fee charged by the verifier for the transaction.
*/
function getMinFees(
uint32
)
external
view
override
returns (uint128 switchboardFee_, uint128 verificationFee_)
{
return (switchboardFees, verificationOverheadFees);
}
/**
* @inheritdoc ISwitchboard
*/
function setFees(
uint256 nonce_,
uint32,
uint128 switchboardFees_,
uint128 verificationOverheadFees_,
bytes calldata signature_
) external override {
address feesUpdater = signatureVerifier__.recoverSigner(
keccak256(
abi.encode(
FEES_UPDATE_SIG_IDENTIFIER,
address(this),
chainSlug,
nonce_,
switchboardFees_,
verificationOverheadFees_
)
),
signature_
);
_checkRole(FEES_UPDATER_ROLE, feesUpdater);
// Nonce is used by gated roles and we don't expect nonce to reach the max value of uint256
unchecked {
if (nonce_ != nextNonce[feesUpdater]++) revert InvalidNonce();
}
switchboardFees = switchboardFees_;
verificationOverheadFees = verificationOverheadFees_;
emit SwitchboardFeesSet(switchboardFees, verificationOverheadFees);
}
/**
* @inheritdoc ISwitchboard
*/
function registerSiblingSlug(
uint32 siblingChainSlug_,
uint256 maxPacketLength_,
uint256 capacitorType_,
uint256 initialPacketCount_,
address remoteNativeSwitchboard_
) external override onlyRole(GOVERNANCE_ROLE) {
if (isInitialized) revert AlreadyInitialized();
initialPacketCount = initialPacketCount_;
(address capacitor, ) = socket__.registerSwitchboardForSibling(
siblingChainSlug_,
maxPacketLength_,
capacitorType_,
remoteNativeSwitchboard_
);
isInitialized = true;
capacitor__ = ICapacitor(capacitor);
remoteNativeSwitchboard = remoteNativeSwitchboard_;
}
/**
* @notice Updates the sibling switchboard for given `siblingChainSlug_`.
* @dev This function is expected to be only called by admin
* @param siblingChainSlug_ The slug of the sibling chain to register switchboard with.
* @param remoteNativeSwitchboard_ The switchboard address deployed on `siblingChainSlug_`
*/
function updateSibling(
uint32 siblingChainSlug_,
address remoteNativeSwitchboard_
) external onlyRole(GOVERNANCE_ROLE) {
// signal to socket
socket__.useSiblingSwitchboard(
siblingChainSlug_,
remoteNativeSwitchboard_
);
// use address while relaying via native bridge
remoteNativeSwitchboard = remoteNativeSwitchboard_;
emit UpdatedRemoteNativeSwitchboard(remoteNativeSwitchboard_);
}
/**
* @notice Allows to trip the global fuse and prevent the switchboard to process packets
* @dev The function recovers the signer from the given signature and verifies if the signer has the TRIP_ROLE.
* The nonce must be equal to the next nonce of the caller. If the caller doesn't have the TRIP_ROLE or the nonce
* is incorrect, it will revert.
* Once the function is successful, the isGlobalTipped variable is set to true and the GlobalTripChanged event is emitted.
* @param nonce_ The nonce of the caller.
* @param signature_ The signature of the message
*/
function tripGlobal(uint256 nonce_, bytes memory signature_) external {
address tripper = signatureVerifier__.recoverSigner(
// it includes trip status at the end
keccak256(
abi.encode(
TRIP_NATIVE_SIG_IDENTIFIER,
address(this),
chainSlug,
nonce_,
true
)
),
signature_
);
_checkRole(TRIP_ROLE, tripper);
// Nonce is used by gated roles and we don't expect nonce to reach the max value of uint256
unchecked {
if (nonce_ != nextNonce[tripper]++) revert InvalidNonce();
}
isGlobalTipped = true;
emit GlobalTripChanged(true);
}
/**
* @notice Allows a untripper to un trip the switchboard by providing a signature and a nonce.
* @dev To un trip, the untripper must have the UN_TRIP_ROLE.
* @param nonce_ The nonce to prevent replay attacks.
* @param signature_ The signature created by the untripper.
*/
function unTrip(uint256 nonce_, bytes memory signature_) external {
address untripper = signatureVerifier__.recoverSigner(
// it includes trip status at the end
keccak256(
abi.encode(
UN_TRIP_NATIVE_SIG_IDENTIFIER,
address(this),
chainSlug,
nonce_,
false
)
),
signature_
);
_checkRole(UN_TRIP_ROLE, untripper);
// Nonce is used by gated roles and we don't expect nonce to reach the max value of uint256
unchecked {
if (nonce_ != nextNonce[untripper]++) revert InvalidNonce();
}
isGlobalTipped = false;
emit GlobalTripChanged(false);
}
/**
* @notice Allows the withdrawal of fees by the account with the specified address.
* @param withdrawTo_ The address of the account to withdraw fees to.
* @dev The caller must have the WITHDRAW_ROLE.
*/
function withdrawFees(
address withdrawTo_
) external onlyRole(WITHDRAW_ROLE) {
if (withdrawTo_ == address(0)) revert ZeroAddress();
SafeTransferLib.safeTransferETH(withdrawTo_, address(this).balance);
}
/**
* @notice Rescues funds from the contract if they are locked by mistake.
* @param token_ The address of the token contract.
* @param rescueTo_ The address where rescued tokens need to be sent.
* @param amount_ The amount of tokens to be rescued.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
/**
* @inheritdoc ISwitchboard
*/
function receiveFees(uint32) external payable override {
if (msg.sender != address(socket__.executionManager__()))
revert OnlyExecutionManager();
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
/**
* @title Ownable
* @dev The Ownable contract provides a simple way to manage ownership of a contract
* and allows for ownership to be transferred to a nominated address.
*/
abstract contract Ownable {
address private _owner;
address private _nominee;
event OwnerNominated(address indexed nominee);
event OwnerClaimed(address indexed claimer);
error OnlyOwner();
error OnlyNominee();
/**
* @dev Sets the contract's owner to the address that is passed to the constructor.
*/
constructor(address owner_) {
_claimOwner(owner_);
}
/**
* @dev Modifier that restricts access to only the contract's owner.
* Throws an error if the caller is not the owner.
*/
modifier onlyOwner() {
if (msg.sender != _owner) revert OnlyOwner();
_;
}
/**
* @dev Returns the current owner of the contract.
*/
function owner() external view returns (address) {
return _owner;
}
/**
* @dev Returns the current nominee for ownership of the contract.
*/
function nominee() external view returns (address) {
return _nominee;
}
/**
* @dev Allows the current owner to nominate a new owner for the contract.
* Throws an error if the caller is not the owner.
* Emits an `OwnerNominated` event with the address of the nominee.
*/
function nominateOwner(address nominee_) external {
if (msg.sender != _owner) revert OnlyOwner();
_nominee = nominee_;
emit OwnerNominated(_nominee);
}
/**
* @dev Allows the nominated owner to claim ownership of the contract.
* Throws an error if the caller is not the nominee.
* Sets the nominated owner as the new owner of the contract.
* Emits an `OwnerClaimed` event with the address of the new owner.
*/
function claimOwner() external {
if (msg.sender != _nominee) revert OnlyNominee();
_claimOwner(msg.sender);
}
/**
* @dev Internal function that sets the owner of the contract to the specified address
* and sets the nominee to address(0).
*/
function _claimOwner(address claimer_) internal {
_owner = claimer_;
_nominee = address(0);
emit OwnerClaimed(claimer_);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
import "lib/contracts/contracts/tunnel/FxBaseRootTunnel.sol";
import "./NativeSwitchboardBase.sol";
/**
* @title PolygonL1Switchboard
* @notice contract that facilitates cross-chain communication between Polygon and Ethereum mainnet.
* It is an implementation of the NativeSwitchboardBase contract and the FxBaseRootTunnel contract.
*/
contract PolygonL1Switchboard is NativeSwitchboardBase, FxBaseRootTunnel {
/**
* @notice This event is emitted when the fxChildTunnel address is set or updated.
* @param fxChildTunnel is the current fxChildTunnel address.
* @param newFxChildTunnel is the new fxChildTunnel address that was set.
*/
event FxChildTunnelSet(address fxChildTunnel, address newFxChildTunnel);
/**
* @notice This modifier overrides the onlyRemoteSwitchboard modifier in the NativeSwitchboardBase contract
*/
modifier onlyRemoteSwitchboard() override {
revert("ONLY_FX_CHILD");
_;
}
/**
* @notice This is the constructor function of the PolygonL1Switchboard contract.
* initializes the contract with the provided parameters.
* @param chainSlug_ is the identifier of the chain.
* @param checkpointManager_ is the address of the checkpoint manager contract.
* @param fxRoot_ is the address of the root contract.
* @param owner_ is the address of the contract owner.
* @param socket_ is the address of the Socket contract.
*/
constructor(
uint32 chainSlug_,
address checkpointManager_,
address fxRoot_,
address owner_,
address socket_,
ISignatureVerifier signatureVerifier_
)
AccessControlExtended(owner_)
NativeSwitchboardBase(socket_, chainSlug_, signatureVerifier_)
FxBaseRootTunnel(checkpointManager_, fxRoot_)
{}
/**
* @dev Initiates a native confirmation by encoding and sending a message to the child chain.
* @param packetId_ The packet ID to be confirmed.
*/
function initiateNativeConfirmation(bytes32 packetId_) external payable {
bytes memory data = _encodeRemoteCall(packetId_);
_sendMessageToChild(data);
emit InitiatedNativeConfirmation(packetId_);
}
/**
* @dev Internal function to encode the remote call.
* @param packetId_ The packet ID to encode.
* @return data The encoded data.
*/
function _encodeRemoteCall(
bytes32 packetId_
) internal view returns (bytes memory data) {
data = abi.encode(packetId_, _getRoot(packetId_));
}
/**
* @notice The _processMessageFromChild function is an internal function that processes a
* message received from a child contract.decodes the message to extract the packetId and root values
* and stores them in the packetIdToRoot mapping.
* @param message_ The message received from the child contract.
*/
function _processMessageFromChild(bytes memory message_) internal override {
(bytes32 packetId, bytes32 root) = abi.decode(
message_,
(bytes32, bytes32)
);
packetIdToRoot[packetId] = root;
emit RootReceived(packetId, root);
}
/**
* @notice Set the fxChildTunnel address if not set already.
* @param fxChildTunnel_ The new fxChildTunnel address to set.
* @dev The caller must have the GOVERNANCE_ROLE role.
*/
function setFxChildTunnel(
address fxChildTunnel_
) public override onlyRole(GOVERNANCE_ROLE) {
emit FxChildTunnelSet(fxChildTunnel, fxChildTunnel_);
fxChildTunnel = fxChildTunnel_;
}
}
/*
* @author Hamdi Allam hamdi.allam97@gmail.com
* Please reach out with any questions or concerns
*/
pragma solidity ^0.8.0;
library RLPReader {
uint8 constant STRING_SHORT_START = 0x80;
uint8 constant STRING_LONG_START = 0xb8;
uint8 constant LIST_SHORT_START = 0xc0;
uint8 constant LIST_LONG_START = 0xf8;
uint8 constant WORD_SIZE = 32;
struct RLPItem {
uint256 len;
uint256 memPtr;
}
struct Iterator {
RLPItem item; // Item that's being iterated over.
uint256 nextPtr; // Position of the next item in the list.
}
/*
* @dev Returns the next element in the iteration. Reverts if it has not next element.
* @param self The iterator.
* @return The next element in the iteration.
*/
function next(Iterator memory self) internal pure returns (RLPItem memory) {
require(hasNext(self));
uint256 ptr = self.nextPtr;
uint256 itemLength = _itemLength(ptr);
self.nextPtr = ptr + itemLength;
return RLPItem(itemLength, ptr);
}
/*
* @dev Returns true if the iteration has more elements.
* @param self The iterator.
* @return true if the iteration has more elements.
*/
function hasNext(Iterator memory self) internal pure returns (bool) {
RLPItem memory item = self.item;
return self.nextPtr < item.memPtr + item.len;
}
/*
* @param item RLP encoded bytes
*/
function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) {
uint256 memPtr;
assembly {
memPtr := add(item, 0x20)
}
return RLPItem(item.length, memPtr);
}
/*
* @dev Create an iterator. Reverts if item is not a list.
* @param self The RLP item.
* @return An 'Iterator' over the item.
*/
function iterator(RLPItem memory self) internal pure returns (Iterator memory) {
require(isList(self));
uint256 ptr = self.memPtr + _payloadOffset(self.memPtr);
return Iterator(self, ptr);
}
/*
* @param item RLP encoded bytes
*/
function rlpLen(RLPItem memory item) internal pure returns (uint256) {
return item.len;
}
/*
* @param item RLP encoded bytes
*/
function payloadLen(RLPItem memory item) internal pure returns (uint256) {
return item.len - _payloadOffset(item.memPtr);
}
/*
* @param item RLP encoded list in bytes
*/
function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) {
require(isList(item));
uint256 items = numItems(item);
RLPItem[] memory result = new RLPItem[](items);
uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr);
uint256 dataLen;
for (uint256 i = 0; i < items; i++) {
dataLen = _itemLength(memPtr);
result[i] = RLPItem(dataLen, memPtr);
memPtr = memPtr + dataLen;
}
return result;
}
// @return indicator whether encoded payload is a list. negate this function call for isData.
function isList(RLPItem memory item) internal pure returns (bool) {
if (item.len == 0) return false;
uint8 byte0;
uint256 memPtr = item.memPtr;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < LIST_SHORT_START) return false;
return true;
}
/*
* @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory.
* @return keccak256 hash of RLP encoded bytes.
*/
function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) {
uint256 ptr = item.memPtr;
uint256 len = item.len;
bytes32 result;
assembly {
result := keccak256(ptr, len)
}
return result;
}
function payloadLocation(RLPItem memory item) internal pure returns (uint256, uint256) {
uint256 offset = _payloadOffset(item.memPtr);
uint256 memPtr = item.memPtr + offset;
uint256 len = item.len - offset; // data length
return (memPtr, len);
}
/*
* @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory.
* @return keccak256 hash of the item payload.
*/
function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) {
(uint256 memPtr, uint256 len) = payloadLocation(item);
bytes32 result;
assembly {
result := keccak256(memPtr, len)
}
return result;
}
/** RLPItem conversions into data types **/
// @returns raw rlp encoding in bytes
function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) {
bytes memory result = new bytes(item.len);
if (result.length == 0) return result;
uint256 ptr;
assembly {
ptr := add(0x20, result)
}
copy(item.memPtr, ptr, item.len);
return result;
}
// any non-zero byte < 128 is considered true
function toBoolean(RLPItem memory item) internal pure returns (bool) {
require(item.len == 1);
uint256 result;
uint256 memPtr = item.memPtr;
assembly {
result := byte(0, mload(memPtr))
}
return result == 0 ? false : true;
}
function toAddress(RLPItem memory item) internal pure returns (address) {
// 1 byte for the length prefix
require(item.len == 21);
return address(uint160(toUint(item)));
}
function toUint(RLPItem memory item) internal pure returns (uint256) {
require(item.len > 0 && item.len <= 33);
uint256 offset = _payloadOffset(item.memPtr);
uint256 len = item.len - offset;
uint256 result;
uint256 memPtr = item.memPtr + offset;
assembly {
result := mload(memPtr)
// shift to the correct location if neccesary
if lt(len, 32) {
result := div(result, exp(256, sub(32, len)))
}
}
return result;
}
// enforces 32 byte length
function toUintStrict(RLPItem memory item) internal pure returns (uint256) {
// one byte prefix
require(item.len == 33);
uint256 result;
uint256 memPtr = item.memPtr + 1;
assembly {
result := mload(memPtr)
}
return result;
}
function toBytes(RLPItem memory item) internal pure returns (bytes memory) {
require(item.len > 0);
uint256 offset = _payloadOffset(item.memPtr);
uint256 len = item.len - offset; // data length
bytes memory result = new bytes(len);
uint256 destPtr;
assembly {
destPtr := add(0x20, result)
}
copy(item.memPtr + offset, destPtr, len);
return result;
}
/*
* Private Helpers
*/
// @return number of payload items inside an encoded list.
function numItems(RLPItem memory item) private pure returns (uint256) {
if (item.len == 0) return 0;
uint256 count = 0;
uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr);
uint256 endPtr = item.memPtr + item.len;
while (currPtr < endPtr) {
currPtr = currPtr + _itemLength(currPtr); // skip over an item
count++;
}
return count;
}
// @return entire rlp item byte length
function _itemLength(uint256 memPtr) private pure returns (uint256) {
uint256 itemLen;
uint256 byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START) itemLen = 1;
else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1;
else if (byte0 < LIST_SHORT_START) {
assembly {
let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
memPtr := add(memPtr, 1) // skip over the first byte
/* 32 byte word size */
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
itemLen := add(dataLen, add(byteLen, 1))
}
} else if (byte0 < LIST_LONG_START) {
itemLen = byte0 - LIST_SHORT_START + 1;
} else {
assembly {
let byteLen := sub(byte0, 0xf7)
memPtr := add(memPtr, 1)
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
itemLen := add(dataLen, add(byteLen, 1))
}
}
return itemLen;
}
// @return number of bytes until the data
function _payloadOffset(uint256 memPtr) private pure returns (uint256) {
uint256 byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START) return 0;
else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) return 1;
else if (byte0 < LIST_SHORT_START)
// being explicit
return byte0 - (STRING_LONG_START - 1) + 1;
else return byte0 - (LIST_LONG_START - 1) + 1;
}
/*
* @param src Pointer to source
* @param dest Pointer to destination
* @param len Amount of memory to copy from the source
*/
function copy(
uint256 src,
uint256 dest,
uint256 len
) private pure {
if (len == 0) return;
// copy as many word sizes as possible
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
}
if (len == 0) return;
// left over bytes. Mask is used to remove unwanted bytes from the word
uint256 mask = 256**(WORD_SIZE - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask)) // zero out src
let destpart := and(mload(dest), mask) // retrieve the bytes
mstore(dest, or(destpart, srcpart))
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;
import "lib/solmate/src/utils/SafeTransferLib.sol";
error ZeroAddress();
/**
* @title RescueFundsLib
* @dev A library that provides a function to rescue funds from a contract.
*/
library RescueFundsLib {
/**
* @dev The address used to identify ETH.
*/
address public constant ETH_ADDRESS =
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
/**
* @dev thrown when the given token address don't have any code
*/
error InvalidTokenAddress();
/**
* @dev Rescues funds from a contract.
* @param token_ The address of the token contract.
* @param rescueTo_ The address of the user.
* @param amount_ The amount of tokens to be rescued.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) internal {
if (rescueTo_ == address(0)) revert ZeroAddress();
if (token_ == ETH_ADDRESS) {
SafeTransferLib.safeTransferETH(rescueTo_, amount_);
} else {
if (token_.code.length == 0) revert InvalidTokenAddress();
SafeTransferLib.safeTransfer(ERC20(token_), rescueTo_, 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), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
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), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
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), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
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: GPL-3.0-only
pragma solidity 0.8.19;
// contains unique identifiers which are hashes of strings, they help in making signature digest unique
// hence preventing signature replay attacks
// default switchboards
bytes32 constant TRIP_PATH_SIG_IDENTIFIER = keccak256("TRIP_PATH");
bytes32 constant TRIP_PROPOSAL_SIG_IDENTIFIER = keccak256("TRIP_PROPOSAL");
bytes32 constant TRIP_GLOBAL_SIG_IDENTIFIER = keccak256("TRIP_GLOBAL");
bytes32 constant UN_TRIP_PATH_SIG_IDENTIFIER = keccak256("UN_TRIP_PATH");
bytes32 constant UN_TRIP_GLOBAL_SIG_IDENTIFIER = keccak256("UN_TRIP_GLOBAL");
// native switchboards
bytes32 constant TRIP_NATIVE_SIG_IDENTIFIER = keccak256("TRIP_NATIVE");
bytes32 constant UN_TRIP_NATIVE_SIG_IDENTIFIER = keccak256("UN_TRIP_NATIVE");
// value threshold, price and fee updaters
bytes32 constant FEES_UPDATE_SIG_IDENTIFIER = keccak256("FEES_UPDATE");
bytes32 constant RELATIVE_NATIVE_TOKEN_PRICE_UPDATE_SIG_IDENTIFIER = keccak256(
"RELATIVE_NATIVE_TOKEN_PRICE_UPDATE"
);
bytes32 constant MSG_VALUE_MIN_THRESHOLD_SIG_IDENTIFIER = keccak256(
"MSG_VALUE_MIN_THRESHOLD_UPDATE"
);
bytes32 constant MSG_VALUE_MAX_THRESHOLD_SIG_IDENTIFIER = keccak256(
"MSG_VALUE_MAX_THRESHOLD_UPDATE"
);
{
"compilationTarget": {
"contracts/switchboard/native/PolygonL1Switchboard.sol": "PolygonL1Switchboard"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 999999
},
"remappings": []
}
[{"inputs":[{"internalType":"uint32","name":"chainSlug_","type":"uint32"},{"internalType":"address","name":"checkpointManager_","type":"address"},{"internalType":"address","name":"fxRoot_","type":"address"},{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"socket_","type":"address"},{"internalType":"contract ISignatureVerifier","name":"signatureVerifier_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"FeesNotEnough","type":"error"},{"inputs":[],"name":"InvalidNonce","type":"error"},{"inputs":[],"name":"InvalidSender","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"NoPermit","type":"error"},{"inputs":[],"name":"NoRootFound","type":"error"},{"inputs":[],"name":"OnlyExecutionManager","type":"error"},{"inputs":[],"name":"OnlyNominee","type":"error"},{"inputs":[],"name":"OnlyOwner","type":"error"},{"inputs":[],"name":"UnequalArrayLengths","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"fxChildTunnel","type":"address"},{"indexed":false,"internalType":"address","name":"newFxChildTunnel","type":"address"}],"name":"FxChildTunnelSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isGlobalTipped","type":"bool"}],"name":"GlobalTripChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"packetId","type":"bytes32"}],"name":"InitiatedNativeConfirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"claimer","type":"address"}],"name":"OwnerClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nominee","type":"address"}],"name":"OwnerNominated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"grantee","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"revokee","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"packetId","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"RootReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"switchboardFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"verificationOverheadFees","type":"uint256"}],"name":"SwitchboardFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"remoteNativeSwitchboard","type":"address"}],"name":"UpdatedRemoteNativeSwitchboard","type":"event"},{"inputs":[],"name":"SEND_MESSAGE_EVENT_SIG","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"root_","type":"bytes32"},{"internalType":"bytes32","name":"packetId_","type":"bytes32"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowPacket","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"capacitor__","outputs":[{"internalType":"contract ICapacitor","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chainSlug","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"checkpointManager","outputs":[{"internalType":"contract ICheckpointManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fxChildTunnel","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fxRoot","outputs":[{"internalType":"contract IFxStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"getMinFees","outputs":[{"internalType":"uint128","name":"switchboardFee_","type":"uint128"},{"internalType":"uint128","name":"verificationFee_","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"roleNames_","type":"bytes32[]"},{"internalType":"uint32[]","name":"slugs_","type":"uint32[]"},{"internalType":"address[]","name":"grantees_","type":"address[]"}],"name":"grantBatchRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role_","type":"bytes32"},{"internalType":"address","name":"grantee_","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"roleName_","type":"bytes32"},{"internalType":"uint32","name":"chainSlug_","type":"uint32"},{"internalType":"address","name":"grantee_","type":"address"}],"name":"grantRoleWithSlug","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role_","type":"bytes32"},{"internalType":"address","name":"address_","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"roleName_","type":"bytes32"},{"internalType":"uint32","name":"chainSlug_","type":"uint32"},{"internalType":"address","name":"address_","type":"address"}],"name":"hasRoleWithSlug","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"packetId_","type":"bytes32"}],"name":"initiateNativeConfirmation","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"isGlobalTipped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nextNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nominee_","type":"address"}],"name":"nominateOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nominee","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"packetIdToRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"processedExits","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"receiveFees","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"inputData","type":"bytes"}],"name":"receiveMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"packetId_","type":"bytes32"},{"internalType":"bytes32","name":"root_","type":"bytes32"}],"name":"receivePacket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"siblingChainSlug_","type":"uint32"},{"internalType":"uint256","name":"maxPacketLength_","type":"uint256"},{"internalType":"uint256","name":"capacitorType_","type":"uint256"},{"internalType":"uint256","name":"initialPacketCount_","type":"uint256"},{"internalType":"address","name":"remoteNativeSwitchboard_","type":"address"}],"name":"registerSiblingSlug","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"remoteNativeSwitchboard","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"address","name":"rescueTo_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"rescueFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"roleNames_","type":"bytes32[]"},{"internalType":"uint32[]","name":"slugs_","type":"uint32[]"},{"internalType":"address[]","name":"grantees_","type":"address[]"}],"name":"revokeBatchRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role_","type":"bytes32"},{"internalType":"address","name":"revokee_","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"roleName_","type":"bytes32"},{"internalType":"uint32","name":"chainSlug_","type":"uint32"},{"internalType":"address","name":"grantee_","type":"address"}],"name":"revokeRoleWithSlug","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce_","type":"uint256"},{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"uint128","name":"switchboardFees_","type":"uint128"},{"internalType":"uint128","name":"verificationOverheadFees_","type":"uint128"},{"internalType":"bytes","name":"signature_","type":"bytes"}],"name":"setFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fxChildTunnel_","type":"address"}],"name":"setFxChildTunnel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"signatureVerifier__","outputs":[{"internalType":"contract ISignatureVerifier","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"socket__","outputs":[{"internalType":"contract ISocket","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"switchboardFees","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce_","type":"uint256"},{"internalType":"bytes","name":"signature_","type":"bytes"}],"name":"tripGlobal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce_","type":"uint256"},{"internalType":"bytes","name":"signature_","type":"bytes"}],"name":"unTrip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"siblingChainSlug_","type":"uint32"},{"internalType":"address","name":"remoteNativeSwitchboard_","type":"address"}],"name":"updateSibling","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"verificationOverheadFees","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"withdrawTo_","type":"address"}],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"}]