Source Code
Latest 12 internal transactions
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
To
|
|||
|---|---|---|---|---|---|---|---|
| Outbound | 23395967 | 50 days ago | 0.0000152 ETH | ||||
| Deposit To App C... | 23395967 | 50 days ago | 0.0000152 ETH | ||||
| Outbound | 23345794 | 57 days ago | 0.0000092 ETH | ||||
| Deposit To App C... | 23345794 | 57 days ago | 0.0000092 ETH | ||||
| Outbound | 23288124 | 65 days ago | 0.00001488 ETH | ||||
| Deposit To App C... | 23288124 | 65 days ago | 0.00001488 ETH | ||||
| Outbound | 23231345 | 73 days ago | 0.00002752 ETH | ||||
| Deposit To App C... | 23231345 | 73 days ago | 0.00002752 ETH | ||||
| Outbound | 23144384 | 85 days ago | 0.00003086 ETH | ||||
| Deposit To App C... | 23144384 | 85 days ago | 0.00003086 ETH | ||||
| Outbound | 23121519 | 88 days ago | 0.00022 ETH | ||||
| Deposit To App C... | 23121519 | 88 days ago | 0.00022 ETH |
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
Vault
Compiler Version
v0.8.13+commit.abaa5c0e
Optimization Enabled:
Yes with 999999 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
pragma solidity 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 plugs
contract Vault is Gauge, IHub, Ownable(msg.sender) {
using SafeTransferLib for ERC20;
ERC20 public immutable token__;
struct UpdateLimitParams {
bool isLock;
address connector;
uint256 maxLimit;
uint256 ratePerSecond;
}
// connector => receiver => pendingUnlock
mapping(address => mapping(address => uint256)) public pendingUnlocks;
// connector => amount
mapping(address => uint256) public connectorPendingUnlocks;
// connector => lockLimitParams
mapping(address => LimitParams) _lockLimitParams;
// connector => unlockLimitParams
mapping(address => LimitParams) _unlockLimitParams;
// depositor => isPermitted
bool public permitListEnabled = false;
mapping(address => bool) public isPermitted;
error ConnectorUnavailable();
error ZeroAmount();
error NotPermitted();
event PermitListEnabledUpdated(bool enabled);
event PermittedUpdated(address[] addresses, bool isPermitted);
event LimitParamsUpdated(UpdateLimitParams[] updates);
event TokensDeposited(
address connector,
address depositor,
address receiver,
uint256 depositAmount
);
event PendingTokensTransferred(
address connector,
address receiver,
uint256 unlockedAmount,
uint256 pendingAmount
);
event TokensPending(
address connector,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount
);
event TokensUnlocked(
address connector,
address receiver,
uint256 unlockedAmount
);
constructor(address token_) {
token__ = ERC20(token_);
}
function setPermitListEnabled(bool enabled_) external onlyOwner {
permitListEnabled = enabled_;
emit PermitListEnabledUpdated(enabled_);
}
function setPermitted(
address[] calldata addresses_,
bool isPermitted_
) external onlyOwner {
for (uint256 i; i < addresses_.length; i++) {
isPermitted[addresses_[i]] = isPermitted_;
}
emit PermittedUpdated(addresses_, isPermitted_);
}
function updateLimitParams(
UpdateLimitParams[] calldata updates_
) external onlyOwner {
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_);
}
function depositToAppChain(
address receiver_,
uint256 amount_,
uint256 msgGasLimit_,
address connector_
) external payable {
if (permitListEnabled && !isPermitted[msg.sender]) revert NotPermitted();
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_);
}
function unlockPendingFor(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 called
function receiveInbound(bytes memory payload_) external override {
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);
}
function getMinFees(
address connector_,
uint256 msgGasLimit_
) external view returns (uint256 totalFees) {
return IConnector(connector_).getMinFees(msgGasLimit_);
}
function getCurrentLockLimit(
address connector_
) external view returns (uint256) {
return _getCurrentLimit(_lockLimitParams[connector_]);
}
function getCurrentUnlockLimit(
address connector_
) external view returns (uint256) {
return _getCurrentLimit(_unlockLimitParams[connector_]);
}
function getLockLimitParams(
address connector_
) external view returns (LimitParams memory) {
return _lockLimitParams[connector_];
}
function getUnlockLimitParams(
address connector_
) external view returns (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.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyOwner {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 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.
*/
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_];
}
}pragma solidity 0.8.13;
abstract contract Gauge {
struct LimitParams {
uint256 lastUpdateTimestamp;
uint256 ratePerSecond;
uint256 maxLimit;
uint256 lastUpdateLimit;
}
error AmountOutsideLimit();
function _getCurrentLimit(
LimitParams storage _params
) internal view returns (uint256 _limit) {
uint256 timeElapsed = block.timestamp - _params.lastUpdateTimestamp;
uint256 limitIncrease = timeElapsed * _params.ratePerSecond;
if (limitIncrease + _params.lastUpdateLimit > _params.maxLimit) {
_limit = _params.maxLimit;
} else {
_limit = limitIncrease + _params.lastUpdateLimit;
}
}
function _consumePartLimit(
uint256 amount_,
LimitParams storage _params
) internal returns (uint256 consumedAmount, uint256 pendingAmount) {
uint256 currentLimit = _getCurrentLimit(_params);
_params.lastUpdateTimestamp = block.timestamp;
if (currentLimit >= amount_) {
_params.lastUpdateLimit = currentLimit - amount_;
consumedAmount = amount_;
pendingAmount = 0;
} else {
_params.lastUpdateLimit = 0;
consumedAmount = currentLimit;
pendingAmount = amount_ - currentLimit;
}
}
function _consumeFullLimit(
uint256 amount_,
LimitParams storage _params
) internal {
uint256 currentLimit = _getCurrentLimit(_params);
if (currentLimit >= amount_) {
_params.lastUpdateTimestamp = block.timestamp;
_params.lastUpdateLimit = currentLimit - amount_;
} else {
revert AmountOutsideLimit();
}
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 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.
*/
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.13;
/**
* @title IPlug
* @notice Interface for a plug contract that executes the message received from a source chain.
*/
interface IPlug {
/**
* @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
*/
function inbound(
uint32 srcChainSlug_,
bytes calldata payload_
) external payable;
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 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
*/
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 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 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 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
*/
event MessageOutbound(
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
*/
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 memory 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 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 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);
/**
* @notice returns chain slug
* @return chainSlug current chain slug
*/
function chainSlug() external view returns (uint32 chainSlug);
function globalMessageCount() external view returns (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
*/
function getPlugConfig(
address plugAddress_,
uint32 siblingChainSlug_
)
external
view
returns (
address siblingPlug,
address inboundSwitchboard__,
address outboundSwitchboard__,
address capacitor__,
address decapacitor__
);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.13;
library ExcessivelySafeCall {
uint constant 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.
function excessivelySafeCall(
address _target,
uint _gas,
uint16 _maxCopy,
bytes memory _calldata
) internal returns (bool, bytes memory) {
// set up for assembly call
uint _toCopy;
bool _success;
bytes memory _returnData = new bytes(_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 contract
assembly {
_success := call(
_gas, // gas
_target, // recipient
0, // ether value
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_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.
function excessivelySafeStaticCall(
address _target,
uint _gas,
uint16 _maxCopy,
bytes memory _calldata
) internal view returns (bool, bytes memory) {
// set up for assembly call
uint _toCopy;
bool _success;
bytes memory _returnData = new bytes(_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 contract
assembly {
_success := staticcall(
_gas, // gas
_target, // recipient
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_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
*/
function swapSelector(
bytes4 _newSelector,
bytes memory _buf
) internal pure {
require(_buf.length >= 4);
uint _mask = LOW_28_MASK;
assembly {
// load the first word of
let _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)
}
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.13;
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_);
}
}
}pragma solidity 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";
interface IHub {
function receiveInbound(bytes memory payload_) external;
}
interface IConnector {
function outbound(
uint256 msgGasLimit_,
bytes memory payload_
) external payable;
function siblingChainSlug() external view returns (uint32);
function getMinFees(
uint256 msgGasLimit_
) external view returns (uint256 totalFees);
}
contract ConnectorPlug is IConnector, IPlug, Ownable {
IHub public immutable hub__;
ISocket public immutable socket__;
uint32 public immutable siblingChainSlug;
error NotHub();
error NotSocket();
event ConnectorPlugDisconnected();
constructor(
address hub_,
address socket_,
uint32 siblingChainSlug_
) Ownable(msg.sender) {
hub__ = IHub(hub_);
socket__ = ISocket(socket_);
siblingChainSlug = siblingChainSlug_;
}
function outbound(
uint256 msgGasLimit_,
bytes memory payload_
) external payable override {
if (msg.sender != address(hub__)) revert NotHub();
socket__.outbound{value: msg.value}(
siblingChainSlug,
msgGasLimit_,
bytes32(0),
bytes32(0),
payload_
);
}
function inbound(
uint32 /* siblingChainSlug_ */, // cannot be connected for any other slug, immutable variable
bytes calldata payload_
) external payable override {
if (msg.sender != address(socket__)) revert NotSocket();
hub__.receiveInbound(payload_);
}
function getMinFees(
uint256 msgGasLimit_
) external view override returns (uint256 totalFees) {
return
socket__.getMinFees(
msgGasLimit_,
64,
bytes32(0),
bytes32(0),
siblingChainSlug,
address(this)
);
}
function connect(
address siblingPlug_,
address switchboard_
) external onlyOwner {
socket__.connect(
siblingChainSlug,
siblingPlug_,
switchboard_,
switchboard_
);
}
function disconnect() external onlyOwner {
(
,
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.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyOwner {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}pragma solidity 0.8.13;
import {IExchangeRate} from "./ExchangeRate.sol";
import {Ownable} from "../common/Ownable.sol";
import {Gauge} from "../common/Gauge.sol";
import {IConnector, IHub} from "./ConnectorPlug.sol";
import {IMintableERC20} from "./IMintableERC20.sol";
import {RescueFundsLib} from "../libraries/RescueFundsLib.sol";
contract Controller is IHub, Gauge, Ownable(msg.sender) {
IMintableERC20 public immutable token__;
IExchangeRate public exchangeRate__;
struct UpdateLimitParams {
bool isMint;
address connector;
uint256 maxLimit;
uint256 ratePerSecond;
}
// connectorPoolId => totalLockedAmount
mapping(uint256 => uint256) public poolLockedAmounts;
// connector => connectorPoolId
mapping(address => uint256) public connectorPoolIds;
// connector => mintLimitParams
mapping(address => LimitParams) _mintLimitParams;
// connector => burnLimitParams
mapping(address => LimitParams) _burnLimitParams;
// connector => receiver => amount
mapping(address => mapping(address => uint256)) public pendingMints;
// connector => amount
mapping(address => uint256) public connectorPendingMints;
uint256 public totalMinted;
// depositor => isPermitted
bool public permitListEnabled = false;
mapping(address => bool) public isPermitted;
error ConnectorUnavailable();
error InvalidPoolId();
error ZeroAmount();
error NotPermitted();
event PermitListEnabledUpdated(bool enabled);
event PermittedUpdated(address[] addresses, bool isPermitted);
event ExchangeRateUpdated(address exchangeRate);
event ConnectorPoolIdUpdated(address connector, uint256 poolId);
event LimitParamsUpdated(UpdateLimitParams[] updates);
event TokensWithdrawn(
address connector,
address withdrawer,
address receiver,
uint256 burnAmount
);
event PendingTokensMinted(
address connector,
address receiver,
uint256 mintAmount,
uint256 pendingAmount
);
event TokensPending(
address connecter,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount
);
event TokensMinted(address connecter, address receiver, uint256 mintAmount);
constructor(address token_, address exchangeRate_) {
token__ = IMintableERC20(token_);
exchangeRate__ = IExchangeRate(exchangeRate_);
}
function setPermitListEnabled(bool enabled_) external onlyOwner {
permitListEnabled = enabled_;
emit PermitListEnabledUpdated(enabled_);
}
function setPermitted(
address[] calldata addresses_,
bool isPermitted_
) external onlyOwner {
for (uint256 i; i < addresses_.length; i++) {
isPermitted[addresses_[i]] = isPermitted_;
}
emit PermittedUpdated(addresses_, isPermitted_);
}
function updateExchangeRate(address exchangeRate_) external onlyOwner {
exchangeRate__ = IExchangeRate(exchangeRate_);
emit ExchangeRateUpdated(exchangeRate_);
}
function updateConnectorPoolId(
address[] calldata connectors,
uint256[] calldata poolIds
) external onlyOwner {
uint256 length = connectors.length;
for (uint256 i; i < length; i++) {
if (poolIds[i] == 0) revert InvalidPoolId();
connectorPoolIds[connectors[i]] = poolIds[i];
emit ConnectorPoolIdUpdated(connectors[i], poolIds[i]);
}
}
function updateLimitParams(
UpdateLimitParams[] calldata updates_
) external onlyOwner {
for (uint256 i; i < updates_.length; i++) {
if (updates_[i].isMint) {
_consumePartLimit(0, _mintLimitParams[updates_[i].connector]); // to keep current limit in sync
_mintLimitParams[updates_[i].connector].maxLimit = updates_[i]
.maxLimit;
_mintLimitParams[updates_[i].connector]
.ratePerSecond = updates_[i].ratePerSecond;
} else {
_consumePartLimit(0, _burnLimitParams[updates_[i].connector]); // to keep current limit in sync
_burnLimitParams[updates_[i].connector].maxLimit = updates_[i]
.maxLimit;
_burnLimitParams[updates_[i].connector]
.ratePerSecond = updates_[i].ratePerSecond;
}
}
emit LimitParamsUpdated(updates_);
}
// do we throttle burn amount or unlock amount? burn for now
function withdrawFromAppChain(
address receiver_,
uint256 burnAmount_,
uint256 msgGasLimit_,
address connector_
) external payable {
if (permitListEnabled && !isPermitted[msg.sender]) revert NotPermitted();
if (burnAmount_ == 0) revert ZeroAmount();
if (_burnLimitParams[connector_].maxLimit == 0)
revert ConnectorUnavailable();
_consumeFullLimit(burnAmount_, _burnLimitParams[connector_]); // reverts on limit hit
totalMinted -= burnAmount_;
_burn(msg.sender, burnAmount_);
uint256 connectorPoolId = connectorPoolIds[connector_];
if (connectorPoolId == 0) revert InvalidPoolId();
uint256 unlockAmount = exchangeRate__.getUnlockAmount(
burnAmount_,
poolLockedAmounts[connectorPoolId]
);
poolLockedAmounts[connectorPoolId] -= unlockAmount; // underflow revert expected
IConnector(connector_).outbound{value: msg.value}(
msgGasLimit_,
abi.encode(receiver_, unlockAmount)
);
emit TokensWithdrawn(connector_, msg.sender, receiver_, burnAmount_);
}
function _burn(address user_, uint256 burnAmount_) internal virtual {
token__.burn(user_, burnAmount_);
}
function mintPendingFor(address receiver_, address connector_) external {
if (_mintLimitParams[connector_].maxLimit == 0)
revert ConnectorUnavailable();
uint256 pendingMint = pendingMints[connector_][receiver_];
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
pendingMint,
_mintLimitParams[connector_]
);
pendingMints[connector_][receiver_] = pendingAmount;
connectorPendingMints[connector_] -= consumedAmount;
totalMinted += consumedAmount;
token__.mint(receiver_, consumedAmount);
emit PendingTokensMinted(
connector_,
receiver_,
consumedAmount,
pendingAmount
);
}
// receive inbound assuming connector called
function receiveInbound(bytes memory payload_) external override {
if (_mintLimitParams[msg.sender].maxLimit == 0)
revert ConnectorUnavailable();
(address receiver, uint256 lockAmount) = abi.decode(
payload_,
(address, uint256)
);
uint256 connectorPoolId = connectorPoolIds[msg.sender];
if (connectorPoolId == 0) revert InvalidPoolId();
poolLockedAmounts[connectorPoolId] += lockAmount;
uint256 mintAmount = exchangeRate__.getMintAmount(
lockAmount,
poolLockedAmounts[connectorPoolId]
);
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
mintAmount,
_mintLimitParams[msg.sender]
);
if (pendingAmount > 0) {
// add instead of overwrite to handle case where already pending amount is left
pendingMints[msg.sender][receiver] += pendingAmount;
connectorPendingMints[msg.sender] += pendingAmount;
emit TokensPending(
msg.sender,
receiver,
pendingAmount,
pendingMints[msg.sender][receiver]
);
}
totalMinted += consumedAmount;
token__.mint(receiver, consumedAmount);
emit TokensMinted(msg.sender, receiver, consumedAmount);
}
function getMinFees(
address connector_,
uint256 msgGasLimit_
) external view returns (uint256 totalFees) {
return IConnector(connector_).getMinFees(msgGasLimit_);
}
function getCurrentMintLimit(
address connector_
) external view returns (uint256) {
return _getCurrentLimit(_mintLimitParams[connector_]);
}
function getCurrentBurnLimit(
address connector_
) external view returns (uint256) {
return _getCurrentLimit(_burnLimitParams[connector_]);
}
function getMintLimitParams(
address connector_
) external view returns (LimitParams memory) {
return _mintLimitParams[connector_];
}
function getBurnLimitParams(
address connector_
) external view returns (LimitParams memory) {
return _burnLimitParams[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.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyOwner {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}pragma solidity 0.8.13;
import "../common/Ownable.sol";
import {RescueFundsLib} from "../libraries/RescueFundsLib.sol";
interface IExchangeRate {
// not marked pure, may involve state interactions in future
function getMintAmount(
uint256 lockAmount,
uint256 totalLockedAmount
) external returns (uint256 mintAmount);
// not marked pure, may involve state interactions in future
function getUnlockAmount(
uint256 burnAmount,
uint256 totalLockedAmount
) external returns (uint256 unlockAmount);
}
contract ExchangeRate is IExchangeRate, Ownable(msg.sender) {
// chainId input needed? what else? slippage?
function getMintAmount(
uint256 lockAmount,
uint256 /* totalLockedAmount */
) external pure returns (uint256 mintAmount) {
return lockAmount;
}
function getUnlockAmount(
uint256 burnAmount,
uint256 /* totalLockedAmount */
) external pure returns (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.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyOwner {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}pragma solidity 0.8.13;
import "lib/solmate/src/utils/SafeTransferLib.sol";
import {Controller} from "../Controller.sol";
import {IMintableERC20} from "../IMintableERC20.sol";
import {IFiatTokenV2_1_Mintable} from "./IFiatTokenV2_1_Mintable.sol";
contract FiatTokenV2_1_Controller is Controller {
using SafeTransferLib for IMintableERC20;
constructor(
address token_,
address exchangeRate_
) Controller(token_, exchangeRate_) {}
function _burn(address user_, uint256 burnAmount_) internal override {
token__.safeTransferFrom(user_, address(this), burnAmount_);
IFiatTokenV2_1_Mintable(address(token__)).burn(burnAmount_);
}
}pragma solidity 0.8.13;
import "lib/solmate/src/tokens/ERC20.sol";
// USDC's standard token
abstract contract IFiatTokenV2_1_Mintable is ERC20 {
function mint(address receiver_, uint256 amount_) external virtual;
function burn(uint256 _amount) external virtual;
}pragma solidity 0.8.13;
import "lib/solmate/src/tokens/ERC20.sol";
abstract contract IMintableERC20 is ERC20 {
function mint(address receiver_, uint256 amount_) external virtual;
function burn(address burner_, uint256 amount_) external virtual;
}pragma solidity 0.8.13;
import "lib/solmate/src/utils/ReentrancyGuard.sol";
import {RescueFundsLib} from "../libraries/RescueFundsLib.sol";
import {Gauge} from "../common/Gauge.sol";
import {AccessControl} from "../common/AccessControl.sol";
import "./interfaces/ISuperTokenOrVault.sol";
import "./interfaces/IMessageBridge.sol";
/**
* @title Base contract for super token and vault
* @notice It contains relevant execution payload storages.
* @dev This contract implements Socket's IPlug to enable message bridging and IMessageBridge
* to support any type of message bridge.
*/
abstract contract Base is
ReentrancyGuard,
Gauge,
ISuperTokenOrVault,
AccessControl
{
bytes32 constant RESCUE_ROLE = keccak256("RESCUE_ROLE");
error ZeroAddressReceiver();
error ZeroAmount();
/**
* @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_);
}
}pragma solidity 0.8.13;
/**
* @title IMessageBridge
* @notice It should be implemented by message bridge integrated to Super token and Vault.
*/
interface IMessageBridge {
/**
* @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
*/
function outbound(
uint32 siblingChainSlug_,
uint256 msgGasLimit_,
bytes memory payload_,
bytes memory options_
) external payable returns (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
*/
function getMessageId(
uint32 siblingChainSlug_
) external view returns (bytes32);
}pragma solidity 0.8.13;
/**
* @title ISuperTokenOrVault
* @notice It should be implemented Super token and Vault for plugs to communicate.
*/
interface ISuperTokenOrVault {
/**
* @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.
*/
function inbound(
uint32 siblingChainSlug_,
bytes memory payload_
) external payable;
}pragma solidity 0.8.13;
import "./ExecutionHelper.sol";
import "../Base.sol";
abstract contract ExecutablePayloadBase is Base {
/**
* @notice this struct stores relevant details for a pending payload execution
* @param receiver address of receiver where payload executes.
* @param payload payload to be executed
* @param isAmountPending if amount to be bridged is pending
*/
struct PendingExecutionDetails {
bool isAmountPending;
uint32 siblingChainSlug;
address receiver;
bytes payload;
}
ExecutionHelper public executionHelper__;
// messageId => PendingExecutionDetails
mapping(bytes32 => PendingExecutionDetails) public pendingExecutions;
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////
error InvalidExecutionRetry();
error PendingAmount();
error CannotExecuteOnBridgeContracts();
// emitted when a execution helper is updated
event ExecutionHelperUpdated(address executionHelper);
/**
* @notice this function is used to update execution helper contract
* @dev it can only be updated by owner
* @param executionHelper_ new execution helper address
*/
function updateExecutionHelper(
address executionHelper_
) external onlyOwner {
executionHelper__ = ExecutionHelper(executionHelper_);
emit ExecutionHelperUpdated(executionHelper_);
}
/**
* @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.
*/
function retryPayloadExecution(bytes32 msgId_) external nonReentrant {
PendingExecutionDetails storage details = pendingExecutions[msgId_];
if (details.isAmountPending) revert PendingAmount();
if (details.receiver == address(0)) revert InvalidExecutionRetry();
bool success = executionHelper__.execute(
details.receiver,
details.payload
);
if (success) _clearPayload(msgId_);
}
/**
* @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_,
bool isAmountPending_,
uint32 siblingChainSlug_,
address receiver_,
bytes memory payload_
) internal {
pendingExecutions[msgId_].receiver = receiver_;
pendingExecutions[msgId_].payload = payload_;
pendingExecutions[msgId_].siblingChainSlug = siblingChainSlug_;
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_].payload = bytes("");
pendingExecutions[msgId_].siblingChainSlug = 0;
pendingExecutions[msgId_].isAmountPending = false;
}
}pragma solidity 0.8.13;
import "../../libraries/ExcessivelySafeCall.sol";
/**
* @title ExecutionHelper
* @notice It is an untrusted contract used for payload execution by Super token and Vault.
*/
contract ExecutionHelper {
using ExcessivelySafeCall for address;
uint16 private constant MAX_COPY_BYTES = 0;
/**
* @notice this function is used to execute a payload at target_
* @dev receiver address cannot be this contract address.
* @param target_ address of target.
* @param payload_ payload to be executed at target.
*/
function execute(
address target_,
bytes memory payload_
) external returns (bool success) {
if (target_ == address(this)) return false;
(success, ) = target_.excessivelySafeCall(
gasleft(),
MAX_COPY_BYTES,
payload_
);
}
}pragma solidity 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 "../interfaces/IMessageBridge.sol";
import {ISuperTokenOrVault} from "../interfaces/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.
*/
contract SocketPlug is IPlug, AccessControl, IMessageBridge {
bytes32 constant RESCUE_ROLE = keccak256("RESCUE_ROLE");
// socket address
ISocket public immutable socket__;
// super token or vault address
ISuperTokenOrVault public tokenOrVault__;
// chain slug of current chain
uint32 public immutable chainSlug;
// map of sibling chain slugs with the plug addresses
mapping(uint32 => address) public siblingPlugs;
////////////////////////////////////////////////////////
////////////////////// EVENTS //////////////////////////
////////////////////////////////////////////////////////
// emitted when a plug is disconnected
event SocketPlugDisconnected(uint32 siblingChainSlug);
// emitted when a super token or vault address is set
event SuperTokenOrVaultSet();
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////
error NotSuperTokenOrVault();
error NotSocket();
error TokenOrVaultAlreadySet();
/**
* @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.
*/
function outbound(
uint32 siblingChainSlug_,
uint256 msgGasLimit_,
bytes memory payload_,
bytes memory
) external payable returns (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.
*/
function inbound(
uint32 siblingChainSlug_,
bytes memory payload_
) external payable override {
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.
*/
function getMinFees(
uint32 siblingChainSlug_,
uint256 msgGasLimit_,
uint256 payloadSize_
) external view returns (uint256 totalFees) {
return
socket__.getMinFees(
msgGasLimit_,
payloadSize_,
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.
*/
function setSuperTokenOrVault(address tokenOrVault_) external onlyOwner {
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
*/
function connect(
uint32 siblingChainSlug_,
address siblingPlug_,
address inboundSwitchboard_,
address outboundSwitchboard_
) external onlyOwner {
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.
*/
function disconnect(uint32 siblingChainSlug_) external onlyOwner {
delete siblingPlugs[siblingChainSlug_];
(
,
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
*/
function getMessageId(
uint32 siblingChainSlug_
) public view returns (bytes32) {
return
bytes32(
(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.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}pragma solidity 0.8.13;
import "lib/solmate/src/tokens/ERC20.sol";
import "./Base.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.
*/
contract SuperToken is ERC20, Base {
struct UpdateLimitParams {
bool isMint;
uint32 siblingChainSlug;
uint256 maxLimit;
uint256 ratePerSecond;
}
bytes32 constant LIMIT_UPDATER_ROLE = keccak256("LIMIT_UPDATER_ROLE");
// bridge contract address which provides AMB support
IMessageBridge public bridge__;
// siblingChainSlug => mintLimitParams
mapping(uint32 => LimitParams) _receivingLimitParams;
// siblingChainSlug => burnLimitParams
mapping(uint32 => LimitParams) _sendingLimitParams;
// siblingChainSlug => receiver => identifier => amount
mapping(uint32 => mapping(address => mapping(bytes32 => uint256)))
public pendingMints;
// siblingChainSlug => amount
mapping(uint32 => uint256) public siblingPendingMints;
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////
error SiblingNotSupported();
error MessageIdMisMatched();
error NotMessageBridge();
error InvalidSiblingChainSlug();
////////////////////////////////////////////////////////
////////////////////// EVENTS //////////////////////////
////////////////////////////////////////////////////////
// emitted when limit params are updated
event LimitParamsUpdated(UpdateLimitParams[] updates);
// emitted when message bridge is updated
event MessageBridgeUpdated(address newBridge);
// emitted at source when tokens are bridged to a sibling chain
event BridgeTokens(
uint32 siblingChainSlug,
address withdrawer,
address receiver,
uint256 bridgedAmount,
bytes32 identifier
);
// emitted when pending tokens are minted to the receiver
event PendingTokensBridged(
uint32 siblingChainSlug,
address receiver,
uint256 mintAmount,
uint256 pendingAmount,
bytes32 identifier
);
// emitted when transfer reaches limit and token mint is added to pending queue
event TokensPending(
uint32 siblingChainSlug,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount,
bytes32 identifier
);
// emitted when pending tokens are minted as limits are replenished
event TokensBridged(
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(
string memory name_,
string memory 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
*/
function updateMessageBridge(address bridge_) external onlyOwner {
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.
*/
function updateLimitParams(
UpdateLimitParams[] calldata updates_
) external onlyRole(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 options_ additional message bridge options can be provided using this param
*/
function bridge(
address receiver_,
uint32 siblingChainSlug_,
uint256 sendingAmount_,
uint256 msgGasLimit_,
bytes calldata options_
) external payable {
if (receiver_ == address(0)) revert ZeroAddressReceiver();
if (sendingAmount_ == 0) revert ZeroAmount();
if (_sendingLimitParams[siblingChainSlug_].maxLimit == 0)
revert SiblingNotSupported();
_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 caching
bytes32 returnedMessageId = bridge__.outbound{value: msg.value}(
siblingChainSlug_,
msgGasLimit_,
abi.encode(receiver_, sendingAmount_, messageId),
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
*/
function mintPendingFor(
address receiver_,
uint32 siblingChainSlug_,
bytes32 identifier_
) external nonReentrant {
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);
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.
*/
function inbound(
uint32 siblingChainSlug_,
bytes memory payload_
) external payable override nonReentrant {
if (msg.sender != address(bridge__)) revert NotMessageBridge();
if (_receivingLimitParams[siblingChainSlug_].maxLimit == 0)
revert SiblingNotSupported();
(address receiver, uint256 mintAmount, bytes32 identifier) = abi.decode(
payload_,
(address, uint256, bytes32)
);
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
mintAmount,
_receivingLimitParams[siblingChainSlug_]
);
_mint(receiver, consumedAmount);
if (pendingAmount > 0) {
pendingMints[siblingChainSlug_][receiver][
identifier
] = pendingAmount;
siblingPendingMints[siblingChainSlug_] += pendingAmount;
emit TokensPending(
siblingChainSlug_,
receiver,
pendingAmount,
pendingMints[siblingChainSlug_][receiver][identifier],
identifier
);
}
emit TokensBridged(
siblingChainSlug_,
receiver,
consumedAmount,
mintAmount,
identifier
);
}
function getCurrentReceivingLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_receivingLimitParams[siblingChainSlug_]);
}
function getCurrentSendingLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_sendingLimitParams[siblingChainSlug_]);
}
function getReceivingLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _receivingLimitParams[siblingChainSlug_];
}
function getSendingLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _sendingLimitParams[siblingChainSlug_];
}
}pragma solidity 0.8.13;
import "lib/solmate/src/utils/SafeTransferLib.sol";
import "./Base.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.
*/
contract SuperTokenVault is Base {
using SafeTransferLib for ERC20;
struct UpdateLimitParams {
bool isLock;
uint32 siblingChainSlug;
uint256 maxLimit;
uint256 ratePerSecond;
}
bytes32 constant LIMIT_UPDATER_ROLE = keccak256("LIMIT_UPDATER_ROLE");
ERC20 public immutable token__;
IMessageBridge public bridge__;
// siblingChainSlug => receiver => identifier => pendingUnlock
mapping(uint32 => mapping(address => mapping(bytes32 => uint256)))
public pendingUnlocks;
// siblingChainSlug => amount
mapping(uint32 => uint256) public siblingPendingUnlocks;
// siblingChainSlug => lockLimitParams
mapping(uint32 => LimitParams) _lockLimitParams;
// siblingChainSlug => unlockLimitParams
mapping(uint32 => LimitParams) _unlockLimitParams;
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////
error SiblingChainSlugUnavailable();
error NotMessageBridge();
error InvalidSiblingChainSlug();
error MessageIdMisMatched();
error InvalidTokenContract();
////////////////////////////////////////////////////////
////////////////////// EVENTS //////////////////////////
////////////////////////////////////////////////////////
// emitted when a message bridge is updated
event MessageBridgeUpdated(address bridge);
// emitted when limit params are updated
event LimitParamsUpdated(UpdateLimitParams[] updates);
// emitted at source when tokens are deposited to be bridged to a sibling chain
event TokensDeposited(
uint32 siblingChainSlug,
address depositor,
address receiver,
uint256 depositAmount,
bytes32 identifier
);
// emitted when pending tokens are transferred to the receiver
event PendingTokensTransferred(
uint32 siblingChainSlug,
address receiver,
uint256 unlockedAmount,
uint256 pendingAmount,
bytes32 identifier
);
// emitted when transfer reaches limit and token transfer is added to pending queue
event TokensPending(
uint32 siblingChainSlug,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount,
bytes32 identifier
);
// emitted when pending tokens are unlocked as limits are replenished
event TokensUnlocked(
uint32 siblingChainSlug,
address receiver,
uint256 unlockedAmount,
bytes32 identifier
);
/**
* @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_) {
if (token_.code.length == 0) revert InvalidTokenContract();
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
*/
function updateMessageBridge(address bridge_) external onlyOwner {
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.
*/
function updateLimitParams(
UpdateLimitParams[] calldata updates_
) external onlyRole(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 options_ additional message bridge options can be provided using this param
*/
function bridge(
address receiver_,
uint32 siblingChainSlug_,
uint256 amount_,
uint256 msgGasLimit_,
bytes calldata options_
) external payable {
if (receiver_ == address(0)) revert ZeroAddressReceiver();
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_);
bytes32 returnedMessageId = bridge__.outbound{value: msg.value}(
siblingChainSlug_,
msgGasLimit_,
abi.encode(receiver_, amount_, messageId),
options_
);
if (returnedMessageId != messageId) revert MessageIdMisMatched();
emit TokensDeposited(
siblingChainSlug_,
msg.sender,
receiver_,
amount_,
messageId
);
}
/**
* @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
*/
function unlockPendingFor(
address receiver_,
uint32 siblingChainSlug_,
bytes32 identifier_
) external nonReentrant {
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);
emit PendingTokensTransferred(
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.
*/
function inbound(
uint32 siblingChainSlug_,
bytes memory payload_
) external payable override nonReentrant {
if (msg.sender != address(bridge__)) revert NotMessageBridge();
if (_unlockLimitParams[siblingChainSlug_].maxLimit == 0)
revert SiblingChainSlugUnavailable();
(address receiver, uint256 unlockAmount, bytes32 identifier) = abi
.decode(payload_, (address, uint256, bytes32));
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
unlockAmount,
_unlockLimitParams[siblingChainSlug_]
);
token__.safeTransfer(receiver, consumedAmount);
if (pendingAmount > 0) {
pendingUnlocks[siblingChainSlug_][receiver][
identifier
] = pendingAmount;
siblingPendingUnlocks[siblingChainSlug_] += pendingAmount;
emit TokensPending(
siblingChainSlug_,
receiver,
pendingAmount,
pendingUnlocks[siblingChainSlug_][receiver][identifier],
identifier
);
}
emit TokensUnlocked(
siblingChainSlug_,
receiver,
consumedAmount,
identifier
);
}
function getCurrentLockLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_lockLimitParams[siblingChainSlug_]);
}
function getCurrentUnlockLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_unlockLimitParams[siblingChainSlug_]);
}
function getLockLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _lockLimitParams[siblingChainSlug_];
}
function getUnlockLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _unlockLimitParams[siblingChainSlug_];
}
}pragma solidity 0.8.13;
import "lib/solmate/src/utils/SafeTransferLib.sol";
import "./plugins/ExecutablePayloadBase.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.
*/
contract SuperTokenVaultWithExecutionPayload is ExecutablePayloadBase {
using SafeTransferLib for ERC20;
struct UpdateLimitParams {
bool isLock;
uint32 siblingChainSlug;
uint256 maxLimit;
uint256 ratePerSecond;
}
bytes32 constant LIMIT_UPDATER_ROLE = keccak256("LIMIT_UPDATER_ROLE");
ERC20 public immutable token__;
IMessageBridge public bridge__;
// siblingChainSlug => receiver => identifier => pendingUnlock
mapping(uint32 => mapping(address => mapping(bytes32 => uint256)))
public pendingUnlocks;
// siblingChainSlug => amount
mapping(uint32 => uint256) public siblingPendingUnlocks;
// siblingChainSlug => lockLimitParams
mapping(uint32 => LimitParams) _lockLimitParams;
// siblingChainSlug => unlockLimitParams
mapping(uint32 => LimitParams) _unlockLimitParams;
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////
error SiblingChainSlugUnavailable();
error NotMessageBridge();
error InvalidReceiver();
error InvalidSiblingChainSlug();
error MessageIdMisMatched();
error InvalidTokenContract();
////////////////////////////////////////////////////////
////////////////////// EVENTS //////////////////////////
////////////////////////////////////////////////////////
// emitted when a message bridge is updated
event MessageBridgeUpdated(address bridge);
// emitted when limit params are updated
event LimitParamsUpdated(UpdateLimitParams[] updates);
// emitted at source when tokens are deposited to be bridged to a sibling chain
event TokensDeposited(
uint32 siblingChainSlug,
address depositor,
address receiver,
uint256 depositAmount,
bytes32 identifier
);
// emitted when pending tokens are transferred to the receiver
event PendingTokensTransferred(
uint32 siblingChainSlug,
address receiver,
uint256 unlockedAmount,
uint256 pendingAmount,
bytes32 identifier
);
// emitted when transfer reaches limit and token transfer is added to pending queue
event TokensPending(
uint32 siblingChainSlug,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount,
bytes32 identifier
);
// emitted when pending tokens are unlocked as limits are replenished
event TokensUnlocked(
uint32 siblingChainSlug,
address receiver,
uint256 unlockedAmount,
bytes32 identifier
);
/**
* @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_,
address executionHelper_
) AccessControl(owner_) {
if (token_.code.length == 0) revert InvalidTokenContract();
token__ = ERC20(token_);
bridge__ = IMessageBridge(bridge_);
executionHelper__ = ExecutionHelper(executionHelper_);
}
/**
* @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
*/
function updateMessageBridge(address bridge_) external onlyOwner {
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.
*/
function updateLimitParams(
UpdateLimitParams[] calldata updates_
) external onlyRole(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
*/
function bridge(
address receiver_,
uint32 siblingChainSlug_,
uint256 amount_,
uint256 msgGasLimit_,
bytes calldata payload_,
bytes calldata options_
) external payable {
if (receiver_ == address(0)) revert ZeroAddressReceiver();
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_);
bytes32 returnedMessageId = bridge__.outbound{value: msg.value}(
siblingChainSlug_,
msgGasLimit_,
abi.encode(receiver_, amount_, messageId, payload_),
options_
);
if (returnedMessageId != messageId) revert MessageIdMisMatched();
emit TokensDeposited(
siblingChainSlug_,
msg.sender,
receiver_,
amount_,
messageId
);
}
/**
* @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
*/
function unlockPendingFor(
address receiver_,
uint32 siblingChainSlug_,
bytes32 identifier_
) external nonReentrant {
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);
address receiver = pendingExecutions[identifier_].receiver;
if (pendingAmount == 0 && receiver != address(0)) {
if (receiver_ != receiver) revert InvalidReceiver();
uint32 siblingChainSlug = pendingExecutions[identifier_]
.siblingChainSlug;
if (siblingChainSlug != siblingChainSlug_)
revert InvalidSiblingChainSlug();
// execute
pendingExecutions[identifier_].isAmountPending = false;
bool success = executionHelper__.execute(
receiver_,
pendingExecutions[identifier_].payload
);
if (success) _clearPayload(identifier_);
}
emit PendingTokensTransferred(
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.
*/
function inbound(
uint32 siblingChainSlug_,
bytes memory payload_
) external payable override nonReentrant {
if (msg.sender != address(bridge__)) revert NotMessageBridge();
if (_unlockLimitParams[siblingChainSlug_].maxLimit == 0)
revert SiblingChainSlugUnavailable();
(
address receiver,
uint256 unlockAmount,
bytes32 identifier,
bytes memory execPayload
) = abi.decode(payload_, (address, uint256, bytes32, bytes));
if (
receiver == address(this) ||
receiver == address(bridge__) ||
receiver == address(token__)
) revert CannotExecuteOnBridgeContracts();
(uint256 consumedAmount, uint256 pendingAmount) = _consumePartLimit(
unlockAmount,
_unlockLimitParams[siblingChainSlug_]
);
token__.safeTransfer(receiver, consumedAmount);
if (pendingAmount > 0) {
pendingUnlocks[siblingChainSlug_][receiver][
identifier
] = pendingAmount;
siblingPendingUnlocks[siblingChainSlug_] += pendingAmount;
// cache payload
if (execPayload.length > 0)
_cachePayload(
identifier,
true,
siblingChainSlug_,
receiver,
execPayload
);
emit TokensPending(
siblingChainSlug_,
receiver,
pendingAmount,
pendingUnlocks[siblingChainSlug_][receiver][identifier],
identifier
);
} else if (execPayload.length > 0) {
// execute
bool success = executionHelper__.execute(receiver, execPayload);
if (!success)
_cachePayload(
identifier,
false,
siblingChainSlug_,
receiver,
execPayload
);
}
emit TokensUnlocked(
siblingChainSlug_,
receiver,
consumedAmount,
identifier
);
}
function getCurrentLockLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_lockLimitParams[siblingChainSlug_]);
}
function getCurrentUnlockLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_unlockLimitParams[siblingChainSlug_]);
}
function getLockLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _lockLimitParams[siblingChainSlug_];
}
function getUnlockLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _unlockLimitParams[siblingChainSlug_];
}
}pragma solidity 0.8.13;
import "lib/solmate/src/tokens/ERC20.sol";
import "./plugins/ExecutablePayloadBase.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.
*/
contract SuperTokenWithExecutionPayload is ERC20, ExecutablePayloadBase {
struct UpdateLimitParams {
bool isMint;
uint32 siblingChainSlug;
uint256 maxLimit;
uint256 ratePerSecond;
}
bytes32 constant LIMIT_UPDATER_ROLE = keccak256("LIMIT_UPDATER_ROLE");
// bridge contract address which provides AMB support
IMessageBridge public bridge__;
// siblingChainSlug => mintLimitParams
mapping(uint32 => LimitParams) _receivingLimitParams;
// siblingChainSlug => burnLimitParams
mapping(uint32 => LimitParams) _sendingLimitParams;
// siblingChainSlug => receiver => identifier => amount
mapping(uint32 => mapping(address => mapping(bytes32 => uint256)))
public pendingMints;
// siblingChainSlug => amount
mapping(uint32 => uint256) public siblingPendingMints;
////////////////////////////////////////////////////////
////////////////////// ERRORS //////////////////////////
////////////////////////////////////////////////////////
error SiblingNotSupported();
error MessageIdMisMatched();
error NotMessageBridge();
error InvalidReceiver();
error InvalidSiblingChainSlug();
////////////////////////////////////////////////////////
////////////////////// EVENTS //////////////////////////
////////////////////////////////////////////////////////
// emitted when limit params are updated
event LimitParamsUpdated(UpdateLimitParams[] updates);
// emitted when message bridge is updated
event MessageBridgeUpdated(address newBridge);
// emitted at source when tokens are bridged to a sibling chain
event BridgeTokens(
uint32 siblingChainSlug,
address withdrawer,
address receiver,
uint256 bridgedAmount,
bytes32 identifier
);
// emitted when pending tokens are minted to the receiver
event PendingTokensBridged(
uint32 siblingChainSlug,
address receiver,
uint256 mintAmount,
uint256 pendingAmount,
bytes32 identifier
);
// emitted when transfer reaches limit and token mint is added to pending queue
event TokensPending(
uint32 siblingChainSlug,
address receiver,
uint256 pendingAmount,
uint256 totalPendingAmount,
bytes32 identifier
);
// emitted when pending tokens are minted as limits are replenished
event TokensBridged(
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(
string memory name_,
string memory symbol_,
uint8 decimals_,
address initialSupplyHolder_,
address owner_,
uint256 initialSupply_,
address bridge_,
address executionHelper_
) ERC20(name_, symbol_, decimals_) AccessControl(owner_) {
_mint(initialSupplyHolder_, initialSupply_);
bridge__ = IMessageBridge(bridge_);
executionHelper__ = ExecutionHelper(executionHelper_);
}
/**
* @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
*/
function updateMessageBridge(address bridge_) external onlyOwner {
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.
*/
function updateLimitParams(
UpdateLimitParams[] calldata updates_
) external onlyRole(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
*/
function bridge(
address receiver_,
uint32 siblingChainSlug_,
uint256 sendingAmount_,
uint256 msgGasLimit_,
bytes calldata payload_,
bytes calldata options_
) external payable {
if (receiver_ == address(0)) revert ZeroAddressReceiver();
if (sendingAmount_ == 0) revert ZeroAmount();
if (_sendingLimitParams[siblingChainSlug_].maxLimit == 0)
revert SiblingNotSupported();
_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 caching
bytes32 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
*/
function mintPendingFor(
address receiver_,
uint32 siblingChainSlug_,
bytes32 identifier_
) external nonReentrant {
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);
address receiver = pendingExecutions[identifier_].receiver;
if (pendingAmount == 0 && receiver != address(0)) {
if (receiver_ != receiver) revert InvalidReceiver();
uint32 siblingChainSlug = pendingExecutions[identifier_]
.siblingChainSlug;
if (siblingChainSlug != siblingChainSlug_)
revert InvalidSiblingChainSlug();
// execute
pendingExecutions[identifier_].isAmountPending = false;
bool success = executionHelper__.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.
*/
function inbound(
uint32 siblingChainSlug_,
bytes memory payload_
) external payable override nonReentrant {
if (msg.sender != address(bridge__)) revert NotMessageBridge();
if (_receivingLimitParams[siblingChainSlug_].maxLimit == 0)
revert SiblingNotSupported();
(
address receiver,
uint256 mintAmount,
bytes32 identifier,
bytes memory 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 cached
if (execPayload.length > 0)
_cachePayload(
identifier,
true,
siblingChainSlug_,
receiver,
execPayload
);
emit TokensPending(
siblingChainSlug_,
receiver,
pendingAmount,
pendingMints[siblingChainSlug_][receiver][identifier],
identifier
);
} else if (execPayload.length > 0) {
// execute
bool success = executionHelper__.execute(receiver, execPayload);
if (!success)
_cachePayload(
identifier,
false,
siblingChainSlug_,
receiver,
execPayload
);
}
emit TokensBridged(
siblingChainSlug_,
receiver,
consumedAmount,
mintAmount,
identifier
);
}
function getCurrentReceivingLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_receivingLimitParams[siblingChainSlug_]);
}
function getCurrentSendingLimit(
uint32 siblingChainSlug_
) external view returns (uint256) {
return _getCurrentLimit(_sendingLimitParams[siblingChainSlug_]);
}
function getReceivingLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _receivingLimitParams[siblingChainSlug_];
}
function getSendingLimitParams(
uint32 siblingChainSlug_
) external view returns (LimitParams memory) {
return _sendingLimitParams[siblingChainSlug_];
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}// 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");
}
}{
"optimizer": {
"enabled": true,
"runs": 999999
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"token_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AmountOutsideLimit","type":"error"},{"inputs":[],"name":"ConnectorUnavailable","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[],"name":"NotPermitted","type":"error"},{"inputs":[],"name":"OnlyNominee","type":"error"},{"inputs":[],"name":"OnlyOwner","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"components":[{"internalType":"bool","name":"isLock","type":"bool"},{"internalType":"address","name":"connector","type":"address"},{"internalType":"uint256","name":"maxLimit","type":"uint256"},{"internalType":"uint256","name":"ratePerSecond","type":"uint256"}],"indexed":false,"internalType":"struct Vault.UpdateLimitParams[]","name":"updates","type":"tuple[]"}],"name":"LimitParamsUpdated","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":false,"internalType":"address","name":"connector","type":"address"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"unlockedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"pendingAmount","type":"uint256"}],"name":"PendingTokensTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"PermitListEnabledUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"addresses","type":"address[]"},{"indexed":false,"internalType":"bool","name":"isPermitted","type":"bool"}],"name":"PermittedUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"connector","type":"address"},{"indexed":false,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"}],"name":"TokensDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"connector","type":"address"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"pendingAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalPendingAmount","type":"uint256"}],"name":"TokensPending","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"connector","type":"address"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"unlockedAmount","type":"uint256"}],"name":"TokensUnlocked","type":"event"},{"inputs":[],"name":"claimOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"connectorPendingUnlocks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"msgGasLimit_","type":"uint256"},{"internalType":"address","name":"connector_","type":"address"}],"name":"depositToAppChain","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"connector_","type":"address"}],"name":"getCurrentLockLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"connector_","type":"address"}],"name":"getCurrentUnlockLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"connector_","type":"address"}],"name":"getLockLimitParams","outputs":[{"components":[{"internalType":"uint256","name":"lastUpdateTimestamp","type":"uint256"},{"internalType":"uint256","name":"ratePerSecond","type":"uint256"},{"internalType":"uint256","name":"maxLimit","type":"uint256"},{"internalType":"uint256","name":"lastUpdateLimit","type":"uint256"}],"internalType":"struct Gauge.LimitParams","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"connector_","type":"address"},{"internalType":"uint256","name":"msgGasLimit_","type":"uint256"}],"name":"getMinFees","outputs":[{"internalType":"uint256","name":"totalFees","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"connector_","type":"address"}],"name":"getUnlockLimitParams","outputs":[{"components":[{"internalType":"uint256","name":"lastUpdateTimestamp","type":"uint256"},{"internalType":"uint256","name":"ratePerSecond","type":"uint256"},{"internalType":"uint256","name":"maxLimit","type":"uint256"},{"internalType":"uint256","name":"lastUpdateLimit","type":"uint256"}],"internalType":"struct Gauge.LimitParams","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isPermitted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"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":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"pendingUnlocks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"permitListEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload_","type":"bytes"}],"name":"receiveInbound","outputs":[],"stateMutability":"nonpayable","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":"bool","name":"enabled_","type":"bool"}],"name":"setPermitListEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses_","type":"address[]"},{"internalType":"bool","name":"isPermitted_","type":"bool"}],"name":"setPermitted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token__","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver_","type":"address"},{"internalType":"address","name":"connector_","type":"address"}],"name":"unlockPendingFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"isLock","type":"bool"},{"internalType":"address","name":"connector","type":"address"},{"internalType":"uint256","name":"maxLimit","type":"uint256"},{"internalType":"uint256","name":"ratePerSecond","type":"uint256"}],"internalType":"struct Vault.UpdateLimitParams[]","name":"updates_","type":"tuple[]"}],"name":"updateLimitParams","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
60a06040526006805460ff191690553480156200001b57600080fd5b5060405162001fbe38038062001fbe8339810160408190526200003e91620000b0565b336200004a816200005d565b506001600160a01b0316608052620000e2565b600080546001600160a01b0383166001600160a01b0319918216811783556001805490921690915560405190917ffbe19c9b601f5ee90b44c7390f3fa2319eba01762d34ee372aeafd59b25c7f8791a250565b600060208284031215620000c357600080fd5b81516001600160a01b0381168114620000db57600080fd5b9392505050565b608051611eab620001136000396000818161031c0152818161065301528181611038015261132b0152611eab6000f3fe60806040526004361061016a5760003560e01c80635b94db27116100cb57806384f289cc1161007f5780639b4cd940116100595780639b4cd9401461046f578063ad0f66941461048f578063f2b7c5cd146104a957600080fd5b806384f289cc14610411578063864f6a7a146104315780638da5cb5b1461044457600080fd5b80636ccae054116100b05780636ccae0541461037e578063755e1e241461039e5780638367080f146103f157600080fd5b80635b94db271461033e578063636ad0811461035e57600080fd5b80633bd1adec1161012257806341b2bf861161010757806341b2bf86146102ca5780634603f9f4146102ea57806349eec51e1461030a57600080fd5b80633bd1adec146102755780633fd8cc4e1461028a57600080fd5b806320f99c0a1161015357806320f99c0a146101dc5780632bc9c08a146102285780632fef4a8b1461024857600080fd5b806310c56ed91461016f578063186669e814610191575b600080fd5b34801561017b57600080fd5b5061018f61018a36600461189e565b6104c9565b005b34801561019d57600080fd5b506101c96101ac366004611992565b600260209081526000928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b3480156101e857600080fd5b5060015473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d3565b34801561023457600080fd5b5061018f6102433660046119cb565b6106d8565b34801561025457600080fd5b506101c9610263366004611a40565b60036020526000908152604090205481565b34801561028157600080fd5b5061018f610a18565b34801561029657600080fd5b506102ba6102a5366004611a40565b60076020526000908152604090205460ff1681565b60405190151581526020016101d3565b3480156102d657600080fd5b5061018f6102e5366004611a72565b610a74565b3480156102f657600080fd5b506101c9610305366004611a40565b610ba2565b34801561031657600080fd5b506102037f000000000000000000000000000000000000000000000000000000000000000081565b34801561034a57600080fd5b5061018f610359366004611a40565b610bd6565b34801561036a57600080fd5b5061018f610379366004611af6565b610c96565b34801561038a57600080fd5b5061018f610399366004611b11565b610d4c565b3480156103aa57600080fd5b506103be6103b9366004611a40565b610dad565b6040516101d391908151815260208083015190820152604080830151908201526060918201519181019190915260800190565b3480156103fd57600080fd5b506101c961040c366004611b52565b610e33565b34801561041d57600080fd5b506101c961042c366004611a40565b610ecc565b61018f61043f366004611b7e565b610efa565b34801561045057600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610203565b34801561047b57600080fd5b506103be61048a366004611a40565b61117e565b34801561049b57600080fd5b506006546102ba9060ff1681565b3480156104b557600080fd5b5061018f6104c4366004611992565b611204565b336000908152600560205260408120600201549003610514576040517fb1efb84a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000808280602001905181019061052b9190611bc8565b33600090815260056020526040812092945090925090819061054e9084906113ae565b909250905080156106395733600090815260026020908152604080832073ffffffffffffffffffffffffffffffffffffffff8816845290915281208054839290610599908490611c25565b909155505033600090815260036020526040812080548392906105bd908490611c25565b909155505033600081815260026020908152604080832073ffffffffffffffffffffffffffffffffffffffff891680855290835292819020548151948552918401929092528282018490526060830152517ffef75efa635291b302f2c3dfcc6ec92874e699d186cee8bd68c36c86ad35232c9181900360800190a15b61067a73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168584611402565b6040805133815273ffffffffffffffffffffffffffffffffffffffff861660208201529081018390527fece684e11f49f06d351439e63189ad1703238b8040d90cf994901ca2b3da8d44906060015b60405180910390a15050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610729576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156109da5782828281811061074657610746611c3d565b61075c9260206080909202019081019150611af6565b156108c2576107d360006004600086868681811061077c5761077c611c3d565b90506080020160200160208101906107949190611a40565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206113ae565b50508282828181106107e7576107e7611c3d565b905060800201604001356004600085858581811061080757610807611c3d565b905060800201602001602081019061081f9190611a40565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040016000206002015582828281811061085b5761085b611c3d565b905060800201606001356004600085858581811061087b5761087b611c3d565b90506080020160200160208101906108939190611a40565b73ffffffffffffffffffffffffffffffffffffffff1681526020810191909152604001600020600101556109c8565b6108dd60006005600086868681811061077c5761077c611c3d565b50508282828181106108f1576108f1611c3d565b905060800201604001356005600085858581811061091157610911611c3d565b90506080020160200160208101906109299190611a40565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040016000206002015582828281811061096557610965611c3d565b905060800201606001356005600085858581811061098557610985611c3d565b905060800201602001602081019061099d9190611a40565b73ffffffffffffffffffffffffffffffffffffffff1681526020810191909152604001600020600101555b806109d281611c6c565b91505061072c565b507f19863caed14ed012a54f927e56250018b7ac3c04fd197e0edf23369f75a6ccd48282604051610a0c929190611ca4565b60405180910390a15050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a69576040517f7c91ccdd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610a72336114dc565b565b60005473ffffffffffffffffffffffffffffffffffffffff163314610ac5576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b82811015610b61578160076000868685818110610ae757610ae7611c3d565b9050602002016020810190610afc9190611a40565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905580610b5981611c6c565b915050610ac8565b507fcb5b335e0174ef57b9a8fd87a033e135ab60b4bbd8119f877a7297f228e8409e838383604051610b9593929190611d27565b60405180910390a1505050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600460205260408120610bd090611554565b92915050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610c27576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce2290600090a250565b60005473ffffffffffffffffffffffffffffffffffffffff163314610ce7576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168215159081179091556040519081527fff45a227927e6940d23f41f29349e50d3d67ef33e3285ad6ba04d7f77ce2315c9060200160405180910390a150565b60005473ffffffffffffffffffffffffffffffffffffffff163314610d9d576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610da88383836115bb565b505050565b610dd86040518060800160405280600081526020016000815260200160008152602001600081525090565b5073ffffffffffffffffffffffffffffffffffffffff16600090815260046020908152604091829020825160808101845281548152600182015492810192909252600281015492820192909252600390910154606082015290565b6040517f89c1cf9a0000000000000000000000000000000000000000000000000000000081526004810182905260009073ffffffffffffffffffffffffffffffffffffffff8416906389c1cf9a90602401602060405180830381865afa158015610ea1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ec59190611d8d565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120610bd090611554565b60065460ff168015610f1c57503360009081526007602052604090205460ff16155b15610f53576040517f39218f3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82600003610f8d576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81166000908152600460205260408120600201549003610fee576040517fb1efb84a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116600090815260046020526040902061101e9084906116ab565b61106073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016333086611708565b8073ffffffffffffffffffffffffffffffffffffffff16631cbdf12b348487876040516020016110b292919073ffffffffffffffffffffffffffffffffffffffff929092168252602082015260400190565b6040516020818303038152906040526040518463ffffffff1660e01b81526004016110de929190611da6565b6000604051808303818588803b1580156110f757600080fd5b505af115801561110b573d6000803e3d6000fd5b50506040805173ffffffffffffffffffffffffffffffffffffffff8087168252336020830152891691810191909152606081018790527f9474e087d8a0e83962ac44e292b4aba027426203ea66adfb1dd9f65795ff599a935060800191506111709050565b60405180910390a150505050565b6111a96040518060800160405280600081526020016000815260200160008152602001600081525090565b5073ffffffffffffffffffffffffffffffffffffffff16600090815260056020908152604091829020825160808101845281548152600182015492810192909252600281015492820192909252600390910154606082015290565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120600201549003611265576040517fb1efb84a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff80821660008181526002602090815260408083209487168352938152838220549282526005905291822090919081906112b49084906113ae565b73ffffffffffffffffffffffffffffffffffffffff8087166000818152600260209081526040808320948c16835293815283822085905591815260039091529081208054939550919350849261130b908490611e21565b90915550611352905073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168684611402565b6040805173ffffffffffffffffffffffffffffffffffffffff808716825287166020820152908101839052606081018290527fc77b48bb4899bc689b5733d1b7b8bfc0f70e7660295ad5f94841902db16ddae2906080016106c9565b60008060006113bc84611554565b42855590508481106113e3576113d28582611e21565b6003850155849250600091506113fa565b600060038501559150816113f78186611e21565b91505b509250929050565b60006040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152826024820152602060006044836000895af13d15601f3d11600160005114161716915050806114d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c4544000000000000000000000000000000000060448201526064015b60405180910390fd5b50505050565b6000805473ffffffffffffffffffffffffffffffffffffffff83167fffffffffffffffffffffffff0000000000000000000000000000000000000000918216811783556001805490921690915560405190917ffbe19c9b601f5ee90b44c7390f3fa2319eba01762d34ee372aeafd59b25c7f8791a250565b805460009081906115659042611e21565b905060008360010154826115799190611e38565b905083600201548460030154826115909190611c25565b11156115a257836002015492506115b4565b60038401546115b19082611c25565b92505b5050919050565b73ffffffffffffffffffffffffffffffffffffffff8216611608576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff84160161164f57610da882826117fa565b8273ffffffffffffffffffffffffffffffffffffffff163b6000036116a0576040517f1eb00b0600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610da8838383611402565b60006116b682611554565b90508281106116d6574282556116cc8382611e21565b6003830155505050565b6040517f47ebad2000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff841660248201528260448201526020600060648360008a5af13d15601f3d11600160005114161716915050806117f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c454400000000000000000000000060448201526064016114cd565b5050505050565b600080600080600085875af1905080610da8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4554485f5452414e534645525f4641494c45440000000000000000000000000060448201526064016114cd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156118b057600080fd5b813567ffffffffffffffff808211156118c857600080fd5b818401915084601f8301126118dc57600080fd5b8135818111156118ee576118ee61186f565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156119345761193461186f565b8160405282815287602084870101111561194d57600080fd5b826020860160208301376000928101602001929092525095945050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461198f57600080fd5b50565b600080604083850312156119a557600080fd5b82356119b08161196d565b915060208301356119c08161196d565b809150509250929050565b600080602083850312156119de57600080fd5b823567ffffffffffffffff808211156119f657600080fd5b818501915085601f830112611a0a57600080fd5b813581811115611a1957600080fd5b8660208260071b8501011115611a2e57600080fd5b60209290920196919550909350505050565b600060208284031215611a5257600080fd5b8135610ec58161196d565b80358015158114611a6d57600080fd5b919050565b600080600060408486031215611a8757600080fd5b833567ffffffffffffffff80821115611a9f57600080fd5b818601915086601f830112611ab357600080fd5b813581811115611ac257600080fd5b8760208260051b8501011115611ad757600080fd5b602092830195509350611aed9186019050611a5d565b90509250925092565b600060208284031215611b0857600080fd5b610ec582611a5d565b600080600060608486031215611b2657600080fd5b8335611b318161196d565b92506020840135611b418161196d565b929592945050506040919091013590565b60008060408385031215611b6557600080fd5b8235611b708161196d565b946020939093013593505050565b60008060008060808587031215611b9457600080fd5b8435611b9f8161196d565b935060208501359250604085013591506060850135611bbd8161196d565b939692955090935050565b60008060408385031215611bdb57600080fd5b8251611be68161196d565b6020939093015192949293505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115611c3857611c38611bf6565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611c9d57611c9d611bf6565b5060010190565b6020808252818101839052600090604080840186845b87811015611d1a57611ccb82611a5d565b1515835284820135611cdc8161196d565b73ffffffffffffffffffffffffffffffffffffffff168386015281840135848401526060808301359084015260809283019290910190600101611cba565b5090979650505050505050565b6040808252810183905260008460608301825b86811015611d77578235611d4d8161196d565b73ffffffffffffffffffffffffffffffffffffffff16825260209283019290910190600101611d3a565b5080925050508215156020830152949350505050565b600060208284031215611d9f57600080fd5b5051919050565b82815260006020604081840152835180604085015260005b81811015611dda57858101830151858201606001528201611dbe565b81811115611dec576000606083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201606001949350505050565b600082821015611e3357611e33611bf6565b500390565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7057611e70611bf6565b50029056fea2646970667358221220fa6d2f23a6fe0e0141a7ee7f9d69765c413310141bd14f65f1421acdc52ea1ab64736f6c634300080d0033000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Deployed Bytecode
0x60806040526004361061016a5760003560e01c80635b94db27116100cb57806384f289cc1161007f5780639b4cd940116100595780639b4cd9401461046f578063ad0f66941461048f578063f2b7c5cd146104a957600080fd5b806384f289cc14610411578063864f6a7a146104315780638da5cb5b1461044457600080fd5b80636ccae054116100b05780636ccae0541461037e578063755e1e241461039e5780638367080f146103f157600080fd5b80635b94db271461033e578063636ad0811461035e57600080fd5b80633bd1adec1161012257806341b2bf861161010757806341b2bf86146102ca5780634603f9f4146102ea57806349eec51e1461030a57600080fd5b80633bd1adec146102755780633fd8cc4e1461028a57600080fd5b806320f99c0a1161015357806320f99c0a146101dc5780632bc9c08a146102285780632fef4a8b1461024857600080fd5b806310c56ed91461016f578063186669e814610191575b600080fd5b34801561017b57600080fd5b5061018f61018a36600461189e565b6104c9565b005b34801561019d57600080fd5b506101c96101ac366004611992565b600260209081526000928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b3480156101e857600080fd5b5060015473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d3565b34801561023457600080fd5b5061018f6102433660046119cb565b6106d8565b34801561025457600080fd5b506101c9610263366004611a40565b60036020526000908152604090205481565b34801561028157600080fd5b5061018f610a18565b34801561029657600080fd5b506102ba6102a5366004611a40565b60076020526000908152604090205460ff1681565b60405190151581526020016101d3565b3480156102d657600080fd5b5061018f6102e5366004611a72565b610a74565b3480156102f657600080fd5b506101c9610305366004611a40565b610ba2565b34801561031657600080fd5b506102037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b34801561034a57600080fd5b5061018f610359366004611a40565b610bd6565b34801561036a57600080fd5b5061018f610379366004611af6565b610c96565b34801561038a57600080fd5b5061018f610399366004611b11565b610d4c565b3480156103aa57600080fd5b506103be6103b9366004611a40565b610dad565b6040516101d391908151815260208083015190820152604080830151908201526060918201519181019190915260800190565b3480156103fd57600080fd5b506101c961040c366004611b52565b610e33565b34801561041d57600080fd5b506101c961042c366004611a40565b610ecc565b61018f61043f366004611b7e565b610efa565b34801561045057600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610203565b34801561047b57600080fd5b506103be61048a366004611a40565b61117e565b34801561049b57600080fd5b506006546102ba9060ff1681565b3480156104b557600080fd5b5061018f6104c4366004611992565b611204565b336000908152600560205260408120600201549003610514576040517fb1efb84a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000808280602001905181019061052b9190611bc8565b33600090815260056020526040812092945090925090819061054e9084906113ae565b909250905080156106395733600090815260026020908152604080832073ffffffffffffffffffffffffffffffffffffffff8816845290915281208054839290610599908490611c25565b909155505033600090815260036020526040812080548392906105bd908490611c25565b909155505033600081815260026020908152604080832073ffffffffffffffffffffffffffffffffffffffff891680855290835292819020548151948552918401929092528282018490526060830152517ffef75efa635291b302f2c3dfcc6ec92874e699d186cee8bd68c36c86ad35232c9181900360800190a15b61067a73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2168584611402565b6040805133815273ffffffffffffffffffffffffffffffffffffffff861660208201529081018390527fece684e11f49f06d351439e63189ad1703238b8040d90cf994901ca2b3da8d44906060015b60405180910390a15050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610729576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156109da5782828281811061074657610746611c3d565b61075c9260206080909202019081019150611af6565b156108c2576107d360006004600086868681811061077c5761077c611c3d565b90506080020160200160208101906107949190611a40565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206113ae565b50508282828181106107e7576107e7611c3d565b905060800201604001356004600085858581811061080757610807611c3d565b905060800201602001602081019061081f9190611a40565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040016000206002015582828281811061085b5761085b611c3d565b905060800201606001356004600085858581811061087b5761087b611c3d565b90506080020160200160208101906108939190611a40565b73ffffffffffffffffffffffffffffffffffffffff1681526020810191909152604001600020600101556109c8565b6108dd60006005600086868681811061077c5761077c611c3d565b50508282828181106108f1576108f1611c3d565b905060800201604001356005600085858581811061091157610911611c3d565b90506080020160200160208101906109299190611a40565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040016000206002015582828281811061096557610965611c3d565b905060800201606001356005600085858581811061098557610985611c3d565b905060800201602001602081019061099d9190611a40565b73ffffffffffffffffffffffffffffffffffffffff1681526020810191909152604001600020600101555b806109d281611c6c565b91505061072c565b507f19863caed14ed012a54f927e56250018b7ac3c04fd197e0edf23369f75a6ccd48282604051610a0c929190611ca4565b60405180910390a15050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a69576040517f7c91ccdd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610a72336114dc565b565b60005473ffffffffffffffffffffffffffffffffffffffff163314610ac5576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b82811015610b61578160076000868685818110610ae757610ae7611c3d565b9050602002016020810190610afc9190611a40565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905580610b5981611c6c565b915050610ac8565b507fcb5b335e0174ef57b9a8fd87a033e135ab60b4bbd8119f877a7297f228e8409e838383604051610b9593929190611d27565b60405180910390a1505050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600460205260408120610bd090611554565b92915050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610c27576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce2290600090a250565b60005473ffffffffffffffffffffffffffffffffffffffff163314610ce7576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168215159081179091556040519081527fff45a227927e6940d23f41f29349e50d3d67ef33e3285ad6ba04d7f77ce2315c9060200160405180910390a150565b60005473ffffffffffffffffffffffffffffffffffffffff163314610d9d576040517f5fc483c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610da88383836115bb565b505050565b610dd86040518060800160405280600081526020016000815260200160008152602001600081525090565b5073ffffffffffffffffffffffffffffffffffffffff16600090815260046020908152604091829020825160808101845281548152600182015492810192909252600281015492820192909252600390910154606082015290565b6040517f89c1cf9a0000000000000000000000000000000000000000000000000000000081526004810182905260009073ffffffffffffffffffffffffffffffffffffffff8416906389c1cf9a90602401602060405180830381865afa158015610ea1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ec59190611d8d565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120610bd090611554565b60065460ff168015610f1c57503360009081526007602052604090205460ff16155b15610f53576040517f39218f3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82600003610f8d576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81166000908152600460205260408120600201549003610fee576040517fb1efb84a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116600090815260046020526040902061101e9084906116ab565b61106073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216333086611708565b8073ffffffffffffffffffffffffffffffffffffffff16631cbdf12b348487876040516020016110b292919073ffffffffffffffffffffffffffffffffffffffff929092168252602082015260400190565b6040516020818303038152906040526040518463ffffffff1660e01b81526004016110de929190611da6565b6000604051808303818588803b1580156110f757600080fd5b505af115801561110b573d6000803e3d6000fd5b50506040805173ffffffffffffffffffffffffffffffffffffffff8087168252336020830152891691810191909152606081018790527f9474e087d8a0e83962ac44e292b4aba027426203ea66adfb1dd9f65795ff599a935060800191506111709050565b60405180910390a150505050565b6111a96040518060800160405280600081526020016000815260200160008152602001600081525090565b5073ffffffffffffffffffffffffffffffffffffffff16600090815260056020908152604091829020825160808101845281548152600182015492810192909252600281015492820192909252600390910154606082015290565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120600201549003611265576040517fb1efb84a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff80821660008181526002602090815260408083209487168352938152838220549282526005905291822090919081906112b49084906113ae565b73ffffffffffffffffffffffffffffffffffffffff8087166000818152600260209081526040808320948c16835293815283822085905591815260039091529081208054939550919350849261130b908490611e21565b90915550611352905073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2168684611402565b6040805173ffffffffffffffffffffffffffffffffffffffff808716825287166020820152908101839052606081018290527fc77b48bb4899bc689b5733d1b7b8bfc0f70e7660295ad5f94841902db16ddae2906080016106c9565b60008060006113bc84611554565b42855590508481106113e3576113d28582611e21565b6003850155849250600091506113fa565b600060038501559150816113f78186611e21565b91505b509250929050565b60006040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152826024820152602060006044836000895af13d15601f3d11600160005114161716915050806114d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c4544000000000000000000000000000000000060448201526064015b60405180910390fd5b50505050565b6000805473ffffffffffffffffffffffffffffffffffffffff83167fffffffffffffffffffffffff0000000000000000000000000000000000000000918216811783556001805490921690915560405190917ffbe19c9b601f5ee90b44c7390f3fa2319eba01762d34ee372aeafd59b25c7f8791a250565b805460009081906115659042611e21565b905060008360010154826115799190611e38565b905083600201548460030154826115909190611c25565b11156115a257836002015492506115b4565b60038401546115b19082611c25565b92505b5050919050565b73ffffffffffffffffffffffffffffffffffffffff8216611608576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff84160161164f57610da882826117fa565b8273ffffffffffffffffffffffffffffffffffffffff163b6000036116a0576040517f1eb00b0600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610da8838383611402565b60006116b682611554565b90508281106116d6574282556116cc8382611e21565b6003830155505050565b6040517f47ebad2000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff841660248201528260448201526020600060648360008a5af13d15601f3d11600160005114161716915050806117f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c454400000000000000000000000060448201526064016114cd565b5050505050565b600080600080600085875af1905080610da8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4554485f5452414e534645525f4641494c45440000000000000000000000000060448201526064016114cd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156118b057600080fd5b813567ffffffffffffffff808211156118c857600080fd5b818401915084601f8301126118dc57600080fd5b8135818111156118ee576118ee61186f565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156119345761193461186f565b8160405282815287602084870101111561194d57600080fd5b826020860160208301376000928101602001929092525095945050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461198f57600080fd5b50565b600080604083850312156119a557600080fd5b82356119b08161196d565b915060208301356119c08161196d565b809150509250929050565b600080602083850312156119de57600080fd5b823567ffffffffffffffff808211156119f657600080fd5b818501915085601f830112611a0a57600080fd5b813581811115611a1957600080fd5b8660208260071b8501011115611a2e57600080fd5b60209290920196919550909350505050565b600060208284031215611a5257600080fd5b8135610ec58161196d565b80358015158114611a6d57600080fd5b919050565b600080600060408486031215611a8757600080fd5b833567ffffffffffffffff80821115611a9f57600080fd5b818601915086601f830112611ab357600080fd5b813581811115611ac257600080fd5b8760208260051b8501011115611ad757600080fd5b602092830195509350611aed9186019050611a5d565b90509250925092565b600060208284031215611b0857600080fd5b610ec582611a5d565b600080600060608486031215611b2657600080fd5b8335611b318161196d565b92506020840135611b418161196d565b929592945050506040919091013590565b60008060408385031215611b6557600080fd5b8235611b708161196d565b946020939093013593505050565b60008060008060808587031215611b9457600080fd5b8435611b9f8161196d565b935060208501359250604085013591506060850135611bbd8161196d565b939692955090935050565b60008060408385031215611bdb57600080fd5b8251611be68161196d565b6020939093015192949293505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115611c3857611c38611bf6565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611c9d57611c9d611bf6565b5060010190565b6020808252818101839052600090604080840186845b87811015611d1a57611ccb82611a5d565b1515835284820135611cdc8161196d565b73ffffffffffffffffffffffffffffffffffffffff168386015281840135848401526060808301359084015260809283019290910190600101611cba565b5090979650505050505050565b6040808252810183905260008460608301825b86811015611d77578235611d4d8161196d565b73ffffffffffffffffffffffffffffffffffffffff16825260209283019290910190600101611d3a565b5080925050508215156020830152949350505050565b600060208284031215611d9f57600080fd5b5051919050565b82815260006020604081840152835180604085015260005b81811015611dda57858101830151858201606001528201611dbe565b81811115611dec576000606083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201606001949350505050565b600082821015611e3357611e33611bf6565b500390565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7057611e70611bf6565b50029056fea2646970667358221220fa6d2f23a6fe0e0141a7ee7f9d69765c413310141bd14f65f1421acdc52ea1ab64736f6c634300080d0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
-----Decoded View---------------
Arg [0] : token_ (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|---|---|---|---|---|
| ETH | 100.00% | $3,405.83 | 496.729 | $1,691,773.13 |
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.