// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;import"./Ownable.sol";
/**
* @title AccessControl
* @dev This abstract contract implements access control mechanism based on roles.
* Each role can have one or more addresses associated with it, which are granted
* permission to execute functions with the onlyRole modifier.
*/abstractcontractAccessControlisOwnable{
/**
* @dev A mapping of roles to a mapping of addresses to boolean values indicating whether or not they have the role.
*/mapping(bytes32=>mapping(address=>bool)) private _permits;
/**
* @dev Emitted when a role is granted to an address.
*/eventRoleGranted(bytes32indexed role, addressindexed grantee);
/**
* @dev Emitted when a role is revoked from an address.
*/eventRoleRevoked(bytes32indexed role, addressindexed revokee);
/**
* @dev Error message thrown when an address does not have permission to execute a function with onlyRole modifier.
*/errorNoPermit(bytes32 role);
/**
* @dev Constructor that sets the owner of the contract.
*/constructor(address owner_) Ownable(owner_) {}
/**
* @dev Modifier that restricts access to addresses having roles
* Throws an error if the caller do not have permit
*/modifieronlyRole(bytes32 role) {
if (!_permits[role][msg.sender]) revert NoPermit(role);
_;
}
/**
* @dev Checks and reverts if an address do not have a specific role.
* @param role_ The role to check.
* @param address_ The address to check.
*/function_checkRole(bytes32 role_, address address_) internalvirtual{
if (!_hasRole(role_, address_)) revert NoPermit(role_);
}
/**
* @dev Grants a role to a given address.
* @param role_ The role to grant.
* @param grantee_ The address to grant the role to.
* Emits a RoleGranted event.
* Can only be called by the owner of the contract.
*/functiongrantRole(bytes32 role_,
address grantee_
) externalvirtualonlyOwner{
_grantRole(role_, grantee_);
}
/**
* @dev Revokes a role from a given address.
* @param role_ The role to revoke.
* @param revokee_ The address to revoke the role from.
* Emits a RoleRevoked event.
* Can only be called by the owner of the contract.
*/functionrevokeRole(bytes32 role_,
address revokee_
) externalvirtualonlyOwner{
_revokeRole(role_, revokee_);
}
/**
* @dev Internal function to grant a role to a given address.
* @param role_ The role to grant.
* @param grantee_ The address to grant the role to.
* Emits a RoleGranted event.
*/function_grantRole(bytes32 role_, address grantee_) internal{
_permits[role_][grantee_] =true;
emit RoleGranted(role_, grantee_);
}
/**
* @dev Internal function to revoke a role from a given address.
* @param role_ The role to revoke.
* @param revokee_ The address to revoke the role from.
* Emits a RoleRevoked event.
*/function_revokeRole(bytes32 role_, address revokee_) internal{
_permits[role_][revokee_] =false;
emit RoleRevoked(role_, revokee_);
}
/**
* @dev Checks whether an address has a specific role.
* @param role_ The role to check.
* @param address_ The address to check.
* @return A boolean value indicating whether or not the address has the role.
*/functionhasRole(bytes32 role_,
address address_
) externalviewreturns (bool) {
return _hasRole(role_, address_);
}
function_hasRole(bytes32 role_,
address address_
) internalviewreturns (bool) {
return _permits[role_][address_];
}
}
Contract Source Code
File 2 of 44: Base.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import {IMintableERC20} from"../interfaces/IMintableERC20.sol";
import {IConnector} from"../interfaces/IConnector.sol";
import"lib/solmate/src/utils/SafeTransferLib.sol";
import"../interfaces/IHook.sol";
import"../common/Errors.sol";
import"lib/solmate/src/utils/ReentrancyGuard.sol";
import"../interfaces/IBridge.sol";
import"../utils/RescueBase.sol";
import"../common/Constants.sol";
abstractcontractBaseisReentrancyGuard, IBridge, RescueBase{
addresspublicimmutable token;
bytes32public bridgeType;
IHook public hook__;
// message identifier => cachemapping(bytes32=>bytes) public identifierCache;
// connector => cachemapping(address=>bytes) public connectorCache;
mapping(address=>bool) public validConnectors;
eventConnectorStatusUpdated(address connector, bool status);
eventHookUpdated(address newHook);
eventBridgingTokens(address connector,
address sender,
address receiver,
uint256 amount,
bytes32 messageId
);
eventTokensBridged(address connecter,
address receiver,
uint256 amount,
bytes32 messageId
);
constructor(address token_) AccessControl(msg.sender) {
if (token_ != ETH_ADDRESS && token_.code.length==0)
revert InvalidTokenContract();
token = token_;
_grantRole(RESCUE_ROLE, msg.sender);
}
/**
* @notice this function is used to update hook
* @dev it can only be updated by owner
* @dev should be carefully migrated as it can risk user funds
* @param hook_ new hook address
*/functionupdateHook(address hook_,
bool approve_
) externalvirtualonlyOwner{
// remove the approval from the old hookif (token != ETH_ADDRESS) {
if (ERC20(token).allowance(address(this), address(hook__)) >0) {
SafeTransferLib.safeApprove(ERC20(token), address(hook__), 0);
}
if (approve_) {
SafeTransferLib.safeApprove(
ERC20(token),
hook_,
type(uint256).max
);
}
}
hook__ = IHook(hook_);
emit HookUpdated(hook_);
}
functionupdateConnectorStatus(address[] calldata connectors,
bool[] calldata statuses
) externalonlyOwner{
uint256 length = connectors.length;
for (uint256 i; i < length; i++) {
validConnectors[connectors[i]] = statuses[i];
emit ConnectorStatusUpdated(connectors[i], statuses[i]);
}
}
/**
* @notice Executes pre-bridge operations before initiating a token bridge transfer.
* @dev This internal function is called before initiating a token bridge transfer.
* It validates the receiver address and the connector, and if a pre-hook contract is defined,
* it executes the source pre-hook call.
* @param connector_ The address of the connector responsible for the transfer.
* @param transferInfo_ Information about the transfer.
* @return transferInfo Information about the transfer after pre-bridge operations.
* @return postHookData Data returned from the pre-hook call.
* @dev Reverts with `ZeroAddressReceiver` if the receiver address is zero.
* Reverts with `InvalidConnector` if the connector address is not valid.
*/function_beforeBridge(address connector_,
TransferInfo memory transferInfo_
)
internalreturns (TransferInfo memory transferInfo, bytesmemory postHookData)
{
if (transferInfo_.receiver ==address(0)) revert ZeroAddressReceiver();
if (!validConnectors[connector_]) revert InvalidConnector();
if (token == ETH_ADDRESS &&msg.value< transferInfo_.amount)
revert InsufficientMsgValue();
if (address(hook__) !=address(0)) {
(transferInfo, postHookData) = hook__.srcPreHookCall(
SrcPreHookCallParams(connector_, msg.sender, transferInfo_)
);
} else {
transferInfo = transferInfo_;
}
}
/**
* @notice Executes post-bridge operations after completing a token bridge transfer.
* @dev This internal function is called after completing a token bridge transfer.
* It executes the source post-hook call if a hook contract is defined, calculates fees,
* calls the outbound function of the connector, and emits an event for tokens withdrawn.
* @param msgGasLimit_ The gas limit for the outbound call.
* @param connector_ The address of the connector responsible for the transfer.
* @param options_ Additional options for the outbound call.
* @param postHookData_ Data returned from the source post-hook call.
* @param transferInfo_ Information about the transfer.
* @dev Reverts with `MessageIdMisMatched` if the returned message ID does not match the expected message ID.
*/function_afterBridge(uint256 msgGasLimit_,
address connector_,
bytesmemory options_,
bytesmemory postHookData_,
TransferInfo memory transferInfo_
) internal{
TransferInfo memory transferInfo = transferInfo_;
if (address(hook__) !=address(0)) {
transferInfo = hook__.srcPostHookCall(
SrcPostHookCallParams(
connector_,
options_,
postHookData_,
transferInfo_
)
);
}
uint256 fees = token == ETH_ADDRESS
? msg.value- transferInfo.amount
: msg.value;
bytes32 messageId = IConnector(connector_).getMessageId();
bytes32 returnedMessageId = IConnector(connector_).outbound{
value: fees
}(
msgGasLimit_,
abi.encode(
transferInfo.receiver,
transferInfo.amount,
messageId,
transferInfo.extraData
),
options_
);
if (returnedMessageId != messageId) revert MessageIdMisMatched();
emit BridgingTokens(
connector_,
msg.sender,
transferInfo.receiver,
transferInfo.amount,
messageId
);
}
/**
* @notice Executes pre-mint operations before minting tokens.
* @dev This internal function is called before minting tokens.
* It validates the caller as a valid connector, checks if the receiver is not this contract, the bridge contract,
* or the token contract, and executes the destination pre-hook call if a hook contract is defined.
* @param transferInfo_ Information about the transfer.
* @return postHookData Data returned from the destination pre-hook call.
* @return transferInfo Information about the transfer after pre-mint operations.
* @dev Reverts with `InvalidConnector` if the caller is not a valid connector.
* Reverts with `CannotTransferOrExecuteOnBridgeContracts` if the receiver is this contract, the bridge contract,
* or the token contract.
*/function_beforeMint(uint32,
TransferInfo memory transferInfo_
)
internalreturns (bytesmemory postHookData, TransferInfo memory transferInfo)
{
if (!validConnectors[msg.sender]) revert InvalidConnector();
// no need of source check here, as if invalid caller, will revert with InvalidPoolIdif (
transferInfo_.receiver ==address(this) ||// transferInfo_.receiver == address(bridge__) ||
transferInfo_.receiver == token
) revert CannotTransferOrExecuteOnBridgeContracts();
if (address(hook__) !=address(0)) {
(postHookData, transferInfo) = hook__.dstPreHookCall(
DstPreHookCallParams(
msg.sender,
connectorCache[msg.sender],
transferInfo_
)
);
} else {
transferInfo = transferInfo_;
}
}
/**
* @notice Executes post-mint operations after minting tokens.
* @dev This internal function is called after minting tokens.
* It executes the destination post-hook call if a hook contract is defined and updates cache data.
* @param messageId_ The unique identifier for the mint transaction.
* @param postHookData_ Data returned from the destination pre-hook call.
* @param transferInfo_ Information about the mint transaction.
*/function_afterMint(uint256,
bytes32 messageId_,
bytesmemory postHookData_,
TransferInfo memory transferInfo_
) internal{
if (address(hook__) !=address(0)) {
CacheData memory cacheData = hook__.dstPostHookCall(
DstPostHookCallParams(
msg.sender,
messageId_,
connectorCache[msg.sender],
postHookData_,
transferInfo_
)
);
identifierCache[messageId_] = cacheData.identifierCache;
connectorCache[msg.sender] = cacheData.connectorCache;
}
emit TokensBridged(
msg.sender,
transferInfo_.receiver,
transferInfo_.amount,
messageId_
);
}
/**
* @notice Executes pre-retry operations before retrying a failed transaction.
* @dev This internal function is called before retrying a failed transaction.
* It validates the connector, retrieves cache data for the given message ID,
* and executes the pre-retry hook if defined.
* @param connector_ The address of the connector responsible for the failed transaction.
* @param messageId_ The unique identifier for the failed transaction.
* @return postHookData Data returned from the pre-retry hook call.
* @return transferInfo Information about the transfer.
* @dev Reverts with `InvalidConnector` if the connector is not valid.
* Reverts with `NoPendingData` if there is no pending data for the given message ID.
*/function_beforeRetry(address connector_,
bytes32 messageId_
)
internalreturns (bytesmemory postHookData, TransferInfo memory transferInfo)
{
if (!validConnectors[connector_]) revert InvalidConnector();
CacheData memory cacheData = CacheData(
identifierCache[messageId_],
connectorCache[connector_]
);
if (cacheData.identifierCache.length==0) revert NoPendingData();
(postHookData, transferInfo) = hook__.preRetryHook(
PreRetryHookCallParams(connector_, cacheData)
);
}
/**
* @notice Executes post-retry operations after retrying a failed transaction.
* @dev This internal function is called after retrying a failed transaction.
* It retrieves cache data for the given message ID, executes the post-retry hook if defined,
* and updates cache data.
* @param connector_ The address of the connector responsible for the failed transaction.
* @param messageId_ The unique identifier for the failed transaction.
* @param postHookData Data returned from the pre-retry hook call.
*/function_afterRetry(address connector_,
bytes32 messageId_,
bytesmemory postHookData
) internal{
CacheData memory cacheData = CacheData(
identifierCache[messageId_],
connectorCache[connector_]
);
(cacheData) = hook__.postRetryHook(
PostRetryHookCallParams(
connector_,
messageId_,
postHookData,
cacheData
)
);
identifierCache[messageId_] = cacheData.identifierCache;
connectorCache[connector_] = cacheData.connectorCache;
}
/**
* @notice Retrieves the minimum fees required for a transaction from a connector.
* @dev This function returns the minimum fees required for a transaction from the specified connector,
* based on the provided message gas limit and payload size.
* @param connector_ The address of the connector.
* @param msgGasLimit_ The gas limit for the transaction.
* @param payloadSize_ The size of the payload for the transaction.
* @return totalFees The total minimum fees required for the transaction.
*/functiongetMinFees(address connector_,
uint256 msgGasLimit_,
uint256 payloadSize_
) externalviewreturns (uint256 totalFees) {
return IConnector(connector_).getMinFees(msgGasLimit_, payloadSize_);
}
}
Contract Source Code
File 3 of 44: ConnectorPlug.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"./utils/RescueBase.sol";
import {ISocket} from"./interfaces/ISocket.sol";
import {IPlug} from"./interfaces/IPlug.sol";
import {IConnector} from"./interfaces/IConnector.sol";
import {IBridge} from"./interfaces/IBridge.sol";
import"./common/Errors.sol";
contractConnectorPlugisIConnector, IPlug, RescueBase{
IBridge publicimmutable bridge__;
ISocket publicimmutable socket__;
uint32publicimmutable siblingChainSlug;
bytes32publicimmutable transmissionParams;
uint256public messageIdPart;
eventConnectorPlugDisconnected();
constructor(address bridge_,
address socket_,
uint32 siblingChainSlug_,
bytes32 transmissionParams_
) AccessControl(msg.sender) {
bridge__ = IBridge(bridge_);
socket__ = ISocket(socket_);
siblingChainSlug = siblingChainSlug_;
transmissionParams = transmissionParams_;
_grantRole(RESCUE_ROLE, msg.sender);
}
functionoutbound(uint256 msgGasLimit_,
bytesmemory payload_,
bytesmemory options_
) externalpayableoverridereturns (bytes32 messageId_) {
if (msg.sender!=address(bridge__)) revert NotBridge();
if (options_.length==0) {
return
socket__.outbound{value: msg.value}(
siblingChainSlug,
msgGasLimit_,
bytes32(0),
transmissionParams,
payload_
);
} else {
if (options_.length!=32) revert InvalidOptionsLength();
bytes32 executionParams =abi.decode(options_, (bytes32));
return
socket__.outbound{value: msg.value}(
siblingChainSlug,
msgGasLimit_,
executionParams,
transmissionParams,
payload_
);
}
}
functioninbound(uint32 siblingChainSlug_, // cannot be connected for any other slug, immutable variablebytescalldata payload_
) externalpayableoverride{
if (msg.sender!=address(socket__)) revert NotSocket();
bridge__.receiveInbound(siblingChainSlug_, payload_);
}
/**
* @notice this function calculates the fees needed to send the message to Socket.
* @param msgGasLimit_ min gas limit needed at destination chain to execute the message.
*/functiongetMinFees(uint256 msgGasLimit_,
uint256 payloadSize_
) externalviewreturns (uint256 totalFees) {
return
socket__.getMinFees(
msgGasLimit_,
payloadSize_,
bytes32(0),
bytes32(0),
siblingChainSlug,
address(this)
);
}
functionconnect(address siblingPlug_,
address switchboard_
) externalonlyOwner{
messageIdPart =
(uint256(socket__.chainSlug()) <<224) |
(uint256(uint160(siblingPlug_)) <<64);
socket__.connect(
siblingChainSlug,
siblingPlug_,
switchboard_,
switchboard_
);
}
functiondisconnect() externalonlyOwner{
messageIdPart =0;
(
,
address inboundSwitchboard,
address outboundSwitchboard,
,
) = socket__.getPlugConfig(address(this), siblingChainSlug);
socket__.connect(
siblingChainSlug,
address(0),
inboundSwitchboard,
outboundSwitchboard
);
emit ConnectorPlugDisconnected();
}
/**
* @notice this function is used to calculate message id before sending outbound().
* @return messageId
*/functiongetMessageId() externalviewreturns (bytes32) {
returnbytes32(messageIdPart | (socket__.globalMessageCount()));
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"./Base.sol";
contractControllerisBase{
uint256public totalMinted;
constructor(address token_) Base(token_) {
bridgeType = NORMAL_CONTROLLER;
}
/**
* @notice Bridges tokens between chains.
* @dev This function allows bridging tokens between different chains.
* @param receiver_ The address to receive the bridged tokens.
* @param amount_ The amount of tokens to bridge.
* @param msgGasLimit_ The gas limit for the execution of the bridging process.
* @param connector_ The address of the connector contract responsible for the bridge.
* @param extraData_ The extra data passed to hook functions.
* @param options_ Additional options for the bridging process.
*/functionbridge(address receiver_,
uint256 amount_,
uint256 msgGasLimit_,
address connector_,
bytescalldata extraData_,
bytescalldata options_
) externalpayablenonReentrant{
(
TransferInfo memory transferInfo,
bytesmemory postHookData
) = _beforeBridge(
connector_,
TransferInfo(receiver_, amount_, extraData_)
);
// to maintain socket dl specific accounting for super token// re check this logic for mint and mint use cases and if other minter involved
totalMinted -= transferInfo.amount;
_burn(msg.sender, transferInfo.amount);
_afterBridge(
msgGasLimit_,
connector_,
options_,
postHookData,
transferInfo
);
}
/**
* @notice Receives inbound tokens from another chain.
* @dev This function is used to receive tokens from another chain.
* @param siblingChainSlug_ The identifier of the sibling chain.
* @param payload_ The payload containing the inbound tokens.
*/functionreceiveInbound(uint32 siblingChainSlug_,
bytesmemory payload_
) externalpayableoverridenonReentrant{
(
address receiver,
uint256 lockAmount,
bytes32 messageId,
bytesmemory extraData
) =abi.decode(payload_, (address, uint256, bytes32, bytes));
// convert to shares
TransferInfo memory transferInfo = TransferInfo(
receiver,
lockAmount,
extraData
);
bytesmemory postHookData;
(postHookData, transferInfo) = _beforeMint(
siblingChainSlug_,
transferInfo
);
_mint(transferInfo.receiver, transferInfo.amount);
totalMinted += transferInfo.amount;
_afterMint(lockAmount, messageId, postHookData, transferInfo);
}
/**
* @notice Retry a failed transaction.
* @dev This function allows retrying a failed transaction sent through a connector.
* @param connector_ The address of the connector contract responsible for the failed transaction.
* @param messageId_ The unique identifier of the failed transaction.
*/functionretry(address connector_,
bytes32 messageId_
) externalnonReentrant{
(
bytesmemory postHookData,
TransferInfo memory transferInfo
) = _beforeRetry(connector_, messageId_);
_mint(transferInfo.receiver, transferInfo.amount);
totalMinted += transferInfo.amount;
_afterRetry(connector_, messageId_, postHookData);
}
function_burn(address user_, uint256 burnAmount_) internalvirtual{
IMintableERC20(token).burn(user_, burnAmount_);
}
function_mint(address user_, uint256 mintAmount_) internalvirtual{
if (mintAmount_ ==0) return;
IMintableERC20(token).mint(user_, mintAmount_);
}
}
Contract Source Code
File 7 of 44: Controller_YieldLimitExecHook.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
import {FixedPointMathLib} from"lib/solmate/src/utils/FixedPointMathLib.sol";
import {IStrategy} from"../interfaces/IStrategy.sol";
import {IMintableERC20} from"../interfaces/IMintableERC20.sol";
import"lib/solmate/src/utils/SafeTransferLib.sol";
import {IConnector} from"../ConnectorPlug.sol";
import"./LimitExecutionHook.sol";
interfaceIYieldToken{
functionupdateTotalUnderlyingAssets(uint256 amount_) external;
functioncalculateMintAmount(uint256 amount_) externalreturns (uint256);
functionconvertToShares(uint256 underlyingAssets
) externalviewreturns (uint256);
functiontransfer(address to_, uint256 amount_) externalreturns (bool);
functionconvertToAssets(uint256 shares) externalviewreturns (uint256);
}
// limits on underlying or visible tokenscontractController_YieldLimitExecHookisLimitExecutionHook{
usingSafeTransferLibforIMintableERC20;
usingFixedPointMathLibforuint256;
uint256privateconstant MAX_BPS =10_000;
IYieldToken publicimmutable yieldToken__;
// total yielduint256public totalUnderlyingAssets;
// if true, no funds can be invested in the strategyboolpublic emergencyShutdown;
eventShutdownStateUpdated(bool shutdownState);
modifiernotShutdown() {
if (emergencyShutdown) revert VaultShutdown();
_;
}
constructor(address underlyingAsset_,
address controller_,
address executionHelper_
) LimitExecutionHook(msg.sender, controller_, executionHelper_, true) {
yieldToken__ = IYieldToken(underlyingAsset_);
hookType = LIMIT_EXECUTION_YIELD_TOKEN_HOOK;
_grantRole(LIMIT_UPDATER_ROLE, msg.sender);
}
// assumed transfer info inputs are validated at controller// transfer info data is untrustedfunctionsrcPreHookCall(
SrcPreHookCallParams calldata params_
)
publicoverridenotShutdownreturns (TransferInfo memory transferInfo, bytesmemory postHookData)
{
super.srcPreHookCall(params_);
uint256 amount = params_.transferInfo.amount;
postHookData =abi.encode(amount);
totalUnderlyingAssets -= amount;
transferInfo = params_.transferInfo;
transferInfo.amount = yieldToken__.convertToShares(amount);
}
functionsrcPostHookCall(
SrcPostHookCallParams memory srcPostHookCallParams_
)
publicoverrideisVaultOrControllerreturns (TransferInfo memory transferInfo)
{
yieldToken__.updateTotalUnderlyingAssets(totalUnderlyingAssets);
transferInfo.receiver = srcPostHookCallParams_.transferInfo.receiver;
transferInfo.extraData =abi.encode(
srcPostHookCallParams_.options,
srcPostHookCallParams_.transferInfo.extraData
);
transferInfo.amount =abi.decode(
srcPostHookCallParams_.postHookData,
(uint256)
);
}
/**
* @notice This function is called before the execution of a destination hook.
* @dev It checks if the sibling chain is supported, consumes a part of the limit, and prepares post-hook data.
*/functiondstPreHookCall(
DstPreHookCallParams calldata params_
)
publicoverridenotShutdownisVaultOrControllerreturns (bytesmemory postHookData, TransferInfo memory transferInfo)
{
(uint256 increasedUnderlying, bytesmemory payload) =abi.decode(
params_.transferInfo.extraData,
(uint256, bytes)
);
_poolDstHook(params_.connector, increasedUnderlying);
totalUnderlyingAssets += increasedUnderlying;
yieldToken__.updateTotalUnderlyingAssets(totalUnderlyingAssets);
yieldToken__.updateTotalUnderlyingAssets(totalUnderlyingAssets);
if (params_.transferInfo.amount ==0)
return (abi.encode(0, 0, 0, address(0)), transferInfo);
(uint256 consumedUnderlying, uint256 pendingUnderlying) = _limitDstHook(
params_.connector,
params_.transferInfo.amount
);
uint256 sharesToMint = yieldToken__.calculateMintAmount(
params_.transferInfo.amount
);
postHookData =abi.encode(
consumedUnderlying,
pendingUnderlying,
params_.transferInfo.amount,
params_.transferInfo.receiver
);
transferInfo = params_.transferInfo;
if (pendingUnderlying !=0) transferInfo.receiver =address(this);
transferInfo.amount = sharesToMint;
transferInfo.extraData = payload;
}
/**
* @notice Handles post-hook logic after the execution of a destination hook.
* @dev This function processes post-hook data to update the identifier cache and sibling chain cache.
*/functiondstPostHookCall(
DstPostHookCallParams calldata params_
)
publicoverrideisVaultOrControllernotShutdownreturns (CacheData memory cacheData)
{
(
uint256 consumedUnderlying,
uint256 pendingUnderlying,
uint256 depositUnderlying,
address receiver
) =abi.decode(
params_.postHookData,
(uint256, uint256, uint256, address)
);
bytesmemory execPayload = params_.transferInfo.extraData;
uint256 connectorPendingShares = _getConnectorPendingAmount(
params_.connectorCache
);
uint256 pendingShares;
if (pendingUnderlying >0) {
// totalShares * consumedU / totalUuint256 consumedShares = (params_.transferInfo.amount *
pendingUnderlying) / depositUnderlying;
pendingShares = params_.transferInfo.amount - consumedShares;
cacheData.identifierCache =abi.encode(
params_.transferInfo.receiver,
pendingShares,
params_.connector,
execPayload
);
yieldToken__.transfer(receiver, consumedUnderlying);
emit TokensPending(
params_.connector,
params_.transferInfo.receiver,
consumedShares,
pendingShares,
params_.messageId
);
} else {
if (execPayload.length>0) {
// executebool success = executionHelper__.execute(
params_.transferInfo.receiver,
execPayload,
params_.messageId,
depositUnderlying
);
if (success) {
emit MessageExecuted(
params_.messageId,
params_.transferInfo.receiver
);
cacheData.identifierCache =newbytes(0);
} else
cacheData.identifierCache =abi.encode(
params_.transferInfo.receiver,
0,
params_.connector,
execPayload
);
} else cacheData.identifierCache =newbytes(0);
}
cacheData.connectorCache =abi.encode(
connectorPendingShares + pendingShares
);
}
// /**// * @notice Handles pre-retry hook logic before execution.// * @dev This function can be used to mint funds which were in a pending state due to limits.// * @param siblingChainSlug_ The unique identifier of the sibling chain.// * @param identifierCache_ Identifier cache containing pending mint information.// * @param connectorCache_ Sibling chain cache containing pending amount information.// * @return updatedReceiver The updated receiver of the funds.// * @return consumedUnderlying The amount consumed from the limit.// * @return postHookData The post-hook data to be processed after the retry hook execution.// */functionpreRetryHook(
PreRetryHookCallParams calldata params_
)
publicoverrideisVaultOrControllernotShutdownreturns (bytesmemory postHookData, TransferInfo memory transferInfo)
{
(
address receiver,
uint256 totalPendingShares,
address connector,
) =abi.decode(
params_.cacheData.identifierCache,
(address, uint256, address, bytes)
);
if (connector != params_.connector) revert InvalidConnector();
(uint256 consumedShares, uint256 pendingShares) = _limitDstHook(
params_.connector,
totalPendingShares
);
postHookData =abi.encode(receiver, consumedShares, pendingShares);
uint256 consumedUnderlying = yieldToken__.convertToAssets(
consumedShares
);
yieldToken__.transfer(receiver, consumedUnderlying);
transferInfo = TransferInfo(transferInfo.receiver, 0, bytes(""));
}
// /**// * @notice Handles post-retry hook logic after execution.// * @dev This function updates the identifier cache and sibling chain cache based on the post-hook data.// * @param siblingChainSlug_ The unique identifier of the sibling chain.// * @param identifierCache_ Identifier cache containing pending mint information.// * @param connectorCache_ Sibling chain cache containing pending amount information.// * @param postHookData_ The post-hook data containing updated receiver and consumed/pending amounts.// * @return newIdentifierCache The updated identifier cache.// * @return newConnectorCache The updated sibling chain cache.// */functionpostRetryHook(
PostRetryHookCallParams calldata params_
) publicoverridereturns (CacheData memory cacheData) {
returnsuper.postRetryHook(params_);
}
functionupdateEmergencyShutdownState(bool shutdownState_
) externalonlyOwner{
emergencyShutdown = shutdownState_;
emit ShutdownStateUpdated(shutdownState_);
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"lib/solmate/src/tokens/ERC20.sol";
/**
* @title DummyERC20
* @notice An Dummy ERC20 contract which is used for testing Superbridge. Do not use in production
* @dev This contract suports minting and burning of tokens
*/contractDummyERC20isERC20{
/**
* @notice constructor for creating a new Token
* @param name_ token name
* @param symbol_ token symbol
* @param decimals_ token decimals
*/constructor(stringmemory name_,
stringmemory symbol_,
uint8 decimals_
) ERC20(name_, symbol_, decimals_) {}
functionburn(address user_, uint256 amount_) external{
_burn(user_, amount_);
}
functionmint(address receiver_, uint256 amount_) external{
_mint(receiver_, amount_);
}
}
Contract Source Code
File 10 of 44: ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.abstractcontractERC20{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventTransfer(addressindexedfrom, addressindexed to, uint256 amount);
eventApproval(addressindexed owner, addressindexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
uint8publicimmutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/uint256public totalSupply;
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/uint256internalimmutable INITIAL_CHAIN_ID;
bytes32internalimmutable INITIAL_DOMAIN_SEPARATOR;
mapping(address=>uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name,
stringmemory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID =block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 amount) publicvirtualreturns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
returntrue;
}
functiontransfer(address to, uint256 amount) publicvirtualreturns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
returntrue;
}
functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualreturns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.if (allowed !=type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
returntrue;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) publicvirtual{
require(deadline >=block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing// the owner's nonce which cannot realistically overflow.unchecked {
address recoveredAddress =ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress !=address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
functionDOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
functioncomputeDomainSeparator() internalviewvirtualreturns (bytes32) {
returnkeccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/function_mint(address to, uint256 amount) internalvirtual{
totalSupply += amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function_burn(addressfrom, uint256 amount) internalvirtual{
balanceOf[from] -= amount;
// Cannot underflow because a user's balance// will never be larger than the total supply.unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0pragmasolidity 0.8.13;libraryExcessivelySafeCall{
uintconstant LOW_28_MASK =0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/// @notice Use when you _really_ really _really_ don't trust the called/// contract. This prevents the called contract from causing reversion of/// the caller in as many ways as we can./// @dev The main difference between this and a solidity low-level call is/// that we limit the number of bytes that the callee can cause to be/// copied to caller memory. This prevents stupid things like malicious/// contracts returning 10,000,000 bytes causing a local OOG when copying/// to memory./// @param _target The address to call/// @param _gas The amount of gas to forward to the remote contract/// @param _maxCopy The maximum number of bytes of returndata to copy/// to memory./// @param _calldata The data to send to the remote contract/// @return success and returndata, as `.call()`. Returndata is capped to/// `_maxCopy` bytes.functionexcessivelySafeCall(address _target,
uint _gas,
uint16 _maxCopy,
bytesmemory _calldata
) internalreturns (bool, bytesmemory) {
// set up for assembly calluint _toCopy;
bool _success;
bytesmemory _returnData =newbytes(_maxCopy);
// dispatch message to recipient// by assembly calling "handle" function// we call via assembly to avoid memcopying a very large returndata// returned by a malicious contractassembly {
_success :=call(
_gas, // gas
_target, // recipient0, // ether valueadd(_calldata, 0x20), // inlocmload(_calldata), // inlen0, // outloc0// outlen
)
// limit our copy to 256 bytes
_toCopy :=returndatasize()
ifgt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytesmstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/// @notice Use when you _really_ really _really_ don't trust the called/// contract. This prevents the called contract from causing reversion of/// the caller in as many ways as we can./// @dev The main difference between this and a solidity low-level call is/// that we limit the number of bytes that the callee can cause to be/// copied to caller memory. This prevents stupid things like malicious/// contracts returning 10,000,000 bytes causing a local OOG when copying/// to memory./// @param _target The address to call/// @param _gas The amount of gas to forward to the remote contract/// @param _maxCopy The maximum number of bytes of returndata to copy/// to memory./// @param _calldata The data to send to the remote contract/// @return success and returndata, as `.call()`. Returndata is capped to/// `_maxCopy` bytes.functionexcessivelySafeStaticCall(address _target,
uint _gas,
uint16 _maxCopy,
bytesmemory _calldata
) internalviewreturns (bool, bytesmemory) {
// set up for assembly calluint _toCopy;
bool _success;
bytesmemory _returnData =newbytes(_maxCopy);
// dispatch message to recipient// by assembly calling "handle" function// we call via assembly to avoid memcopying a very large returndata// returned by a malicious contractassembly {
_success :=staticcall(
_gas, // gas
_target, // recipientadd(_calldata, 0x20), // inlocmload(_calldata), // inlen0, // outloc0// outlen
)
// limit our copy to 256 bytes
_toCopy :=returndatasize()
ifgt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytesmstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/**
* @notice Swaps function selectors in encoded contract calls
* @dev Allows reuse of encoded calldata for functions with identical
* argument types but different names. It simply swaps out the first 4 bytes
* for the new selector. This function modifies memory in place, and should
* only be used with caution.
* @param _newSelector The new 4-byte selector
* @param _buf The encoded contract args
*/functionswapSelector(bytes4 _newSelector,
bytesmemory _buf
) internalpure{
require(_buf.length>=4);
uint _mask = LOW_28_MASK;
assembly {
// load the first word oflet _word :=mload(add(_buf, 0x20))
// mask out the top 4 bytes// /x
_word :=and(_word, _mask)
_word :=or(_newSelector, _word)
mstore(add(_buf, 0x20), _word)
}
}
}
Contract Source Code
File 13 of 44: ExecutionHelper.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"../../libraries/ExcessivelySafeCall.sol";
import"../../utils/RescueBase.sol";
import"../../common/Errors.sol";
/**
* @title ExecutionHelper
* @notice It is an untrusted contract used for payload execution by Super token and Vault.
*/contractExecutionHelperisRescueBase{
usingExcessivelySafeCallforaddress;
uint16privateconstant MAX_COPY_BYTES =0;
addresspublic hook;
bytes32public messageId;
uint256public bridgeAmount;
constructor(address owner_) AccessControl(owner_) {
_grantRole(RESCUE_ROLE, owner_);
}
modifieronlyHook() {
require(msg.sender== hook, "ExecutionHelper: only hook");
_;
}
functionsetHook(address hook_) externalonlyOwner{
hook = hook_;
}
/**
* @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.
*/functionexecute(address target_,
bytesmemory payload_,
bytes32 messageId_,
uint256 bridgeAmount_
) externalonlyHookreturns (bool success) {
if (target_ ==address(this)) returnfalse;
messageId = messageId_;
bridgeAmount = bridgeAmount_;
(success, ) = target_.excessivelySafeCall(
gasleft(),
MAX_COPY_BYTES,
payload_
);
messageId =bytes32(0);
bridgeAmount =0;
}
}
Contract Source Code
File 14 of 44: Faucet.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"lib/solmate/src/tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer./// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.librarySafeTransferLib{
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Transfer the ETH and store if it succeeded or not.
success :=call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(
ERC20 token,
addressfrom,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(
freeMemoryPointer,
0x23b872dd00000000000000000000000000000000000000000000000000000000
)
mstore(
add(freeMemoryPointer, 4),
and(from, 0xffffffffffffffffffffffffffffffffffffffff)
) // Append and mask the "from" argument.mstore(
add(freeMemoryPointer, 36),
and(to, 0xffffffffffffffffffffffffffffffffffffffff)
) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(
and(eq(mload(0), 1), gt(returndatasize(), 31)),
iszero(returndatasize())
),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
functionsafeTransfer(ERC20 token, address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(
freeMemoryPointer,
0xa9059cbb00000000000000000000000000000000000000000000000000000000
)
mstore(
add(freeMemoryPointer, 4),
and(to, 0xffffffffffffffffffffffffffffffffffffffff)
) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(
and(eq(mload(0), 1), gt(returndatasize(), 31)),
iszero(returndatasize())
),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
functionsafeApprove(ERC20 token, address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(
freeMemoryPointer,
0x095ea7b300000000000000000000000000000000000000000000000000000000
)
mstore(
add(freeMemoryPointer, 4),
and(to, 0xffffffffffffffffffffffffffffffffffffffff)
) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(
and(eq(mload(0), 1), gt(returndatasize(), 31)),
iszero(returndatasize())
),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
errorZeroAddress();
/**
* @title RescueFundsLib
* @dev A library that provides a function to rescue funds from a contract.
*/libraryRescueFundsLib{
/**
* @dev The address used to identify ETH.
*/addresspublicconstant ETH_ADDRESS =address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
/**
* @dev thrown when the given token address don't have any code
*/errorInvalidTokenAddress();
/**
* @dev Rescues funds from a contract.
* @param token_ The address of the token contract.
* @param rescueTo_ The address of the user.
* @param amount_ The amount of tokens to be rescued.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) internal{
if (rescueTo_ ==address(0)) revert ZeroAddress();
if (token_ == ETH_ADDRESS) {
SafeTransferLib.safeTransferETH(rescueTo_, amount_);
} else {
if (token_.code.length==0) revert InvalidTokenAddress();
SafeTransferLib.safeTransfer(ERC20(token_), rescueTo_, amount_);
}
}
}
/**
* @title Ownable
* @dev The Ownable contract provides a simple way to manage ownership of a contract
* and allows for ownership to be transferred to a nominated address.
*/abstractcontractOwnable{
addressprivate _owner;
addressprivate _nominee;
eventOwnerNominated(addressindexed nominee);
eventOwnerClaimed(addressindexed claimer);
errorOnlyOwner();
errorOnlyNominee();
/**
* @dev Sets the contract's owner to the address that is passed to the constructor.
*/constructor(address owner_) {
_claimOwner(owner_);
}
/**
* @dev Modifier that restricts access to only the contract's owner.
* Throws an error if the caller is not the owner.
*/modifieronlyOwner() {
if (msg.sender!= _owner) revert OnlyOwner();
_;
}
/**
* @dev Returns the current owner of the contract.
*/functionowner() externalviewreturns (address) {
return _owner;
}
/**
* @dev Returns the current nominee for ownership of the contract.
*/functionnominee() externalviewreturns (address) {
return _nominee;
}
/**
* @dev Allows the current owner to nominate a new owner for the contract.
* Throws an error if the caller is not the owner.
* Emits an `OwnerNominated` event with the address of the nominee.
*/functionnominateOwner(address nominee_) external{
if (msg.sender!= _owner) revert OnlyOwner();
_nominee = nominee_;
emit OwnerNominated(_nominee);
}
/**
* @dev Allows the nominated owner to claim ownership of the contract.
* Throws an error if the caller is not the nominee.
* Sets the nominated owner as the new owner of the contract.
* Emits an `OwnerClaimed` event with the address of the new owner.
*/functionclaimOwner() external{
if (msg.sender!= _nominee) revert OnlyNominee();
_claimOwner(msg.sender);
}
/**
* @dev Internal function that sets the owner of the contract to the specified address
* and sets the nominee to address(0).
*/function_claimOwner(address claimer_) internal{
_owner = claimer_;
_nominee =address(0);
emit OwnerClaimed(claimer_);
}
}
/**
* @title AccessControl
* @dev This abstract contract implements access control mechanism based on roles.
* Each role can have one or more addresses associated with it, which are granted
* permission to execute functions with the onlyRole modifier.
*/abstractcontractAccessControlisOwnable{
/**
* @dev A mapping of roles to a mapping of addresses to boolean values indicating whether or not they have the role.
*/mapping(bytes32=>mapping(address=>bool)) private _permits;
/**
* @dev Emitted when a role is granted to an address.
*/eventRoleGranted(bytes32indexed role, addressindexed grantee);
/**
* @dev Emitted when a role is revoked from an address.
*/eventRoleRevoked(bytes32indexed role, addressindexed revokee);
/**
* @dev Error message thrown when an address does not have permission to execute a function with onlyRole modifier.
*/errorNoPermit(bytes32 role);
/**
* @dev Constructor that sets the owner of the contract.
*/constructor(address owner_) Ownable(owner_) {}
/**
* @dev Modifier that restricts access to addresses having roles
* Throws an error if the caller do not have permit
*/modifieronlyRole(bytes32 role) {
if (!_permits[role][msg.sender]) revert NoPermit(role);
_;
}
/**
* @dev Checks and reverts if an address do not have a specific role.
* @param role_ The role to check.
* @param address_ The address to check.
*/function_checkRole(bytes32 role_, address address_) internalvirtual{
if (!_hasRole(role_, address_)) revert NoPermit(role_);
}
/**
* @dev Grants a role to a given address.
* @param role_ The role to grant.
* @param grantee_ The address to grant the role to.
* Emits a RoleGranted event.
* Can only be called by the owner of the contract.
*/functiongrantRole(bytes32 role_,
address grantee_
) externalvirtualonlyOwner{
_grantRole(role_, grantee_);
}
/**
* @dev Revokes a role from a given address.
* @param role_ The role to revoke.
* @param revokee_ The address to revoke the role from.
* Emits a RoleRevoked event.
* Can only be called by the owner of the contract.
*/functionrevokeRole(bytes32 role_,
address revokee_
) externalvirtualonlyOwner{
_revokeRole(role_, revokee_);
}
/**
* @dev Internal function to grant a role to a given address.
* @param role_ The role to grant.
* @param grantee_ The address to grant the role to.
* Emits a RoleGranted event.
*/function_grantRole(bytes32 role_, address grantee_) internal{
_permits[role_][grantee_] =true;
emit RoleGranted(role_, grantee_);
}
/**
* @dev Internal function to revoke a role from a given address.
* @param role_ The role to revoke.
* @param revokee_ The address to revoke the role from.
* Emits a RoleRevoked event.
*/function_revokeRole(bytes32 role_, address revokee_) internal{
_permits[role_][revokee_] =false;
emit RoleRevoked(role_, revokee_);
}
/**
* @dev Checks whether an address has a specific role.
* @param role_ The role to check.
* @param address_ The address to check.
* @return A boolean value indicating whether or not the address has the role.
*/functionhasRole(bytes32 role_,
address address_
) externalviewreturns (bool) {
return _hasRole(role_, address_);
}
function_hasRole(bytes32 role_,
address address_
) internalviewreturns (bool) {
return _permits[role_][address_];
}
}
/**
* @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.
*/abstractcontractRescueBaseisAccessControl{
bytes32constant RESCUE_ROLE =keccak256("RESCUE_ROLE");
/**
* @notice Rescues funds from the contract if they are locked by mistake.
* @param token_ The address of the token contract.
* @param rescueTo_ The address where rescued tokens need to be sent.
* @param amount_ The amount of tokens to be rescued.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
contractFaucetisRescueBase{
usingSafeTransferLibforERC20;
constructor() AccessControl(msg.sender) {
_grantRole(RESCUE_ROLE, msg.sender);
}
functiongetTokens(address receiver_, address[] calldata tokens_) external{
for (uint256 i =0; i < tokens_.length; i++) {
ERC20 token = ERC20(tokens_[i]);
uint256 amount =10** token.decimals() *1000;
token.safeTransfer(receiver_, amount);
}
}
}
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;/// @notice Arithmetic library with operations for fixed-point numbers./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)libraryFixedPointMathLib{
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/uint256internalconstant MAX_UINT256 =2**256-1;
uint256internalconstant WAD =1e18; // The scalar of ETH and most ERC20s.functionmulWadDown(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
functionmulWadUp(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
functiondivWadDown(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
functiondivWadUp(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/functionmulDivDown(uint256 x,
uint256 y,
uint256 denominator
) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))ifiszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z :=div(mul(x, y), denominator)
}
}
functionmulDivUp(uint256 x,
uint256 y,
uint256 denominator
) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))ifiszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,// 1 is added to round up the division of x * y by the denominator.
z :=add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
functionrpow(uint256 x,
uint256 n,
uint256 scalar
) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
switch x
case0 {
switch n
case0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z :=0
}
}
default {
switchmod(n, 2)
case0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.let half :=shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n :=shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n :=shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.// Equivalent to iszero(eq(div(xx, x), x)) here.ifshr(128, x) {
revert(0, 0)
}
// Store x squared.let xx :=mul(x, x)
// Round to the nearest number.let xxRound :=add(xx, half)
// Revert if xx + half overflowed.iflt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x :=div(xxRound, scalar)
// If n is even:ifmod(n, 2) {
// Compute z * x.let zx :=mul(z, x)
// If z * x overflowed:ifiszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.ifiszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.let zxRound :=add(zx, half)
// Revert if zx + half overflowed.iflt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z :=div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/functionsqrt(uint256 x) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
let y := x // We start y at x, which will help us make our initial estimate.
z :=181// The "correct" value is 1, but this saves a multiplication later.// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.// We check y >= 2^(k + 8) but shift right by k bits// each branch to ensure that if x >= 256, then y >= 256.ifiszero(lt(y, 0x10000000000000000000000000000000000)) {
y :=shr(128, y)
z :=shl(64, z)
}
ifiszero(lt(y, 0x1000000000000000000)) {
y :=shr(64, y)
z :=shl(32, z)
}
ifiszero(lt(y, 0x10000000000)) {
y :=shr(32, y)
z :=shl(16, z)
}
ifiszero(lt(y, 0x1000000)) {
y :=shr(16, y)
z :=shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could// get y in a tighter range. Currently, we will have y in [256, 256*2^16).// We ensured y >= 256 so that the relative difference between y and y+1 is small.// That's not possible if x < 256 but we can just verify those cases exhaustively.// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.// There is no overflow risk here since y < 2^136 after the first branch above.
z :=shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z :=sub(z, lt(div(x, z), z))
}
}
functionunsafeMod(uint256 x, uint256 y) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Mod x by y. Note this will return// 0 instead of reverting if y is zero.
z :=mod(x, y)
}
}
functionunsafeDiv(uint256 x, uint256 y) internalpurereturns (uint256 r) {
/// @solidity memory-safe-assemblyassembly {
// Divide x by y. Note this will return// 0 instead of reverting if y is zero.
r :=div(x, y)
}
}
functionunsafeDivUp(uint256 x, uint256 y) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Add 1 to x * y if x % y > 0. Note this will// return 0 instead of reverting if y is zero.
z :=add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransfer(address to, uint256 amount) externalreturns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/functionallowance(address owner, address spender) externalviewreturns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/functionapprove(address spender, uint256 amount) externalreturns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransferFrom(addressfrom, address to, uint256 amount) externalreturns (bool);
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.3;import"../common/Structs.sol";
interfaceIHook{
/**
* @notice Executes pre-hook call for source underlyingAsset.
* @dev This function is used to execute a pre-hook call for the source underlyingAsset before initiating a transfer.
* @param params_ Parameters for the pre-hook call.
* @return transferInfo Information about the transfer.
* @return postHookData returned from the pre-hook call.
*/functionsrcPreHookCall(
SrcPreHookCallParams calldata params_
)
externalreturns (TransferInfo memory transferInfo, bytesmemory postHookData);
functionsrcPostHookCall(
SrcPostHookCallParams calldata params_
) externalreturns (TransferInfo memory transferInfo);
/**
* @notice Executes pre-hook call for destination underlyingAsset.
* @dev This function is used to execute a pre-hook call for the destination underlyingAsset before initiating a transfer.
* @param params_ Parameters for the pre-hook call.
*/functiondstPreHookCall(
DstPreHookCallParams calldata params_
)
externalreturns (bytesmemory postHookData, TransferInfo memory transferInfo);
/**
* @notice Executes post-hook call for destination underlyingAsset.
* @dev This function is used to execute a post-hook call for the destination underlyingAsset after completing a transfer.
* @param params_ Parameters for the post-hook call.
* @return cacheData Cached data for the post-hook call.
*/functiondstPostHookCall(
DstPostHookCallParams calldata params_
) externalreturns (CacheData memory cacheData);
/**
* @notice Executes a pre-retry hook for a failed transaction.
* @dev This function is used to execute a pre-retry hook for a failed transaction.
* @param params_ Parameters for the pre-retry hook.
* @return postHookData Data from the post-retry hook.
* @return transferInfo Information about the transfer.
*/functionpreRetryHook(
PreRetryHookCallParams calldata params_
)
externalreturns (bytesmemory postHookData, TransferInfo memory transferInfo);
/**
* @notice Executes a post-retry hook for a failed transaction.
* @dev This function is used to execute a post-retry hook for a failed transaction.
* @param params_ Parameters for the post-retry hook.
* @return cacheData Cached data for the post-retry hook.
*/functionpostRetryHook(
PostRetryHookCallParams calldata params_
) externalreturns (CacheData memory cacheData);
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;/**
* @title IPlug
* @notice Interface for a plug contract that executes the message received from a source chain.
*/interfaceIPlug{
/**
* @dev this should be only executable by socket
* @notice executes the message received from source chain
* @notice It is expected to have original sender checks in the destination plugs using payload
* @param srcChainSlug_ chain slug of source
* @param payload_ the data which is needed by plug at inbound call on remote
*/functioninbound(uint32 srcChainSlug_,
bytescalldata payload_
) externalpayable;
}
Contract Source Code
File 28 of 44: ISocket.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;/**
* @title ISocket
* @notice An interface for a cross-chain communication contract
* @dev This interface provides methods for transmitting and executing messages between chains,
* connecting a plug to a remote chain and setting up switchboards for the message transmission
* This interface also emits events for important operations such as message transmission, execution status,
* and plug connection
*/interfaceISocket{
/**
* @notice A struct containing fees required for message transmission and execution
* @param transmissionFees fees needed for transmission
* @param switchboardFees fees needed by switchboard
* @param executionFee fees needed for execution
*/structFees {
uint128 transmissionFees;
uint128 executionFee;
uint128 switchboardFees;
}
/**
* @title MessageDetails
* @dev This struct defines the details of a message to be executed in a Decapacitor contract.
*/structMessageDetails {
// A unique identifier for the message.bytes32 msgId;
// The fee to be paid for executing the message.uint256 executionFee;
// The maximum amount of gas that can be used to execute the message.uint256 minMsgGasLimit;
// The extra params which provides msg value and additional info needed for message execbytes32 executionParams;
// The payload data to be executed in the message.bytes payload;
}
/**
* @title ExecutionDetails
* @dev This struct defines the execution details
*/structExecutionDetails {
// packet idbytes32 packetId;
// proposal countuint256 proposalCount;
// gas limit needed to execute inbounduint256 executionGasLimit;
// proof data required by the Decapacitor contract to verify the message's authenticitybytes decapacitorProof;
// signature of executorbytes signature;
}
/**
* @notice emits the message details when a new message arrives at outbound
* @param localChainSlug local chain slug
* @param localPlug local plug address
* @param dstChainSlug remote chain slug
* @param dstPlug remote plug address
* @param msgId message id packed with remoteChainSlug and nonce
* @param minMsgGasLimit gas limit needed to execute the inbound at remote
* @param payload the data which will be used by inbound at remote
*/eventMessageOutbound(uint32 localChainSlug,
address localPlug,
uint32 dstChainSlug,
address dstPlug,
bytes32 msgId,
uint256 minMsgGasLimit,
bytes32 executionParams,
bytes32 transmissionParams,
bytes payload,
Fees fees
);
/**
* @notice emits the status of message after inbound call
* @param msgId msg id which is executed
*/eventExecutionSuccess(bytes32 msgId);
/**
* @notice emits the config set by a plug for a remoteChainSlug
* @param plug address of plug on current chain
* @param siblingChainSlug sibling chain slug
* @param siblingPlug address of plug on sibling chain
* @param inboundSwitchboard inbound switchboard (select from registered options)
* @param outboundSwitchboard outbound switchboard (select from registered options)
* @param capacitor capacitor selected based on outbound switchboard
* @param decapacitor decapacitor selected based on inbound switchboard
*/eventPlugConnected(address plug,
uint32 siblingChainSlug,
address siblingPlug,
address inboundSwitchboard,
address outboundSwitchboard,
address capacitor,
address decapacitor
);
/**
* @notice registers a message
* @dev Packs the message and includes it in a packet with capacitor
* @param remoteChainSlug_ the remote chain slug
* @param minMsgGasLimit_ the gas limit needed to execute the payload on remote
* @param payload_ the data which is needed by plug at inbound call on remote
*/functionoutbound(uint32 remoteChainSlug_,
uint256 minMsgGasLimit_,
bytes32 executionParams_,
bytes32 transmissionParams_,
bytesmemory payload_
) externalpayablereturns (bytes32 msgId);
/**
* @notice executes a message
* @param executionDetails_ the packet details, proof and signature needed for message execution
* @param messageDetails_ the message details
*/functionexecute(
ISocket.ExecutionDetails calldata executionDetails_,
ISocket.MessageDetails calldata messageDetails_
) externalpayable;
/**
* @notice sets the config specific to the plug
* @param siblingChainSlug_ the sibling chain slug
* @param siblingPlug_ address of plug present at sibling chain to call inbound
* @param inboundSwitchboard_ the address of switchboard to use for receiving messages
* @param outboundSwitchboard_ the address of switchboard to use for sending messages
*/functionconnect(uint32 siblingChainSlug_,
address siblingPlug_,
address inboundSwitchboard_,
address outboundSwitchboard_
) external;
/**
* @notice Retrieves the minimum fees required for a message with a specified gas limit and destination chain.
* @param minMsgGasLimit_ The gas limit of the message.
* @param remoteChainSlug_ The slug of the destination chain for the message.
* @param plug_ The address of the plug through which the message is sent.
* @return totalFees The minimum fees required for the specified message.
*/functiongetMinFees(uint256 minMsgGasLimit_,
uint256 payloadSize_,
bytes32 executionParams_,
bytes32 transmissionParams_,
uint32 remoteChainSlug_,
address plug_
) externalviewreturns (uint256 totalFees);
/**
* @notice returns chain slug
* @return chainSlug current chain slug
*/functionchainSlug() externalviewreturns (uint32 chainSlug);
functionglobalMessageCount() externalviewreturns (uint64);
/**
* @notice returns the config for given `plugAddress_` and `siblingChainSlug_`
* @param siblingChainSlug_ the sibling chain slug
* @param plugAddress_ address of plug present at current chain
*/functiongetPlugConfig(address plugAddress_,
uint32 siblingChainSlug_
)
externalviewreturns (address siblingPlug,
address inboundSwitchboard__,
address outboundSwitchboard__,
address capacitor__,
address decapacitor__
);
}
Contract Source Code
File 29 of 44: IStrategy.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;/**
* @title IStrategy
* @notice Interface for strategy contract which interacts with other protocols
*/interfaceIStrategy{
functionwithdraw(uint256 amount_) externalreturns (uint256 loss_);
functionwithdrawAll() external;
functionestimatedTotalAssets()
externalviewreturns (uint256 totalUnderlyingAssets_);
functioninvest() external;
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"../HookBase.sol";
import {Gauge} from"../../utils/Gauge.sol";
abstractcontractLimitPluginisGauge, HookBase{
bytes32constant LIMIT_UPDATER_ROLE =keccak256("LIMIT_UPDATER_ROLE");
// connector => receivingLimitParamsmapping(address=> LimitParams) _receivingLimitParams;
// connector => sendingLimitParamsmapping(address=> LimitParams) _sendingLimitParams;
////////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////////////// Emitted when limit parameters are updatedeventLimitParamsUpdated(UpdateLimitParams[] updates);
// Emitted when pending tokens are minted to the receivereventPendingTokensBridged(address connector,
address receiver,
uint256 consumedAmount,
uint256 pendingAmount,
bytes32 messageId
);
// Emitted when the transfer reaches the limit, and the token mint is added to the pending queueeventTokensPending(address connector,
address receiver,
uint256 consumedAmount,
uint256 pendingAmount,
bytes32 messageId
);
/**
* @notice This function is used to set bridge limits.
* @dev It can only be updated by the owner.
* @param updates An array of structs containing update parameters.
*/functionupdateLimitParams(
UpdateLimitParams[] calldata updates
) externalonlyRole(LIMIT_UPDATER_ROLE) {
for (uint256 i =0; i < updates.length; i++) {
if (updates[i].isMint) {
_consumePartLimit(
0,
_receivingLimitParams[updates[i].connector]
); // To keep the current limit in sync
_receivingLimitParams[updates[i].connector].maxLimit = updates[
i
].maxLimit;
_receivingLimitParams[updates[i].connector]
.ratePerSecond = updates[i].ratePerSecond;
} else {
_consumePartLimit(0, _sendingLimitParams[updates[i].connector]); // To keep the current limit in sync
_sendingLimitParams[updates[i].connector].maxLimit = updates[i]
.maxLimit;
_sendingLimitParams[updates[i].connector]
.ratePerSecond = updates[i].ratePerSecond;
}
}
emit LimitParamsUpdated(updates);
}
functiongetCurrentReceivingLimit(address connector_
) externalviewreturns (uint256) {
return _getCurrentLimit(_receivingLimitParams[connector_]);
}
functiongetCurrentSendingLimit(address connector_
) externalviewreturns (uint256) {
return _getCurrentLimit(_sendingLimitParams[connector_]);
}
functiongetReceivingLimitParams(address connector_
) externalviewreturns (LimitParams memory) {
return _receivingLimitParams[connector_];
}
functiongetSendingLimitParams(address connector_
) externalviewreturns (LimitParams memory) {
return _sendingLimitParams[connector_];
}
function_limitSrcHook(address connector_, uint256 amount_) internal{
if (_sendingLimitParams[connector_].maxLimit ==0)
revert SiblingNotSupported();
_consumeFullLimit(amount_, _sendingLimitParams[connector_]); // Reverts on limit hit
}
function_limitDstHook(address connector_,
uint256 amount_
) internalreturns (uint256 consumedAmount, uint256 pendingAmount) {
if (_receivingLimitParams[connector_].maxLimit ==0)
revert SiblingNotSupported();
(consumedAmount, pendingAmount) = _consumePartLimit(
amount_,
_receivingLimitParams[connector_]
);
}
function_getConnectorPendingAmount(bytesmemory connectorCache_
) internalpurereturns (uint256) {
if (connectorCache_.length>0) {
returnabi.decode(connectorCache_, (uint256));
} elsereturn0;
}
}
Contract Source Code
File 33 of 44: Math.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)pragmasolidity ^0.8.0;/**
* @dev Standard math utilities missing in the Solidity language.
*/libraryMath{
enumRounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/functionmax(uint256 a, uint256 b) internalpurereturns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/functionmin(uint256 a, uint256 b) internalpurereturns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/functionaverage(uint256 a, uint256 b) internalpurereturns (uint256) {
// (a + b) / 2 can overflow.return (a & b) + (a ^ b) /2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/functionceilDiv(uint256 a, uint256 b) internalpurereturns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.return a ==0 ? 0 : (a -1) / b +1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/functionmulDiv(uint256 x, uint256 y, uint256 denominator) internalpurereturns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256// variables such that product = prod1 * 2^256 + prod0.uint256 prod0; // Least significant 256 bits of the productuint256 prod1; // Most significant 256 bits of the productassembly {
let mm :=mulmod(x, y, not(0))
prod0 :=mul(x, y)
prod1 :=sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.if (prod1 ==0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.// The surrounding unchecked block does not change this fact.// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////// 512 by 256 division.///////////////////////////////////////////////// Make division exact by subtracting the remainder from [prod1 prod0].uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder :=mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 :=sub(prod1, gt(remainder, prod0))
prod0 :=sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.// See https://cs.stackexchange.com/q/138556/92363.// Does not overflow because the denominator cannot be zero at this stage in the function.uint256 twos = denominator & (~denominator +1);
assembly {
// Divide denominator by twos.
denominator :=div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 :=div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos :=add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for// four bits. That is, denominator * inv = 1 mod 2^4.uint256 inverse = (3* denominator) ^2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works// in modular arithmetic, doubling the correct bits in each step.
inverse *=2- denominator * inverse; // inverse mod 2^8
inverse *=2- denominator * inverse; // inverse mod 2^16
inverse *=2- denominator * inverse; // inverse mod 2^32
inverse *=2- denominator * inverse; // inverse mod 2^64
inverse *=2- denominator * inverse; // inverse mod 2^128
inverse *=2- denominator * inverse; // inverse mod 2^256// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/functionmulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internalpurereturns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up &&mulmod(x, y, denominator) >0) {
result +=1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/functionsqrt(uint256 a) internalpurereturns (uint256) {
if (a ==0) {
return0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.//// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.//// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`//// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.uint256 result =1<< (log2(a) >>1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision// into the expected uint128 result.unchecked {
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/functionsqrt(uint256 a, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/functionlog2(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >>128>0) {
value >>=128;
result +=128;
}
if (value >>64>0) {
value >>=64;
result +=64;
}
if (value >>32>0) {
value >>=32;
result +=32;
}
if (value >>16>0) {
value >>=16;
result +=16;
}
if (value >>8>0) {
value >>=8;
result +=8;
}
if (value >>4>0) {
value >>=4;
result +=4;
}
if (value >>2>0) {
value >>=2;
result +=2;
}
if (value >>1>0) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog2(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result =log2(value);
return result + (rounding == Rounding.Up &&1<< result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/functionlog10(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >=10**64) {
value /=10**64;
result +=64;
}
if (value >=10**32) {
value /=10**32;
result +=32;
}
if (value >=10**16) {
value /=10**16;
result +=16;
}
if (value >=10**8) {
value /=10**8;
result +=8;
}
if (value >=10**4) {
value /=10**4;
result +=4;
}
if (value >=10**2) {
value /=10**2;
result +=2;
}
if (value >=10**1) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog10(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up &&10** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/functionlog256(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >>128>0) {
value >>=128;
result +=16;
}
if (value >>64>0) {
value >>=64;
result +=8;
}
if (value >>32>0) {
value >>=32;
result +=4;
}
if (value >>16>0) {
value >>=16;
result +=2;
}
if (value >>8>0) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog256(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up &&1<< (result <<3) < value ? 1 : 0);
}
}
}
Contract Source Code
File 34 of 44: Ownable.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;/**
* @title Ownable
* @dev The Ownable contract provides a simple way to manage ownership of a contract
* and allows for ownership to be transferred to a nominated address.
*/abstractcontractOwnable{
addressprivate _owner;
addressprivate _nominee;
eventOwnerNominated(addressindexed nominee);
eventOwnerClaimed(addressindexed claimer);
errorOnlyOwner();
errorOnlyNominee();
/**
* @dev Sets the contract's owner to the address that is passed to the constructor.
*/constructor(address owner_) {
_claimOwner(owner_);
}
/**
* @dev Modifier that restricts access to only the contract's owner.
* Throws an error if the caller is not the owner.
*/modifieronlyOwner() {
if (msg.sender!= _owner) revert OnlyOwner();
_;
}
/**
* @dev Returns the current owner of the contract.
*/functionowner() externalviewreturns (address) {
return _owner;
}
/**
* @dev Returns the current nominee for ownership of the contract.
*/functionnominee() externalviewreturns (address) {
return _nominee;
}
/**
* @dev Allows the current owner to nominate a new owner for the contract.
* Throws an error if the caller is not the owner.
* Emits an `OwnerNominated` event with the address of the nominee.
*/functionnominateOwner(address nominee_) external{
if (msg.sender!= _owner) revert OnlyOwner();
_nominee = nominee_;
emit OwnerNominated(_nominee);
}
/**
* @dev Allows the nominated owner to claim ownership of the contract.
* Throws an error if the caller is not the nominee.
* Sets the nominated owner as the new owner of the contract.
* Emits an `OwnerClaimed` event with the address of the new owner.
*/functionclaimOwner() external{
if (msg.sender!= _nominee) revert OnlyNominee();
_claimOwner(msg.sender);
}
/**
* @dev Internal function that sets the owner of the contract to the specified address
* and sets the nominee to address(0).
*/function_claimOwner(address claimer_) internal{
_owner = claimer_;
_nominee =address(0);
emit OwnerClaimed(claimer_);
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import {RescueFundsLib} from"../libraries/RescueFundsLib.sol";
import {AccessControl} from"./AccessControl.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.
*/abstractcontractRescueBaseisAccessControl{
bytes32constant RESCUE_ROLE =keccak256("RESCUE_ROLE");
/**
* @notice Rescues funds from the contract if they are locked by mistake.
* @param token_ The address of the token contract.
* @param rescueTo_ The address where rescued tokens need to be sent.
* @param amount_ The amount of tokens to be rescued.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) externalonlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
Contract Source Code
File 37 of 44: RescueFundsLib.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;import"lib/solmate/src/utils/SafeTransferLib.sol";
import"lib/solmate/src/tokens/ERC20.sol";
import {ZeroAddress} from"../common/Errors.sol";
import {ETH_ADDRESS} from"../common/Constants.sol";
/**
* @title RescueFundsLib
* @dev A library that provides a function to rescue funds from a contract.
*/libraryRescueFundsLib{
/**
* @dev thrown when the given token address don't have any code
*/errorInvalidTokenAddress();
/**
* @dev Rescues funds from a contract.
* @param token_ The address of the token contract.
* @param rescueTo_ The address of the user.
* @param amount_ The amount of tokens to be rescued.
*/functionrescueFunds(address token_,
address rescueTo_,
uint256 amount_
) internal{
if (rescueTo_ ==address(0)) revert ZeroAddress();
if (token_ == ETH_ADDRESS) {
SafeTransferLib.safeTransferETH(rescueTo_, amount_);
} else {
if (token_.code.length==0) revert InvalidTokenAddress();
SafeTransferLib.safeTransfer(ERC20(token_), rescueTo_, amount_);
}
}
}
Contract Source Code
File 38 of 44: SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;import {ERC20} from"../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer./// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.librarySafeTransferLib{
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Transfer the ETH and store if it succeeded or not.
success :=call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(
ERC20 token,
addressfrom,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
functionsafeTransfer(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
functionsafeApprove(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"lib/solmate/src/tokens/ERC20.sol";
import"../utils/RescueBase.sol";
import"../interfaces/IHook.sol";
/**
* @title SuperToken
* @notice An ERC20 contract which enables bridging a token to its sibling chains.
* @dev This contract implements ISuperTokenOrVault to support message bridging through IMessageBridge compliant contracts.
*/contractSuperTokenisERC20, RescueBase{
// for all controller access (mint, burn)bytes32constant CONTROLLER_ROLE =keccak256("CONTROLLER_ROLE");
/**
* @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
*/constructor(stringmemory name_,
stringmemory symbol_,
uint8 decimals_,
address initialSupplyHolder_,
address owner_,
uint256 initialSupply_
) ERC20(name_, symbol_, decimals_) AccessControl(owner_) {
_mint(initialSupplyHolder_, initialSupply_);
_grantRole(RESCUE_ROLE, owner_);
}
functionburn(address user_,
uint256 amount_
) externalonlyRole(CONTROLLER_ROLE) {
_burn(user_, amount_);
}
functionmint(address receiver_,
uint256 amount_
) externalonlyRole(CONTROLLER_ROLE) {
_mint(receiver_, amount_);
}
}
Contract Source Code
File 41 of 44: Vault.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"./Base.sol";
import"../interfaces/IConnector.sol";
import"lib/solmate/src/tokens/ERC20.sol";
/**
* @title SuperToken
* @notice A contract which enables bridging a token to its sibling chains.
* @dev This contract implements ISuperTokenOrVault to support message bridging through IMessageBridge compliant contracts.
*/contractVaultisBase{
usingSafeTransferLibforERC20;
// /**// * @notice constructor for creating a new SuperTokenVault.// * @param token_ token contract address which is to be bridged.// */constructor(address token_) Base(token_) {
bridgeType = token_ == ETH_ADDRESS ? NATIVE_VAULT : ERC20_VAULT;
}
/**
* @notice Bridges tokens between chains.
* @dev This function allows bridging tokens between different chains.
* @param receiver_ The address to receive the bridged tokens.
* @param amount_ The amount of tokens to bridge.
* @param msgGasLimit_ The gas limit for the execution of the bridging process.
* @param connector_ The address of the connector contract responsible for the bridge.
* @param extraData_ The extra data passed to hook functions.
* @param options_ Additional options for the bridging process.
*/functionbridge(address receiver_,
uint256 amount_,
uint256 msgGasLimit_,
address connector_,
bytescalldata extraData_,
bytescalldata options_
) externalpayablenonReentrant{
(
TransferInfo memory transferInfo,
bytesmemory postHookData
) = _beforeBridge(
connector_,
TransferInfo(receiver_, amount_, extraData_)
);
_receiveTokens(transferInfo.amount);
_afterBridge(
msgGasLimit_,
connector_,
options_,
postHookData,
transferInfo
);
}
/**
* @notice Receives inbound tokens from another chain.
* @dev This function is used to receive tokens from another chain.
* @param siblingChainSlug_ The identifier of the sibling chain.
* @param payload_ The payload containing the inbound tokens.
*/functionreceiveInbound(uint32 siblingChainSlug_,
bytesmemory payload_
) externalpayableoverridenonReentrant{
(
address receiver,
uint256 unlockAmount,
bytes32 messageId,
bytesmemory extraData
) =abi.decode(payload_, (address, uint256, bytes32, bytes));
TransferInfo memory transferInfo = TransferInfo(
receiver,
unlockAmount,
extraData
);
bytesmemory postHookData;
(postHookData, transferInfo) = _beforeMint(
siblingChainSlug_,
transferInfo
);
_transferTokens(transferInfo.receiver, transferInfo.amount);
_afterMint(unlockAmount, messageId, postHookData, transferInfo);
}
/**
* @notice Retry a failed transaction.
* @dev This function allows retrying a failed transaction sent through a connector.
* @param connector_ The address of the connector contract responsible for the failed transaction.
* @param messageId_ The unique identifier of the failed transaction.
*/functionretry(address connector_,
bytes32 messageId_
) externalnonReentrant{
(
bytesmemory postHookData,
TransferInfo memory transferInfo
) = _beforeRetry(connector_, messageId_);
_transferTokens(transferInfo.receiver, transferInfo.amount);
_afterRetry(connector_, messageId_, postHookData);
}
function_transferTokens(address receiver_, uint256 amount_) internal{
if (amount_ ==0) return;
if (address(token) == ETH_ADDRESS) {
SafeTransferLib.safeTransferETH(receiver_, amount_);
} else {
ERC20(token).safeTransfer(receiver_, amount_);
}
}
function_receiveTokens(uint256 amount_) internal{
if (amount_ ==0||address(token) == ETH_ADDRESS) return;
ERC20(token).safeTransferFrom(msg.sender, address(this), amount_);
}
}
Contract Source Code
File 42 of 44: Vault_YieldLimitExecHook.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
import {FixedPointMathLib} from"lib/solmate/src/utils/FixedPointMathLib.sol";
import {IStrategy} from"../interfaces/IStrategy.sol";
import"lib/solmate/src/tokens/ERC20.sol";
import"lib/solmate/src/utils/SafeTransferLib.sol";
import {IConnector} from"../ConnectorPlug.sol";
import"./LimitExecutionHook.sol";
contractVault_YieldLimitExecHookisLimitExecutionHook{
usingSafeTransferLibforERC20;
usingFixedPointMathLibforuint256;
uint256privateconstant MAX_BPS =10_000;
IStrategy public strategy; // address of the strategy contract
ERC20 publicimmutable underlyingAsset__;
uint256public totalLockedInStrategy; // total funds deposited in strategyuint256public totalIdle; // Amount of tokens that are in the vaultuint256public totalDebt; // Amount of tokens that strategy have borroweduint128public lastRebalanceTimestamp; // Timestamp of last rebalanceuint128public rebalanceDelay; // Delay between rebalanceuint256public debtRatio; // Debt ratio for the Vault (in BPS, <= 10k)boolpublic emergencyShutdown; // if true, no funds can be invested in the strategyuint256public lastTotalUnderlyingAssetsSynced;
eventWithdrawFromStrategy(uint256 withdrawn);
eventRebalanced(uint256 totalIdle,
uint256 totalDebt,
uint256 credit,
uint256 debtOutstanding
);
eventShutdownStateUpdated(bool shutdownState);
eventDebtRatioUpdated(uint256 debtRatio);
eventStrategyUpdated(address strategy);
eventRebalanceDelayUpdated(uint128 rebalanceDelay);
modifiernotShutdown() {
if (emergencyShutdown) revert VaultShutdown();
_;
}
constructor(uint256 debtRatio_,
uint128 rebalanceDelay_,
address strategy_,
address underlyingAsset_,
address vault_,
address executionHelper_,
bool useControllerPools_
)
LimitExecutionHook(msg.sender,
vault_,
executionHelper_,
useControllerPools_
)
{
underlyingAsset__ = ERC20(underlyingAsset_);
debtRatio = debtRatio_;
rebalanceDelay = rebalanceDelay_;
strategy = IStrategy(strategy_);
hookType = LIMIT_EXECUTION_YIELD_HOOK;
_grantRole(LIMIT_UPDATER_ROLE, msg.sender);
}
/**
* @dev This function calls the srcHookCall function of the connector contract,
* passing in the receiver, amount, siblingChainSlug, extradata, and msg.sender, and returns
* the updated receiver, amount, and extradata.
*/functionsrcPreHookCall(
SrcPreHookCallParams calldata params_
) publicoverridenotShutdownreturns (TransferInfo memory, bytesmemory) {
totalIdle += params_.transferInfo.amount;
returnsuper.srcPreHookCall(params_);
}
functionsrcPostHookCall(
SrcPostHookCallParams memory srcPostHookCallParams_
)
publicoverrideisVaultOrControllerreturns (TransferInfo memory transferInfo)
{
_checkDelayAndRebalance();
uint256 totalUnderlyingAsset = strategy.estimatedTotalAssets() +
totalIdle;
uint256 totalYieldSync = totalUnderlyingAsset -
lastTotalUnderlyingAssetsSynced;
lastTotalUnderlyingAssetsSynced = totalUnderlyingAsset;
transferInfo = srcPostHookCallParams_.transferInfo;
if (srcPostHookCallParams_.transferInfo.amount ==0) {
transferInfo.extraData =abi.encode(totalYieldSync, bytes(""));
} else {
transferInfo.extraData =abi.encode(
totalYieldSync,
srcPostHookCallParams_.transferInfo.extraData
);
}
}
/**
* @notice This function is called before the execution of a destination hook.
* @dev It checks if the sibling chain is supported, consumes a part of the limit, and prepares post-hook data.
*/functiondstPreHookCall(
DstPreHookCallParams calldata params_
)
publicoverridenotShutdownreturns (bytesmemory postHookData, TransferInfo memory transferInfo)
{
(postHookData, transferInfo) =super.dstPreHookCall(params_);
// ensure vault have enough idle underlyingAssetsif (transferInfo.amount > totalUnderlyingAssets())
revert NotEnoughAssets();
(bytesmemory options_, bytesmemory payload_) =abi.decode(
params_.transferInfo.extraData,
(bytes, bytes)
);
bool pullFromStrategy =abi.decode(options_, (bool));
if (transferInfo.amount > totalIdle) {
if (pullFromStrategy) {
_withdrawFromStrategy(transferInfo.amount - totalIdle);
} else {
(
uint256 consumedUnderlying,
uint256 pendingUnderlying,
uint256 bridgeUnderlying
) =abi.decode(postHookData, (uint256, uint256, uint256));
pendingUnderlying += transferInfo.amount - totalIdle;
postHookData =abi.encode(
transferInfo.amount,
pendingUnderlying,
bridgeUnderlying
);
transferInfo.amount = totalIdle;
// Update the lastUpdateLimit as consumedAmount is reduced to totalIdle. This is to ensure that the// receiving limit is updated by correct transferred amount.
LimitParams storage receivingParams = _receivingLimitParams[
params_.connector
];
receivingParams.lastUpdateLimit +=
consumedUnderlying -
transferInfo.amount;
}
totalIdle =0;
} else totalIdle -= transferInfo.amount;
transferInfo.extraData = payload_;
transferInfo.receiver = params_.transferInfo.receiver;
}
functiondstPostHookCall(
DstPostHookCallParams calldata params_
) publicoverridereturns (CacheData memory cacheData) {
returnsuper.dstPostHookCall(params_);
}
/**
* @notice Handles pre-retry hook logic before execution.
* @dev This function can be used to mint funds which were in a pending state due to limits.
*/functionpreRetryHook(
PreRetryHookCallParams calldata params_
)
publicoverridenotShutdownreturns (bytesmemory postHookData, TransferInfo memory transferInfo)
{
(postHookData, transferInfo) =super.preRetryHook(params_);
if (transferInfo.amount > totalIdle) {
_withdrawFromStrategy(transferInfo.amount - totalIdle);
totalIdle =0;
} else totalIdle -= transferInfo.amount;
}
functionpostRetryHook(
PostRetryHookCallParams calldata params_
) publicoverridereturns (CacheData memory cacheData) {
returnsuper.postRetryHook(params_);
}
functionwithdrawFromStrategy(uint256 underlyingAsset_
) externalonlyOwnerreturns (uint256) {
return _withdrawFromStrategy(underlyingAsset_);
}
function_withdrawFromStrategy(uint256 underlyingAsset_
) internalreturns (uint256 withdrawn) {
uint256 preBalance = underlyingAsset__.balanceOf(address(this));
strategy.withdraw(underlyingAsset_);
withdrawn = underlyingAsset__.balanceOf(address(this)) - preBalance;
totalIdle += withdrawn;
totalDebt -= withdrawn;
underlyingAsset__.transfer(vaultOrController, withdrawn);
emit WithdrawFromStrategy(withdrawn);
}
function_withdrawAllFromStrategy() internalreturns (uint256) {
uint256 preBalance = underlyingAsset__.balanceOf(address(this));
strategy.withdrawAll();
uint256 withdrawn = underlyingAsset__.balanceOf(address(this)) -
preBalance;
totalIdle += withdrawn;
totalDebt =0;
underlyingAsset__.transfer(vaultOrController, withdrawn);
emit WithdrawFromStrategy(withdrawn);
return withdrawn;
}
functionrebalance() externalnotShutdown{
_rebalance();
}
function_checkDelayAndRebalance() internal{
uint128 timeElapsed =uint128(block.timestamp) - lastRebalanceTimestamp;
if (timeElapsed >= rebalanceDelay) {
_rebalance();
}
}
function_rebalance() internal{
if (address(strategy) ==address(0)) return;
lastRebalanceTimestamp =uint128(block.timestamp);
// Compute the line of credit the Vault is able to offer the Strategy (if any)uint256 credit = _creditAvailable();
uint256 pendingDebt = _debtOutstanding();
if (credit >0) {
// Credit surplus, give to Strategy
totalIdle -= credit;
totalDebt += credit;
totalLockedInStrategy += credit;
underlyingAsset__.safeTransferFrom(
vaultOrController,
address(strategy),
credit
);
strategy.invest();
} elseif (pendingDebt >0) {
// Credit deficit, take from Strategy
_withdrawFromStrategy(pendingDebt);
}
emit Rebalanced(totalIdle, totalDebt, credit, pendingDebt);
}
/// @notice Returns the total quantity of all underlyingAssets under control of this/// Vault, whether they're loaned out to a Strategy, or currently held in/// the Vault./// @return total quantity of all underlyingAssets under control of this VaultfunctiontotalUnderlyingAssets() publicviewreturns (uint256) {
return strategy.estimatedTotalAssets() + totalIdle;
}
function_creditAvailable() internalviewreturns (uint256) {
uint256 vaultTotalAssets = totalUnderlyingAssets();
uint256 vaultDebtLimit = (debtRatio * vaultTotalAssets) / MAX_BPS;
uint256 vaultTotalDebt = totalDebt;
if (vaultDebtLimit <= vaultTotalDebt) return0;
// Start with debt limit left for the Strategyuint256 availableCredit = vaultDebtLimit - vaultTotalDebt;
// Can only borrow up to what the contract has in reserve// NOTE: Running near 100% is discouragedreturn Math.min(availableCredit, totalIdle);
}
functioncreditAvailable() externalviewreturns (uint256) {
// @notice// Amount of tokens in Vault a Strategy has access to as a credit line.// This will check the Strategy's debt limit, as well as the tokens// available in the Vault, and determine the maximum amount of tokens// (if any) the Strategy may draw on.// In the rare case the Vault is in emergency shutdown this will return 0.// @param strategy The Strategy to check. Defaults to caller.// @return The quantity of tokens available for the Strategy to draw on.return _creditAvailable();
}
function_debtOutstanding() internalviewreturns (uint256) {
// See note on `debtOutstanding()`.if (debtRatio ==0) {
return totalDebt;
}
uint256 debtLimit = ((debtRatio * totalUnderlyingAssets()) / MAX_BPS);
if (totalDebt <= debtLimit) return0;
elsereturn totalDebt - debtLimit;
}
functiondebtOutstanding() externalviewreturns (uint256) {
// @notice// Determines if `strategy` is past its debt limit and if any tokens// should be withdrawn to the Vault.// @return The quantity of tokens to withdraw.return _debtOutstanding();
}
functionupdateEmergencyShutdownState(bool shutdownState_,
bool detachStrategy
) externalonlyOwner{
if (shutdownState_ && detachStrategy) {
// If we're exiting emergency shutdown, we need to empty strategy
_withdrawAllFromStrategy();
strategy = IStrategy(address(0));
}
emergencyShutdown = shutdownState_;
emit ShutdownStateUpdated(shutdownState_);
}
////////////////////////////////////////////////////////////////////////////// SETTERS //////////////////////////////////////////////////////////////////////////////////functionsetDebtRatio(uint256 debtRatio_) externalonlyOwner{
if (debtRatio_ > MAX_BPS) revert DebtRatioTooHigh();
debtRatio = debtRatio_;
emit DebtRatioUpdated(debtRatio_);
}
functionsetStrategy(address strategy_) externalonlyOwner{
strategy = IStrategy(strategy_);
emit StrategyUpdated(strategy_);
}
functionsetRebalanceDelay(uint128 rebalanceDelay_) externalonlyOwner{
rebalanceDelay = rebalanceDelay_;
emit RebalanceDelayUpdated(rebalanceDelay_);
}
}
Contract Source Code
File 43 of 44: YieldToken.sol
// SPDX-License-Identifier: GPL-3.0-onlypragmasolidity 0.8.13;import"./YieldTokenBase.sol";
import {IStrategy} from"../../interfaces/IStrategy.sol";
import {IConnector} from"../../interfaces/IConnector.sol";
import {IHook} from"../../interfaces/IHook.sol";
// add shutdowncontractYieldTokenisYieldTokenBase{
usingFixedPointMathLibforuint256;
bytes32constant MINTER_ROLE =keccak256("MINTER_ROLE");
bytes32constant HOOK_ROLE =keccak256("HOOK_ROLE");
constructor(stringmemory name_,
stringmemory symbol_,
uint8 decimals_
) YieldTokenBase(name_, symbol_, decimals_) AccessControl(msg.sender) {
_grantRole(RESCUE_ROLE, msg.sender);
}
// move to hook// fix to round up and check other casesfunctioncalculateMintAmount(uint256 underlyingAssets_
) externalviewreturns (uint256) {
// total supply -> total shares// total yield -> total underlying from all chains// yield sent from src chain includes new amount hence subtracted hereuint256 supply = _totalSupply; // Saves an extra SLOAD if _totalSupply is non-zero.return
supply ==0
? underlyingAssets_
: underlyingAssets_.mulDivDown(
supply,
totalUnderlyingAssets - underlyingAssets_
);
}
functionburn(address user_,
uint256 shares_
) externalnonReentrantonlyRole(MINTER_ROLE) {
_burn(user_, shares_);
}
// minter rolefunctionmint(address receiver_,
uint256 amount_
) externalnonReentrantonlyRole(MINTER_ROLE) {
_mint(receiver_, amount_);
}
// hook rolefunctionupdateTotalUnderlyingAssets(uint256 amount_
) externalonlyRole(HOOK_ROLE) {
_updateTotalUnderlyingAssets(amount_);
}
function_updateTotalUnderlyingAssets(uint256 amount_) internal{
totalUnderlyingAssets = amount_;
}
}
Contract Source Code
File 44 of 44: YieldTokenBase.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;import"lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";
import {FixedPointMathLib} from"lib/solmate/src/utils/FixedPointMathLib.sol";
import"../../utils/RescueBase.sol";
import"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {PermitDeadlineExpired, InvalidSigner} from"../../common/Errors.sol";
abstractcontractYieldTokenBaseisRescueBase, ReentrancyGuard, IERC20{
usingFixedPointMathLibforuint256;
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
uint8publicimmutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/uint256internal _totalSupply;
mapping(address=>uint256) internal _balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/uint256internalimmutable INITIAL_CHAIN_ID;
bytes32internalimmutable INITIAL_DOMAIN_SEPARATOR;
mapping(address=>uint256) public nonces;
/*//////////////////////////////////////////////////////////////
YIELD STORAGE
//////////////////////////////////////////////////////////////*/// total yield from all siblingsuint256public totalUnderlyingAssets;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name, stringmemory _symbol, uint8 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID =block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/functionconvertToShares(uint256 underlyingAssets
) publicviewvirtualreturns (uint256) {
uint256 supply = _totalSupply; // Saves an extra SLOAD if _totalSupply is non-zero.return
supply ==0
? underlyingAssets
: underlyingAssets.mulDivDown(supply, totalUnderlyingAssets);
}
functionconvertToAssets(uint256 shares
) publicviewvirtualreturns (uint256) {
uint256 supply = _totalSupply; // Saves an extra SLOAD if _totalSupply is non-zero.return
supply ==0
? shares
: shares.mulDivDown(totalUnderlyingAssets, supply);
}
functionbalanceOf(address user_) externalviewreturns (uint256) {
uint256 balance = _balanceOf[user_];
if (balance ==0) return0;
return convertToAssets(balance);
}
// recheck for multi yieldfunctiontotalSupply() externalviewreturns (uint256) {
if (_totalSupply ==0) return0;
return totalUnderlyingAssets;
}
functionapprove(address spender_,
uint256 amount_
) publicvirtualreturns (bool) {
uint256 shares = convertToShares(amount_);
allowance[msg.sender][spender_] = shares;
emit Approval(msg.sender, spender_, shares);
returntrue;
}
functiontransfer(address to_,
uint256 amount_
) publicoverridereturns (bool) {
uint256 sharesToTransfer = convertToShares(amount_);
_balanceOf[msg.sender] -= sharesToTransfer;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
_balanceOf[to_] += sharesToTransfer;
}
emit Transfer(msg.sender, to_, amount_);
returntrue;
}
// transfer changes shares balance but reduces the amountfunctiontransferFrom(address from_,
address to_,
uint256 amount_
) publicoverridereturns (bool) {
uint256 sharesToTransfer = convertToShares(amount_);
uint256 allowed = allowance[from_][msg.sender]; // Saves gas for limited approvals.if (allowed !=type(uint256).max)
allowance[from_][msg.sender] = allowed - sharesToTransfer;
_balanceOf[from_] -= sharesToTransfer;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
_balanceOf[to_] += sharesToTransfer;
}
emit Transfer(from_, to_, amount_);
returntrue;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) publicvirtual{
if (deadline <block.timestamp) revert PermitDeadlineExpired();
// 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,
convertToShares(value),
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
if (recoveredAddress ==address(0) || recoveredAddress != owner)
revert InvalidSigner();
allowance[recoveredAddress][spender] = convertToShares(value);
}
emit Approval(owner, spender, convertToShares(value));
}
functionDOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID
? INITIAL_DOMAIN_SEPARATOR
: computeDomainSeparator();
}
functioncomputeDomainSeparator() internalviewvirtualreturns (bytes32) {
returnkeccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/function_mint(address to, uint256 amount) internalvirtual{
_totalSupply += amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
_balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function_burn(addressfrom, uint256 amount) internalvirtual{
_balanceOf[from] -= amount;
// Cannot underflow because a user's balance// will never be larger than the total supply.unchecked {
_totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}