// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;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.
*/abstractcontractAccessControlisOwnable{
/**
* @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.
*/eventRoleGranted(bytes32indexed role, addressindexed grantee);
/**
* @dev Emitted when a role is revoked from an address.
*/eventRoleRevoked(bytes32indexed role, addressindexed revokee);
/**
* @dev Error message thrown when an address does not have permission to execute a function with onlyRole modifier.
*/errorNoPermit(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
*/modifieronlyRole(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_) internalvirtual{
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.
*/functiongrantRole(bytes32 role_,
address grantee_
) externalvirtualonlyOwner{
_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.
*/functionrevokeRole(bytes32 role_,
address revokee_
) externalvirtualonlyOwner{
_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.
*/functionhasRole(bytes32 role_,
address address_
) externalviewreturns (bool) {
return _hasRole(role_, address_);
}
function_hasRole(bytes32 role_,
address address_
) internalviewreturns (bool) {
return _permits[role_][address_];
}
}
Contract Source Code
File 2 of 23: ConnectorPlug.sol
pragmasolidity 0.8.13;import"../common/Ownable.sol";
import {ISocket} from"../interfaces/ISocket.sol";
import {IPlug} from"../interfaces/IPlug.sol";
import {RescueFundsLib} from"../libraries/RescueFundsLib.sol";
interfaceIHub{
functionreceiveInbound(bytesmemory payload_) external;
}
interfaceIConnector{
functionoutbound(uint256 msgGasLimit_,
bytesmemory payload_
) externalpayable;
functionsiblingChainSlug() externalviewreturns (uint32);
functiongetMinFees(uint256 msgGasLimit_
) externalviewreturns (uint256 totalFees);
}
contractConnectorPlugisIConnector, IPlug, Ownable{
IHub publicimmutable hub__;
ISocket publicimmutable socket__;
uint32publicimmutable siblingChainSlug;
errorNotHub();
errorNotSocket();
eventConnectorPlugDisconnected();
constructor(address hub_,
address socket_,
uint32 siblingChainSlug_
) Ownable(msg.sender) {
hub__ = IHub(hub_);
socket__ = ISocket(socket_);
siblingChainSlug = siblingChainSlug_;
}
functionoutbound(uint256 msgGasLimit_,
bytesmemory payload_
) externalpayableoverride{
if (msg.sender!=address(hub__)) revert NotHub();
socket__.outbound{value: msg.value}(
siblingChainSlug,
msgGasLimit_,
bytes32(0),
bytes32(0),
payload_
);
}
functioninbound(uint32/* siblingChainSlug_ */, // cannot be connected for any other slug, immutable variablebytescalldata payload_
) externalpayableoverride{
if (msg.sender!=address(socket__)) revert NotSocket();
hub__.receiveInbound(payload_);
}
functiongetMinFees(uint256 msgGasLimit_
) externalviewoverridereturns (uint256 totalFees) {
return
socket__.getMinFees(
msgGasLimit_,
64,
bytes32(0),
bytes32(0),
siblingChainSlug,
address(this)
);
}
functionconnect(address siblingPlug_,
address switchboard_
) externalonlyOwner{
socket__.connect(
siblingChainSlug,
siblingPlug_,
switchboard_,
switchboard_
);
}
functiondisconnect() externalonlyOwner{
(
,
address inboundSwitchboard,
address outboundSwitchboard,
,
) = socket__.getPlugConfig(address(this), siblingChainSlug);
socket__.connect(
siblingChainSlug,
address(0),
inboundSwitchboard,
outboundSwitchboard
);
emit ConnectorPlugDisconnected();
}
/**
* @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.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyOwner{
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=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.abstractcontractERC20{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventTransfer(addressindexedfrom, addressindexed to, uint256 amount);
eventApproval(addressindexed owner, addressindexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
uint8publicimmutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/uint256public totalSupply;
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/uint256internalimmutable INITIAL_CHAIN_ID;
bytes32internalimmutable INITIAL_DOMAIN_SEPARATOR;
mapping(address=>uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name,
stringmemory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID =block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 amount) publicvirtualreturns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
returntrue;
}
functiontransfer(address to, uint256 amount) publicvirtualreturns (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);
returntrue;
}
functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualreturns (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);
returntrue;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) publicvirtual{
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);
}
functionDOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
functioncomputeDomainSeparator() internalviewvirtualreturns (bytes32) {
returnkeccak256(
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) internalvirtual{
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(addressfrom, uint256 amount) internalvirtual{
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);
}
}
Contract Source Code
File 5 of 23: ExcessivelySafeCall.sol
// SPDX-License-Identifier: MIT OR Apache-2.0pragmasolidity 0.8.13;libraryExcessivelySafeCall{
uintconstant LOW_28_MASK =0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/// @notice Use when you _really_ really _really_ don't trust the called/// contract. This prevents the called contract from causing reversion of/// the caller in as many ways as we can./// @dev The main difference between this and a solidity low-level call is/// that we limit the number of bytes that the callee can cause to be/// copied to caller memory. This prevents stupid things like malicious/// contracts returning 10,000,000 bytes causing a local OOG when copying/// to memory./// @param _target The address to call/// @param _gas The amount of gas to forward to the remote contract/// @param _maxCopy The maximum number of bytes of returndata to copy/// to memory./// @param _calldata The data to send to the remote contract/// @return success and returndata, as `.call()`. Returndata is capped to/// `_maxCopy` bytes.functionexcessivelySafeCall(address _target,
uint _gas,
uint16 _maxCopy,
bytesmemory _calldata
) internalreturns (bool, bytesmemory) {
// set up for assembly calluint _toCopy;
bool _success;
bytesmemory _returnData =newbytes(_maxCopy);
// dispatch message to recipient// by assembly calling "handle" function// we call via assembly to avoid memcopying a very large returndata// returned by a malicious contractassembly {
_success :=call(
_gas, // gas
_target, // recipient0, // ether valueadd(_calldata, 0x20), // inlocmload(_calldata), // inlen0, // outloc0// outlen
)
// limit our copy to 256 bytes
_toCopy :=returndatasize()
ifgt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytesmstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/// @notice Use when you _really_ really _really_ don't trust the called/// contract. This prevents the called contract from causing reversion of/// the caller in as many ways as we can./// @dev The main difference between this and a solidity low-level call is/// that we limit the number of bytes that the callee can cause to be/// copied to caller memory. This prevents stupid things like malicious/// contracts returning 10,000,000 bytes causing a local OOG when copying/// to memory./// @param _target The address to call/// @param _gas The amount of gas to forward to the remote contract/// @param _maxCopy The maximum number of bytes of returndata to copy/// to memory./// @param _calldata The data to send to the remote contract/// @return success and returndata, as `.call()`. Returndata is capped to/// `_maxCopy` bytes.functionexcessivelySafeStaticCall(address _target,
uint _gas,
uint16 _maxCopy,
bytesmemory _calldata
) internalviewreturns (bool, bytesmemory) {
// set up for assembly calluint _toCopy;
bool _success;
bytesmemory _returnData =newbytes(_maxCopy);
// dispatch message to recipient// by assembly calling "handle" function// we call via assembly to avoid memcopying a very large returndata// returned by a malicious contractassembly {
_success :=staticcall(
_gas, // gas
_target, // recipientadd(_calldata, 0x20), // inlocmload(_calldata), // inlen0, // outloc0// outlen
)
// limit our copy to 256 bytes
_toCopy :=returndatasize()
ifgt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytesmstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/**
* @notice Swaps function selectors in encoded contract calls
* @dev Allows reuse of encoded calldata for functions with identical
* argument types but different names. It simply swaps out the first 4 bytes
* for the new selector. This function modifies memory in place, and should
* only be used with caution.
* @param _newSelector The new 4-byte selector
* @param _buf The encoded contract args
*/functionswapSelector(bytes4 _newSelector,
bytesmemory _buf
) internalpure{
require(_buf.length>=4);
uint _mask = LOW_28_MASK;
assembly {
// load the first word oflet _word :=mload(add(_buf, 0x20))
// mask out the top 4 bytes// /x
_word :=and(_word, _mask)
_word :=or(_newSelector, _word)
mstore(add(_buf, 0x20), _word)
}
}
}
Contract Source Code
File 6 of 23: ExchangeRate.sol
pragmasolidity 0.8.13;import"../common/Ownable.sol";
import {RescueFundsLib} from"../libraries/RescueFundsLib.sol";
interfaceIExchangeRate{
// not marked pure, may involve state interactions in futurefunctiongetMintAmount(uint256 lockAmount,
uint256 totalLockedAmount
) externalreturns (uint256 mintAmount);
// not marked pure, may involve state interactions in futurefunctiongetUnlockAmount(uint256 burnAmount,
uint256 totalLockedAmount
) externalreturns (uint256 unlockAmount);
}
contractExchangeRateisIExchangeRate, Ownable(msg.sender) {
// chainId input needed? what else? slippage?functiongetMintAmount(uint256 lockAmount,
uint256/* totalLockedAmount */) externalpurereturns (uint256 mintAmount) {
return lockAmount;
}
functiongetUnlockAmount(uint256 burnAmount,
uint256/* totalLockedAmount */) externalpurereturns (uint256 unlockAmount) {
return burnAmount;
}
/**
* @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.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyOwner{
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
Contract Source Code
File 7 of 23: Execute.sol
pragmasolidity 0.8.13;import"lib/solmate/src/utils/ReentrancyGuard.sol";
import"../libraries/ExcessivelySafeCall.sol";
/**
* @title Execute
* @notice It enables payload execution and contains relevant storages.
* @dev This contract implements Socket's IPlug to enable message bridging and IMessageBridge
* to support any type of message bridge.
*/contractExecuteisReentrancyGuard{
usingExcessivelySafeCallforaddress;
/**
* @notice this struct stores relevant details for a pending payload execution
* @param receiver address of receiver where payload executes.
* @param siblingChainSlug the unique identifier of the source chain.
* @param payload payload to be executed
* @param isAmountPending if amount to be bridged is pending
*/structPendingExecutionDetails {
address receiver;
uint32 siblingChainSlug;
bytes payload;
bool isAmountPending;
}
uint16privateconstant MAX_COPY_BYTES =150;
// messageId => PendingExecutionDetailsmapping(bytes32=> PendingExecutionDetails) public pendingExecutions;
////////////////////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////////////////////////errorInvalidExecutionRetry();
errorPendingAmount();
errorCannotExecuteOnBridgeContracts();
/**
* @notice this function can be used to retry a payload execution if it was not successful.
* @param msgId_ The unique identifier of the bridging message.
*/functionretryPayloadExecution(bytes32 msgId_) externalnonReentrant{
PendingExecutionDetails storage details = pendingExecutions[msgId_];
if (details.isAmountPending) revert PendingAmount();
if (details.receiver ==address(0)) revert InvalidExecutionRetry();
bool success = _execute(details.receiver, details.payload);
if (success) _clearPayload(msgId_);
}
/**
* @notice this function is used to execute a payload at receiver
* @dev receiver address cannot be bridge address or this contract address.
* @param target_ address of target.
* @param payload_ payload to be executed at target.
*/function_execute(address target_,
bytesmemory payload_
) internalreturns (bool success) {
(success, ) = target_.excessivelySafeCall(
gasleft(),
MAX_COPY_BYTES,
payload_
);
}
/**
* @notice this function caches the execution payload details if the amount to be bridged
* is not pending or execution is reverting
*/function_cachePayload(bytes32 msgId_,
uint32 siblingChainSlug_,
bool isAmountPending_,
address receiver_,
bytesmemory payload_
) internal{
pendingExecutions[msgId_].receiver = receiver_;
pendingExecutions[msgId_].siblingChainSlug = siblingChainSlug_;
pendingExecutions[msgId_].payload = payload_;
pendingExecutions[msgId_].isAmountPending = isAmountPending_;
}
/**
* @notice this function clears the payload details once execution succeeds
*/function_clearPayload(bytes32 msgId_) internal{
pendingExecutions[msgId_].receiver =address(0);
pendingExecutions[msgId_].siblingChainSlug =0;
pendingExecutions[msgId_].payload =bytes("");
pendingExecutions[msgId_].isAmountPending =false;
}
}
pragmasolidity 0.8.13;/**
* @title IMessageBridge
* @notice It should be implemented by message bridge integrated to Super token and Vault.
*/interfaceIMessageBridge{
/**
* @notice calls socket's outbound function which transmits msg to `siblingChainSlug_`.
* @dev Only super token or vault can call this contract
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param msgGasLimit_ min gas limit needed to execute the message on sibling
* @param payload_ payload which should be executed at the sibling chain.
* @param options_ extra bytes memory can be used by other protocol plugs for additional options
*/functionoutbound(uint32 siblingChainSlug_,
uint256 msgGasLimit_,
bytesmemory payload_,
bytesmemory options_
) externalpayablereturns (bytes32 messageId_);
/**
* @notice this function is used to calculate message id before sending outbound().
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @return message id
*/functiongetMessageId(uint32 siblingChainSlug_
) externalviewreturns (bytes32);
}
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;/**
* @title IPlug
* @notice Interface for a plug contract that executes the message received from a source chain.
*/interfaceIPlug{
/**
* @dev this should be only executable by socket
* @notice executes the message received from source chain
* @notice It is expected to have original sender checks in the destination plugs using payload
* @param srcChainSlug_ chain slug of source
* @param payload_ the data which is needed by plug at inbound call on remote
*/functioninbound(uint32 srcChainSlug_,
bytescalldata payload_
) externalpayable;
}
Contract Source Code
File 14 of 23: ISocket.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;/**
* @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
*/interfaceISocket{
/**
* @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
*/structFees {
uint128 transmissionFees;
uint128 executionFee;
uint128 switchboardFees;
}
/**
* @title MessageDetails
* @dev This struct defines the details of a message to be executed in a Decapacitor contract.
*/structMessageDetails {
// A unique identifier for the message.bytes32 msgId;
// The fee to be paid for executing the message.uint256 executionFee;
// The maximum amount of gas that can be used to execute the message.uint256 minMsgGasLimit;
// The extra params which provides msg value and additional info needed for message execbytes32 executionParams;
// The payload data to be executed in the message.bytes payload;
}
/**
* @title ExecutionDetails
* @dev This struct defines the execution details
*/structExecutionDetails {
// packet idbytes32 packetId;
// proposal countuint256 proposalCount;
// gas limit needed to execute inbounduint256 executionGasLimit;
// proof data required by the Decapacitor contract to verify the message's authenticitybytes decapacitorProof;
// signature of executorbytes signature;
}
/**
* @notice emits the message details when a new message arrives at outbound
* @param localChainSlug local chain slug
* @param localPlug local plug address
* @param dstChainSlug remote chain slug
* @param dstPlug remote plug address
* @param msgId message id packed with remoteChainSlug and nonce
* @param minMsgGasLimit gas limit needed to execute the inbound at remote
* @param payload the data which will be used by inbound at remote
*/eventMessageOutbound(uint32 localChainSlug,
address localPlug,
uint32 dstChainSlug,
address dstPlug,
bytes32 msgId,
uint256 minMsgGasLimit,
bytes32 executionParams,
bytes32 transmissionParams,
bytes payload,
Fees fees
);
/**
* @notice emits the status of message after inbound call
* @param msgId msg id which is executed
*/eventExecutionSuccess(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
*/eventPlugConnected(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
*/functionoutbound(uint32 remoteChainSlug_,
uint256 minMsgGasLimit_,
bytes32 executionParams_,
bytes32 transmissionParams_,
bytesmemory payload_
) externalpayablereturns (bytes32 msgId);
/**
* @notice executes a message
* @param executionDetails_ the packet details, proof and signature needed for message execution
* @param messageDetails_ the message details
*/functionexecute(
ISocket.ExecutionDetails calldata executionDetails_,
ISocket.MessageDetails calldata messageDetails_
) externalpayable;
/**
* @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
*/functionconnect(uint32 siblingChainSlug_,
address siblingPlug_,
address inboundSwitchboard_,
address outboundSwitchboard_
) external;
/**
* @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.
*/functiongetMinFees(uint256 minMsgGasLimit_,
uint256 payloadSize_,
bytes32 executionParams_,
bytes32 transmissionParams_,
uint32 remoteChainSlug_,
address plug_
) externalviewreturns (uint256 totalFees);
/**
* @notice returns chain slug
* @return chainSlug current chain slug
*/functionchainSlug() externalviewreturns (uint32 chainSlug);
functionglobalMessageCount() externalviewreturns (uint64);
/**
* @notice returns the config for given `plugAddress_` and `siblingChainSlug_`
* @param siblingChainSlug_ the sibling chain slug
* @param plugAddress_ address of plug present at current chain
*/functiongetPlugConfig(address plugAddress_,
uint32 siblingChainSlug_
)
externalviewreturns (address siblingPlug,
address inboundSwitchboard__,
address outboundSwitchboard__,
address capacitor__,
address decapacitor__
);
}
Contract Source Code
File 15 of 23: ISuperTokenOrVault.sol
pragmasolidity 0.8.13;/**
* @title ISuperTokenOrVault
* @notice It should be implemented Super token and Vault for plugs to communicate.
*/interfaceISuperTokenOrVault{
/**
* @dev this should be only executable by socket.
* @notice executes the message received from source chain.
* @notice It is expected to have original sender checks in the destination plugs using payload.
* @param siblingChainSlug_ chain slug of source.
* @param payload_ the data which is needed to decode receiver, amount, msgId and payload.
*/functioninbound(uint32 siblingChainSlug_,
bytesmemory payload_
) externalpayable;
}
Contract Source Code
File 16 of 23: Ownable.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;/**
* @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.
*/abstractcontractOwnable{
addressprivate _owner;
addressprivate _nominee;
eventOwnerNominated(addressindexed nominee);
eventOwnerClaimed(addressindexed claimer);
errorOnlyOwner();
errorOnlyNominee();
/**
* @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.
*/modifieronlyOwner() {
if (msg.sender!= _owner) revert OnlyOwner();
_;
}
/**
* @dev Returns the current owner of the contract.
*/functionowner() externalviewreturns (address) {
return _owner;
}
/**
* @dev Returns the current nominee for ownership of the contract.
*/functionnominee() externalviewreturns (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.
*/functionnominateOwner(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.
*/functionclaimOwner() 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-onlypragmasolidity 0.8.13;import"lib/solmate/src/utils/SafeTransferLib.sol";
errorZeroAddress();
/**
* @title RescueFundsLib
* @dev A library that provides a function to rescue funds from a contract.
*/libraryRescueFundsLib{
/**
* @dev The address used to identify ETH.
*/addresspublicconstant ETH_ADDRESS =address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
/**
* @dev thrown when the given token address don't have any code
*/errorInvalidTokenAddress();
/**
* @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.
*/functionrescueFunds(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_);
}
}
}
Contract Source Code
File 19 of 23: SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=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.librarySafeTransferLib{
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(
ERC20 token,
addressfrom,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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");
}
functionsafeTransfer(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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");
}
functionsafeApprove(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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");
}
}
Contract Source Code
File 20 of 23: SocketPlug.sol
pragmasolidity 0.8.13;import {ISocket} from"../../interfaces/ISocket.sol";
import {IPlug} from"../../interfaces/IPlug.sol";
import {AccessControl} from"../../common/AccessControl.sol";
import {RescueFundsLib} from"../../libraries/RescueFundsLib.sol";
import {IMessageBridge} from"./../IMessageBridge.sol";
import {ISuperTokenOrVault} from"./../ISuperTokenOrVault.sol";
/**
* @title SocketPlug
* @notice It enables message bridging in Super token and Super Token Vault.
* @dev This contract implements Socket's IPlug to enable message bridging and IMessageBridge
* to support any type of message bridge.
*/contractSocketPlugisIPlug, AccessControl, IMessageBridge{
bytes32constant RESCUE_ROLE =keccak256("RESCUE_ROLE");
// socket address
ISocket publicimmutable socket__;
// super token or vault address
ISuperTokenOrVault public tokenOrVault__;
// chain slug of current chainuint32publicimmutable chainSlug;
// map of sibling chain slugs with the plug addressesmapping(uint32=>address) public siblingPlugs;
////////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////////////// emitted when a plug is disconnectedeventSocketPlugDisconnected(uint32 siblingChainSlug);
// emitted when a super token or vault address is seteventSuperTokenOrVaultSet();
////////////////////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////////////////////////errorNotSuperTokenOrVault();
errorNotSocket();
errorTokenOrVaultAlreadySet();
/**
* @notice constructor for creating a new SocketPlug.
* @param socket_ The address of the Socket contract used to transmit messages.
* @param owner_ The address of the owner who has the initial admin role.
* @param chainSlug_ The unique identifier of the chain this plug is deployed on.
*/constructor(address socket_,
address owner_,
uint32 chainSlug_
) AccessControl(owner_) {
socket__ = ISocket(socket_);
chainSlug = chainSlug_;
}
/**
* @notice calls socket's outbound function which transmits msg to `siblingChainSlug_`.
* @dev Only super token or vault can call this function
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param msgGasLimit_ min gas limit needed to execute the message on sibling
* @param payload_ payload which should be executed at the sibling chain.
* @return messageId_ identifier used to get message details from Socket.
*/functionoutbound(uint32 siblingChainSlug_,
uint256 msgGasLimit_,
bytesmemory payload_,
bytesmemory) externalpayablereturns (bytes32 messageId_) {
if (msg.sender!=address(tokenOrVault__))
revert NotSuperTokenOrVault();
return
socket__.outbound{value: msg.value}(
siblingChainSlug_,
msgGasLimit_,
bytes32(0),
bytes32(0),
payload_
);
}
/**
* @notice this function receives the message from sibling chain.
* @dev Only socket can call this function.
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param payload_ payload which should be executed at the super token or vault.
*/functioninbound(uint32 siblingChainSlug_,
bytesmemory payload_
) externalpayableoverride{
if (msg.sender!=address(socket__)) revert NotSocket();
tokenOrVault__.inbound(siblingChainSlug_, payload_);
}
/**
* @notice this function calculates the fees needed to send the message to Socket.
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param msgGasLimit_ min gas limit needed at destination chain to execute the message.
*/functiongetMinFees(uint32 siblingChainSlug_,
uint256 msgGasLimit_
) externalviewreturns (uint256 totalFees) {
return
socket__.getMinFees(
msgGasLimit_,
96,
bytes32(0),
bytes32(0),
siblingChainSlug_,
address(this)
);
}
/**
* @notice this function is used to set the Super token or Vault address
* @dev only owner can set the token address.
* @dev this can be called only once.
* @param tokenOrVault_ The super token or vault address connected to this plug.
*/functionsetSuperTokenOrVault(address tokenOrVault_) externalonlyOwner{
if (address(tokenOrVault__) !=address(0))
revert TokenOrVaultAlreadySet();
tokenOrVault__ = ISuperTokenOrVault(tokenOrVault_);
emit SuperTokenOrVaultSet();
}
/**
* @notice this function is used to connect Socket for a `siblingChainSlug_`.
* @dev only owner can connect Socket with preferred switchboard address.
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param siblingPlug_ address of plug present at siblingChainSlug_ to call at inbound
* @param inboundSwitchboard_ the address of switchboard to use for verifying messages at inbound
* @param outboundSwitchboard_ the address of switchboard to use for sending messages
*/functionconnect(uint32 siblingChainSlug_,
address siblingPlug_,
address inboundSwitchboard_,
address outboundSwitchboard_
) externalonlyOwner{
siblingPlugs[siblingChainSlug_] = siblingPlug_;
socket__.connect(
siblingChainSlug_,
siblingPlug_,
inboundSwitchboard_,
outboundSwitchboard_
);
}
/**
* @notice this function is used to disconnect Socket for a `siblingChainSlug_`.
* @dev only owner can disconnect Socket
* @dev it sets sibling plug as address(0) which makes it revert at `outbound()` hence
* @dev stopping it from sending any message.
* @param siblingChainSlug_ The unique identifier of the sibling chain.
*/functiondisconnect(uint32 siblingChainSlug_) externalonlyOwner{
(
,
address inboundSwitchboard,
address outboundSwitchboard,
,
) = socket__.getPlugConfig(address(this), siblingChainSlug_);
socket__.connect(
siblingChainSlug_,
address(0),
inboundSwitchboard,
outboundSwitchboard
);
emit SocketPlugDisconnected(siblingChainSlug_);
}
/**
* @notice this function is used to calculate message id before sending outbound().
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @return message id
*/functiongetMessageId(uint32 siblingChainSlug_
) publicviewreturns (bytes32) {
returnbytes32(
(uint256(chainSlug) <<224) |
(uint256(uint160(siblingPlugs[siblingChainSlug_])) <<64) |
(ISocket(socket__).globalMessageCount())
);
}
/**
* @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.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
Contract Source Code
File 21 of 23: SuperToken.sol
pragmasolidity 0.8.13;import"lib/solmate/src/tokens/ERC20.sol";
import {AccessControl} from"../common/AccessControl.sol";
import {Gauge} from"../common/Gauge.sol";
import {RescueFundsLib} from"../libraries/RescueFundsLib.sol";
import"./Execute.sol";
import"./IMessageBridge.sol";
import"./ISuperTokenOrVault.sol";
/**
* @title SuperToken
* @notice An ERC20 contract which enables bridging a token to its sibling chains.
* @dev This contract implements ISuperTokenOrVault to support message bridging through IMessageBridge compliant contracts.
*/contractSuperTokenisERC20,
Gauge,
ISuperTokenOrVault,
AccessControl,
Execute{
structUpdateLimitParams {
bool isMint;
uint32 siblingChainSlug;
uint256 maxLimit;
uint256 ratePerSecond;
}
bytes32constant RESCUE_ROLE =keccak256("RESCUE_ROLE");
bytes32constant LIMIT_UPDATER_ROLE =keccak256("LIMIT_UPDATER_ROLE");
// bridge contract address which provides AMB support
IMessageBridge public bridge__;
// siblingChainSlug => mintLimitParamsmapping(uint32=> LimitParams) _receivingLimitParams;
// siblingChainSlug => burnLimitParamsmapping(uint32=> LimitParams) _sendingLimitParams;
// siblingChainSlug => receiver => identifier => amountmapping(uint32=>mapping(address=>mapping(bytes32=>uint256)))
public pendingMints;
// siblingChainSlug => amountmapping(uint32=>uint256) public siblingPendingMints;
////////////////////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////////////////////////errorSiblingNotSupported();
errorMessageIdMisMatched();
errorZeroAmount();
errorNotMessageBridge();
////////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////////////// emitted when limit params are updatedeventLimitParamsUpdated(UpdateLimitParams[] updates);
// emitted when message bridge is updatedeventMessageBridgeUpdated(address newBridge);
// emitted at source when tokens are bridged to a sibling chaineventBridgeTokens(uint32 siblingChainSlug,
address withdrawer,
address receiver,
uint256 bridgedAmount,
bytes32 identifier
);
// emitted when pending tokens are minted to the receivereventPendingTokensBridged(uint32 siblingChainSlug,
address receiver,
uint256 mintAmount,
uint256 pendingAmount,
bytes32 identifier
);
// emitted when transfer reaches limit and token mint is added to pending queueeventTokensPending(uint32 siblingChainSlug,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount,
bytes32 identifier
);
// emitted when pending tokens are minted as limits are replenishedeventTokensBridged(uint32 siblingChainSlug,
address receiver,
uint256 mintAmount,
uint256 totalAmount,
bytes32 identifier
);
/**
* @notice constructor for creating a new SuperToken.
* @param name_ token name
* @param symbol_ token symbol
* @param decimals_ token decimals (should be same on all chains)
* @param initialSupplyHolder_ address to which initial supply will be minted
* @param owner_ owner of this contract
* @param initialSupply_ initial supply of super token
* @param bridge_ message bridge address
*/constructor(stringmemory name_,
stringmemory symbol_,
uint8 decimals_,
address initialSupplyHolder_,
address owner_,
uint256 initialSupply_,
address bridge_
) ERC20(name_, symbol_, decimals_) AccessControl(owner_) {
_mint(initialSupplyHolder_, initialSupply_);
bridge__ = IMessageBridge(bridge_);
}
/**
* @notice this function is used to update message bridge
* @dev it can only be updated by owner
* @dev should be carefully migrated as it can risk user funds
* @param bridge_ new bridge address
*/functionupdateMessageBridge(address bridge_) externalonlyOwner{
bridge__ = IMessageBridge(bridge_);
emit MessageBridgeUpdated(bridge_);
}
/**
* @notice this function is used to set bridge limits
* @dev it can only be updated by owner
* @param updates_ can be used to set mint and burn limits for all siblings in one call.
*/functionupdateLimitParams(
UpdateLimitParams[] calldata updates_
) externalonlyRole(LIMIT_UPDATER_ROLE) {
for (uint256 i; i < updates_.length; i++) {
if (updates_[i].isMint) {
_consumePartLimit(
0,
_receivingLimitParams[updates_[i].siblingChainSlug]
); // to keep current limit in sync
_receivingLimitParams[updates_[i].siblingChainSlug]
.maxLimit = updates_[i].maxLimit;
_receivingLimitParams[updates_[i].siblingChainSlug]
.ratePerSecond = updates_[i].ratePerSecond;
} else {
_consumePartLimit(
0,
_sendingLimitParams[updates_[i].siblingChainSlug]
); // to keep current limit in sync
_sendingLimitParams[updates_[i].siblingChainSlug]
.maxLimit = updates_[i].maxLimit;
_sendingLimitParams[updates_[i].siblingChainSlug]
.ratePerSecond = updates_[i].ratePerSecond;
}
}
emit LimitParamsUpdated(updates_);
}
/**
* @notice this function is called by users to bridge their funds to a sibling chain
* @dev it is payable to receive message bridge fees to be paid.
* @param receiver_ address receiving bridged tokens
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param sendingAmount_ amount bridged
* @param msgGasLimit_ min gas limit needed for execution at destination
* @param payload_ payload which is executed at destination with bridged amount at receiver address.
* @param options_ additional message bridge options can be provided using this param
*/functionbridge(address receiver_,
uint32 siblingChainSlug_,
uint256 sendingAmount_,
uint256 msgGasLimit_,
bytescalldata payload_,
bytescalldata options_
) externalpayable{
if (_sendingLimitParams[siblingChainSlug_].maxLimit ==0)
revert SiblingNotSupported();
if (sendingAmount_ ==0) revert ZeroAmount();
_consumeFullLimit(
sendingAmount_,
_sendingLimitParams[siblingChainSlug_]
); // reverts on limit hit
_burn(msg.sender, sendingAmount_);
bytes32 messageId = bridge__.getMessageId(siblingChainSlug_);
// important to get message id as it is used as an// identifier for pending amount and payload cachingbytes32 returnedMessageId = bridge__.outbound{value: msg.value}(
siblingChainSlug_,
msgGasLimit_,
abi.encode(receiver_, sendingAmount_, messageId, payload_),
options_
);
if (returnedMessageId != messageId) revert MessageIdMisMatched();
emit BridgeTokens(
siblingChainSlug_,
msg.sender,
receiver_,
sendingAmount_,
messageId
);
}
/**
* @notice this function can be used to mint funds which were in pending state due to limits
* @param receiver_ address receiving bridged tokens
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param identifier_ message identifier where message was received to mint funds
*/functionmintPendingFor(address receiver_,
uint32 siblingChainSlug_,
bytes32 identifier_
) externalnonReentrant{
if (_receivingLimitParams[siblingChainSlug_].maxLimit ==0)
revert SiblingNotSupported();
uint256 pendingMint = pendingMints[siblingChainSlug_][receiver_][
identifier_
];
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
pendingMint,
_receivingLimitParams[siblingChainSlug_]
);
pendingMints[siblingChainSlug_][receiver_][identifier_] = pendingAmount;
siblingPendingMints[siblingChainSlug_] -= consumedAmount;
_mint(receiver_, consumedAmount);
if (
pendingAmount ==0&&
pendingExecutions[identifier_].receiver !=address(0)
) {
// execute
pendingExecutions[identifier_].isAmountPending =false;
bool success = _execute(
receiver_,
pendingExecutions[identifier_].payload
);
if (success) _clearPayload(identifier_);
}
emit PendingTokensBridged(
siblingChainSlug_,
receiver_,
consumedAmount,
pendingAmount,
identifier_
);
}
/**
* @notice this function receives the message from message bridge
* @dev Only bridge can call this function.
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param payload_ payload which is decoded to get `receiver`, `amount to mint`, `message id` and `payload` to execute after token transfer.
*/functioninbound(uint32 siblingChainSlug_,
bytesmemory payload_
) externalpayableoverridenonReentrant{
if (msg.sender!=address(bridge__)) revert NotMessageBridge();
if (_receivingLimitParams[siblingChainSlug_].maxLimit ==0)
revert SiblingNotSupported();
(
address receiver,
uint256 mintAmount,
bytes32 identifier,
bytesmemory execPayload
) =abi.decode(payload_, (address, uint256, bytes32, bytes));
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
mintAmount,
_receivingLimitParams[siblingChainSlug_]
);
if (receiver ==address(this) || receiver ==address(bridge__))
revert CannotExecuteOnBridgeContracts();
_mint(receiver, consumedAmount);
if (pendingAmount >0) {
pendingMints[siblingChainSlug_][receiver][
identifier
] = pendingAmount;
siblingPendingMints[siblingChainSlug_] += pendingAmount;
// if pending amount is more than 0, payload is cachedif (execPayload.length>0)
_cachePayload(
identifier,
siblingChainSlug_,
true,
receiver,
execPayload
);
emit TokensPending(
siblingChainSlug_,
receiver,
pendingAmount,
pendingMints[siblingChainSlug_][receiver][identifier],
identifier
);
} elseif (execPayload.length>0) {
// executebool success = _execute(receiver, execPayload);
if (!success)
_cachePayload(
identifier,
siblingChainSlug_,
false,
receiver,
execPayload
);
}
emit TokensBridged(
siblingChainSlug_,
receiver,
consumedAmount,
mintAmount,
identifier
);
}
functiongetCurrentReceivingLimit(uint32 siblingChainSlug_
) externalviewreturns (uint256) {
return _getCurrentLimit(_receivingLimitParams[siblingChainSlug_]);
}
functiongetCurrentSendingLimit(uint32 siblingChainSlug_
) externalviewreturns (uint256) {
return _getCurrentLimit(_sendingLimitParams[siblingChainSlug_]);
}
functiongetReceivingLimitParams(uint32 siblingChainSlug_
) externalviewreturns (LimitParams memory) {
return _receivingLimitParams[siblingChainSlug_];
}
functiongetSendingLimitParams(uint32 siblingChainSlug_
) externalviewreturns (LimitParams memory) {
return _sendingLimitParams[siblingChainSlug_];
}
/**
* @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.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
Contract Source Code
File 22 of 23: SuperTokenVault.sol
pragmasolidity 0.8.13;import"lib/solmate/src/utils/SafeTransferLib.sol";
import {AccessControl} from"../common/AccessControl.sol";
import {Gauge} from"../common/Gauge.sol";
import {RescueFundsLib} from"../libraries/RescueFundsLib.sol";
import"./Execute.sol";
import {ISuperTokenOrVault} from"./ISuperTokenOrVault.sol";
import {IMessageBridge} from"./IMessageBridge.sol";
/**
* @title SuperTokenVault
* @notice Vault contract which is used to lock/unlock token and enable bridging to its sibling chains.
* @dev This contract implements ISuperTokenOrVault to support message bridging through IMessageBridge compliant contracts.
*/contractSuperTokenVaultisGauge, ISuperTokenOrVault, AccessControl, Execute{
usingSafeTransferLibforERC20;
structUpdateLimitParams {
bool isLock;
uint32 siblingChainSlug;
uint256 maxLimit;
uint256 ratePerSecond;
}
bytes32constant RESCUE_ROLE =keccak256("RESCUE_ROLE");
bytes32constant LIMIT_UPDATER_ROLE =keccak256("LIMIT_UPDATER_ROLE");
ERC20 publicimmutable token__;
IMessageBridge public bridge__;
// siblingChainSlug => receiver => identifier => pendingUnlockmapping(uint32=>mapping(address=>mapping(bytes32=>uint256)))
public pendingUnlocks;
// siblingChainSlug => amountmapping(uint32=>uint256) public siblingPendingUnlocks;
// siblingChainSlug => lockLimitParamsmapping(uint32=> LimitParams) _lockLimitParams;
// siblingChainSlug => unlockLimitParamsmapping(uint32=> LimitParams) _unlockLimitParams;
////////////////////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////////////////////////errorSiblingChainSlugUnavailable();
errorZeroAmount();
errorNotMessageBridge();
////////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////////////// emitted when a message bridge is updatedeventMessageBridgeUpdated(address bridge);
// emitted when limit params are updatedeventLimitParamsUpdated(UpdateLimitParams[] updates);
// emitted at source when tokens are deposited to be bridged to a sibling chaineventTokensDeposited(uint32 siblingChainSlug,
address depositor,
address receiver,
uint256 depositAmount
);
// emitted when pending tokens are transferred to the receivereventPendingTokensTransferred(uint32 siblingChainSlug,
address receiver,
uint256 unlockedAmount,
uint256 pendingAmount
);
// emitted when transfer reaches limit and token transfer is added to pending queueeventTokensPending(uint32 siblingChainSlug,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount
);
// emitted when pending tokens are unlocked as limits are replenishedeventTokensUnlocked(uint32 siblingChainSlug,
address receiver,
uint256 unlockedAmount
);
/**
* @notice constructor for creating a new SuperTokenVault.
* @param token_ token contract address which is to be bridged.
* @param owner_ owner of this contract
* @param bridge_ message bridge address
*/constructor(address token_,
address owner_,
address bridge_
) AccessControl(owner_) {
token__ = ERC20(token_);
bridge__ = IMessageBridge(bridge_);
}
/**
* @notice this function is used to update message bridge
* @dev it can only be updated by owner
* @dev should be carefully migrated as it can risk user funds
* @param bridge_ new bridge address
*/functionupdateMessageBridge(address bridge_) externalonlyOwner{
bridge__ = IMessageBridge(bridge_);
emit MessageBridgeUpdated(bridge_);
}
/**
* @notice this function is used to set bridge limits
* @dev it can only be updated by owner
* @param updates_ can be used to set mint and burn limits for all siblings in one call.
*/functionupdateLimitParams(
UpdateLimitParams[] calldata updates_
) externalonlyRole(LIMIT_UPDATER_ROLE) {
for (uint256 i; i < updates_.length; i++) {
if (updates_[i].isLock) {
_consumePartLimit(
0,
_lockLimitParams[updates_[i].siblingChainSlug]
); // to keep current limit in sync
_lockLimitParams[updates_[i].siblingChainSlug]
.maxLimit = updates_[i].maxLimit;
_lockLimitParams[updates_[i].siblingChainSlug]
.ratePerSecond = updates_[i].ratePerSecond;
} else {
_consumePartLimit(
0,
_unlockLimitParams[updates_[i].siblingChainSlug]
); // to keep current limit in sync
_unlockLimitParams[updates_[i].siblingChainSlug]
.maxLimit = updates_[i].maxLimit;
_unlockLimitParams[updates_[i].siblingChainSlug]
.ratePerSecond = updates_[i].ratePerSecond;
}
}
emit LimitParamsUpdated(updates_);
}
/**
* @notice this function is called by users to bridge their funds to a sibling chain
* @dev it is payable to receive message bridge fees to be paid.
* @param receiver_ address receiving bridged tokens
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param amount_ amount bridged
* @param msgGasLimit_ min gas limit needed for execution at destination
* @param payload_ payload which is executed at destination with bridged amount at receiver address.
* @param options_ additional message bridge options can be provided using this param
*/functionbridge(address receiver_,
uint32 siblingChainSlug_,
uint256 amount_,
uint256 msgGasLimit_,
bytescalldata payload_,
bytescalldata options_
) externalpayable{
if (amount_ ==0) revert ZeroAmount();
if (_lockLimitParams[siblingChainSlug_].maxLimit ==0)
revert SiblingChainSlugUnavailable();
_consumeFullLimit(amount_, _lockLimitParams[siblingChainSlug_]); // reverts on limit hit
token__.safeTransferFrom(msg.sender, address(this), amount_);
bytes32 messageId = bridge__.getMessageId(siblingChainSlug_);
bridge__.outbound{value: msg.value}(
siblingChainSlug_,
msgGasLimit_,
abi.encode(receiver_, amount_, messageId, payload_),
options_
);
emit TokensDeposited(siblingChainSlug_, msg.sender, receiver_, amount_);
}
/**
* @notice this function can be used to unlock funds which were in pending state due to limits
* @param receiver_ address receiving bridged tokens
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param identifier_ message identifier where message was received to unlock funds
*/functionunlockPendingFor(address receiver_,
uint32 siblingChainSlug_,
bytes32 identifier_
) externalnonReentrant{
if (_unlockLimitParams[siblingChainSlug_].maxLimit ==0)
revert SiblingChainSlugUnavailable();
uint256 pendingUnlock = pendingUnlocks[siblingChainSlug_][receiver_][
identifier_
];
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
pendingUnlock,
_unlockLimitParams[siblingChainSlug_]
);
pendingUnlocks[siblingChainSlug_][receiver_][
identifier_
] = pendingAmount;
siblingPendingUnlocks[siblingChainSlug_] -= consumedAmount;
token__.safeTransfer(receiver_, consumedAmount);
if (
pendingAmount ==0&&
pendingExecutions[identifier_].receiver !=address(0)
) {
// execute
pendingExecutions[identifier_].isAmountPending =false;
bool success = _execute(
receiver_,
pendingExecutions[identifier_].payload
);
if (success) _clearPayload(identifier_);
}
emit PendingTokensTransferred(
siblingChainSlug_,
receiver_,
consumedAmount,
pendingAmount
);
}
/**
* @notice this function receives the message from message bridge
* @dev Only bridge can call this function.
* @param siblingChainSlug_ The unique identifier of the sibling chain.
* @param payload_ payload which is decoded to get `receiver`, `amount to mint`, `message id` and `payload` to execute after token transfer.
*/functioninbound(uint32 siblingChainSlug_,
bytesmemory payload_
) externalpayableoverridenonReentrant{
if (msg.sender!=address(bridge__)) revert NotMessageBridge();
if (_unlockLimitParams[siblingChainSlug_].maxLimit ==0)
revert SiblingChainSlugUnavailable();
(
address receiver,
uint256 unlockAmount,
bytes32 identifier,
bytesmemory execPayload
) =abi.decode(payload_, (address, uint256, bytes32, bytes));
if (receiver ==address(this) || receiver ==address(bridge__))
revert CannotExecuteOnBridgeContracts();
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
unlockAmount,
_unlockLimitParams[siblingChainSlug_]
);
token__.safeTransfer(receiver, consumedAmount);
if (pendingAmount >0) {
// add instead of overwrite to handle case where already pending amount is left
pendingUnlocks[siblingChainSlug_][receiver][
identifier
] += pendingAmount;
siblingPendingUnlocks[siblingChainSlug_] += pendingAmount;
// cache payloadif (execPayload.length>0)
_cachePayload(
identifier,
siblingChainSlug_,
true,
receiver,
execPayload
);
emit TokensPending(
siblingChainSlug_,
receiver,
pendingAmount,
pendingUnlocks[siblingChainSlug_][receiver][identifier]
);
} elseif (execPayload.length>0) {
// executebool success = _execute(receiver, execPayload);
if (!success)
_cachePayload(
identifier,
siblingChainSlug_,
false,
receiver,
execPayload
);
}
emit TokensUnlocked(siblingChainSlug_, receiver, consumedAmount);
}
functiongetCurrentLockLimit(uint32 siblingChainSlug_
) externalviewreturns (uint256) {
return _getCurrentLimit(_lockLimitParams[siblingChainSlug_]);
}
functiongetCurrentUnlockLimit(uint32 siblingChainSlug_
) externalviewreturns (uint256) {
return _getCurrentLimit(_unlockLimitParams[siblingChainSlug_]);
}
functiongetLockLimitParams(uint32 siblingChainSlug_
) externalviewreturns (LimitParams memory) {
return _lockLimitParams[siblingChainSlug_];
}
functiongetUnlockLimitParams(uint32 siblingChainSlug_
) externalviewreturns (LimitParams memory) {
return _unlockLimitParams[siblingChainSlug_];
}
/**
* @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.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
Contract Source Code
File 23 of 23: Vault.sol
pragmasolidity 0.8.13;import"lib/solmate/src/utils/SafeTransferLib.sol";
import"../common/Ownable.sol";
import {Gauge} from"../common/Gauge.sol";
import {IConnector, IHub} from"./ConnectorPlug.sol";
import {RescueFundsLib} from"../libraries/RescueFundsLib.sol";
// @todo: separate our connecter plugscontractVaultisGauge, IHub, Ownable(msg.sender) {
usingSafeTransferLibforERC20;
ERC20 publicimmutable token__;
structUpdateLimitParams {
bool isLock;
address connector;
uint256 maxLimit;
uint256 ratePerSecond;
}
// connector => receiver => pendingUnlockmapping(address=>mapping(address=>uint256)) public pendingUnlocks;
// connector => amountmapping(address=>uint256) public connectorPendingUnlocks;
// connector => lockLimitParamsmapping(address=> LimitParams) _lockLimitParams;
// connector => unlockLimitParamsmapping(address=> LimitParams) _unlockLimitParams;
errorConnectorUnavailable();
errorZeroAmount();
eventLimitParamsUpdated(UpdateLimitParams[] updates);
eventTokensDeposited(address connector,
address depositor,
address receiver,
uint256 depositAmount
);
eventPendingTokensTransferred(address connector,
address receiver,
uint256 unlockedAmount,
uint256 pendingAmount
);
eventTokensPending(address connector,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount
);
eventTokensUnlocked(address connector,
address receiver,
uint256 unlockedAmount
);
constructor(address token_) {
token__ = ERC20(token_);
}
functionupdateLimitParams(
UpdateLimitParams[] calldata updates_
) externalonlyOwner{
for (uint256 i; i < updates_.length; i++) {
if (updates_[i].isLock) {
_consumePartLimit(0, _lockLimitParams[updates_[i].connector]); // to keep current limit in sync
_lockLimitParams[updates_[i].connector].maxLimit = updates_[i]
.maxLimit;
_lockLimitParams[updates_[i].connector]
.ratePerSecond = updates_[i].ratePerSecond;
} else {
_consumePartLimit(0, _unlockLimitParams[updates_[i].connector]); // to keep current limit in sync
_unlockLimitParams[updates_[i].connector].maxLimit = updates_[i]
.maxLimit;
_unlockLimitParams[updates_[i].connector]
.ratePerSecond = updates_[i].ratePerSecond;
}
}
emit LimitParamsUpdated(updates_);
}
functiondepositToAppChain(address receiver_,
uint256 amount_,
uint256 msgGasLimit_,
address connector_
) externalpayable{
if (amount_ ==0) revert ZeroAmount();
if (_lockLimitParams[connector_].maxLimit ==0)
revert ConnectorUnavailable();
_consumeFullLimit(amount_, _lockLimitParams[connector_]); // reverts on limit hit
token__.safeTransferFrom(msg.sender, address(this), amount_);
IConnector(connector_).outbound{value: msg.value}(
msgGasLimit_,
abi.encode(receiver_, amount_)
);
emit TokensDeposited(connector_, msg.sender, receiver_, amount_);
}
functionunlockPendingFor(address receiver_, address connector_) external{
if (_unlockLimitParams[connector_].maxLimit ==0)
revert ConnectorUnavailable();
uint256 pendingUnlock = pendingUnlocks[connector_][receiver_];
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
pendingUnlock,
_unlockLimitParams[connector_]
);
pendingUnlocks[connector_][receiver_] = pendingAmount;
connectorPendingUnlocks[connector_] -= consumedAmount;
token__.safeTransfer(receiver_, consumedAmount);
emit PendingTokensTransferred(
connector_,
receiver_,
consumedAmount,
pendingAmount
);
}
// receive inbound assuming connector calledfunctionreceiveInbound(bytesmemory payload_) externaloverride{
if (_unlockLimitParams[msg.sender].maxLimit ==0)
revert ConnectorUnavailable();
(address receiver, uint256 unlockAmount) =abi.decode(
payload_,
(address, uint256)
);
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
unlockAmount,
_unlockLimitParams[msg.sender]
);
if (pendingAmount >0) {
// add instead of overwrite to handle case where already pending amount is left
pendingUnlocks[msg.sender][receiver] += pendingAmount;
connectorPendingUnlocks[msg.sender] += pendingAmount;
emit TokensPending(
msg.sender,
receiver,
pendingAmount,
pendingUnlocks[msg.sender][receiver]
);
}
token__.safeTransfer(receiver, consumedAmount);
emit TokensUnlocked(msg.sender, receiver, consumedAmount);
}
functiongetMinFees(address connector_,
uint256 msgGasLimit_
) externalviewreturns (uint256 totalFees) {
return IConnector(connector_).getMinFees(msgGasLimit_);
}
functiongetCurrentLockLimit(address connector_
) externalviewreturns (uint256) {
return _getCurrentLimit(_lockLimitParams[connector_]);
}
functiongetCurrentUnlockLimit(address connector_
) externalviewreturns (uint256) {
return _getCurrentLimit(_unlockLimitParams[connector_]);
}
functiongetLockLimitParams(address connector_
) externalviewreturns (LimitParams memory) {
return _lockLimitParams[connector_];
}
functiongetUnlockLimitParams(address connector_
) externalviewreturns (LimitParams memory) {
return _unlockLimitParams[connector_];
}
/**
* @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.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyOwner{
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}