Feature Tip: Add private address tag to any address under My Name Tag !
Source Code
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| 0x2e5d5c60b6c251bc8c2f70b453b7d2476c1a2ddea70ed6e4518d82debbbe1d4b | Deposit And Brid... | (pending) | 2 hrs ago | IN | 0.0000152507 ETH | (Pending) | |||
| Deposit And Brid... | 23612948 | 13 days ago | IN | 0.00001071 ETH | 0.0003807 | ||||
| Deposit And Brid... | 23559760 | 20 days ago | IN | 0.00001176 ETH | 0.00036142 | ||||
| Deposit And Brid... | 23544440 | 23 days ago | IN | 0.00001141 ETH | 0.00036455 | ||||
| Deposit And Brid... | 23544419 | 23 days ago | IN | 0.00001141 ETH | 0.00038517 | ||||
| Deposit And Brid... | 23544186 | 23 days ago | IN | 0.00001141 ETH | 0.00036364 | ||||
| Deposit And Brid... | 23532744 | 24 days ago | IN | 0.00001111 ETH | 0.00045506 | ||||
| Deposit And Brid... | 23521684 | 26 days ago | IN | 0.00001186 ETH | 0.00078288 | ||||
| Deposit And Brid... | 23508824 | 28 days ago | IN | 0.0000108 ETH | 0.00037728 | ||||
| Deposit And Brid... | 23506034 | 28 days ago | IN | 0.0000108 ETH | 0.00035903 | ||||
| Deposit And Brid... | 23499849 | 29 days ago | IN | 0.0000108 ETH | 0.00026202 | ||||
| Deposit And Brid... | 23491855 | 30 days ago | IN | 0.00001149 ETH | 0.00111173 | ||||
| Deposit And Brid... | 23484382 | 31 days ago | IN | 0.00001179 ETH | 0.00097277 | ||||
| Deposit And Brid... | 23472975 | 33 days ago | IN | 0.00001179 ETH | 0.00012784 | ||||
| Deposit And Brid... | 23434416 | 38 days ago | IN | 0.00001176 ETH | 0.00068434 | ||||
| Deposit And Brid... | 23434372 | 38 days ago | IN | 0.00001176 ETH | 0.00089 | ||||
| Deposit And Brid... | 23429461 | 39 days ago | IN | 0.00001525 ETH | 0.00000682 | ||||
| Deposit And Brid... | 23412267 | 41 days ago | IN | 0.00001213 ETH | 0.00038829 | ||||
| Deposit And Brid... | 23409512 | 41 days ago | IN | 0.00001213 ETH | 0.0003872 | ||||
| Deposit And Brid... | 23407788 | 42 days ago | IN | 0.00001213 ETH | 0.0004159 | ||||
| Deposit And Brid... | 23398260 | 43 days ago | IN | 0.00001213 ETH | 0.00071842 | ||||
| Deposit And Brid... | 23382458 | 45 days ago | IN | 0.00001213 ETH | 0.00018333 | ||||
| Deposit And Brid... | 23381574 | 45 days ago | IN | 0.00001213 ETH | 0.00046118 | ||||
| Deposit And Brid... | 23367376 | 47 days ago | IN | 0.00001299 ETH | 0.00018804 | ||||
| Deposit And Brid... | 23349698 | 50 days ago | IN | 0.00001209 ETH | 0.00075028 |
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| Deposit And Brid... | 23612948 | 13 days ago | 0.00001071 ETH | ||||
| Deposit And Brid... | 23559760 | 20 days ago | 0.00001176 ETH | ||||
| Deposit And Brid... | 23544440 | 23 days ago | 0.00001141 ETH | ||||
| Deposit And Brid... | 23544186 | 23 days ago | 0.00001141 ETH | ||||
| Deposit And Brid... | 23532744 | 24 days ago | 0.00001111 ETH | ||||
| Deposit And Brid... | 23521684 | 26 days ago | 0.00001186 ETH | ||||
| Deposit And Brid... | 23508824 | 28 days ago | 0.0000108 ETH | ||||
| Deposit And Brid... | 23506034 | 28 days ago | 0.0000108 ETH | ||||
| Deposit And Brid... | 23499849 | 29 days ago | 0.0000108 ETH | ||||
| Deposit And Brid... | 23491855 | 30 days ago | 0.00001149 ETH | ||||
| Deposit And Brid... | 23484382 | 31 days ago | 0.00001179 ETH | ||||
| Deposit And Brid... | 23472975 | 33 days ago | 0.00001179 ETH | ||||
| Deposit And Brid... | 23434416 | 38 days ago | 0.00001176 ETH | ||||
| Deposit And Brid... | 23434372 | 38 days ago | 0.00001176 ETH | ||||
| Deposit And Brid... | 23412267 | 41 days ago | 0.00001213 ETH | ||||
| Deposit And Brid... | 23407788 | 42 days ago | 0.00001213 ETH | ||||
| Deposit And Brid... | 23398260 | 43 days ago | 0.00001213 ETH | ||||
| Deposit And Brid... | 23382458 | 45 days ago | 0.00001213 ETH | ||||
| Deposit And Brid... | 23381574 | 45 days ago | 0.00001213 ETH | ||||
| Deposit And Brid... | 23367376 | 47 days ago | 0.00001299 ETH | ||||
| Deposit And Brid... | 23349698 | 50 days ago | 0.00001209 ETH | ||||
| Deposit And Brid... | 23339212 | 51 days ago | 0.00001239 ETH | ||||
| Deposit And Brid... | 23337170 | 52 days ago | 0.00001239 ETH | ||||
| Deposit And Brid... | 23314647 | 55 days ago | 0.00001089 ETH | ||||
| Deposit And Brid... | 23305355 | 56 days ago | 0.00001026 ETH |
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
DexAggregatorWrapperWithPredicateProxy
Compiler Version
v0.8.21+commit.d9974bed
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
// Solmate Imports
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { WETH } from "@solmate/tokens/WETH.sol";
import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol"; // Import SafeTransferLib
import { ReentrancyGuard } from "@solmate/utils/ReentrancyGuard.sol";
// Interface Imports
import { AggregationRouterV6 } from "src/interfaces/AggregationRouterV6.sol";
import { IOKXRouter } from "src/interfaces/IOKXRouter.sol";
// Base Contract Imports (Assuming paths are correct)
import { CrossChainTellerBase, BridgeData } from "src/base/Roles/CrossChain/CrossChainTellerBase.sol";
import { TellerWithMultiAssetSupport } from "src/base/Roles/TellerWithMultiAssetSupport.sol";
import { PredicateMessage } from "@predicate/src/interfaces/IPredicateClient.sol";
import { TellerWithMultiAssetSupportPredicateProxy } from "src/base/Roles/TellerWithMultiAssetSupportPredicateProxy.sol";
import { FixedPointMathLib } from "@solmate/utils/FixedPointMathLib.sol";
import { AccountantWithRateProviders } from "src/base/Roles/AccountantWithRateProviders.sol";
/**
* @custom:security-contact [email protected]
*/
contract DexAggregatorWrapperWithPredicateProxy is ReentrancyGuard {
// Apply SafeTransferLib only to ERC20 (WETH inherits from ERC20)
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
// --- State Variables ---
AggregationRouterV6 immutable aggregator;
IOKXRouter immutable okxRouter;
address immutable okxApprover;
WETH immutable canonicalWrapToken;
TellerWithMultiAssetSupportPredicateProxy immutable predicateProxy;
// --- OKX Function Selectors ---
bytes4 private constant SMART_SWAP_BY_ORDER_ID_SELECTOR = 0xb80c2f09;
bytes4 private constant SMART_SWAP_TO_SELECTOR = 0x03b87e5f;
bytes4 private constant UNISWAP_V3_SWAP_TO_SELECTOR = 0x0d5f0e3b;
bytes4 private constant UNISWAP_V3_SWAP_TO_WITH_PERMIT_SELECTOR = 0xf3e144b6;
bytes4 private constant UNXSWAP_BY_ORDER_ID_SELECTOR = 0x9871efa4;
bytes4 private constant UNXSWAP_TO_SELECTOR = 0x08298b5a;
// --- Errors ---
error DexAggregatorWrapper__InvalidSwapDescription();
error DexAggregatorWrapper__InvalidOkxSwapDescription();
error DexAggregatorWrapper__UnsupportedOkxFunction();
error DexAggregatorWrapper__OkxSwapFailed();
error DexAggregatorWrapper__InvalidFromToken();
error DexAggregatorWrapper__InsufficientEthForSwap();
error DexAggregatorWrapper__PredicateUnauthorizedTransaction();
error DexAggregatorWrapper__EthRefundFailed();
event Deposit(
address indexed depositAsset,
address indexed receiver,
address indexed supportedAsset,
uint256 depositAmount,
uint256 supportedAssetAmount,
uint256 shareAmount,
address teller,
address vaultAddress
);
// --- Constructor ---
constructor(
AggregationRouterV6 _aggregator,
IOKXRouter _okxRouter,
address _okxApprover,
WETH _canonicalWrapToken,
TellerWithMultiAssetSupportPredicateProxy _predicateProxy
) {
aggregator = _aggregator;
okxRouter = _okxRouter;
okxApprover = _okxApprover;
canonicalWrapToken = _canonicalWrapToken;
predicateProxy = _predicateProxy;
}
// --- Public Functions ---
function depositOneInch(
ERC20 supportedAsset,
TellerWithMultiAssetSupport teller,
uint256 minimumMint,
address executor,
AggregationRouterV6.SwapDescription calldata desc,
bytes calldata data,
uint256 nativeValueToWrap,
PredicateMessage calldata predicateMessage
)
external
payable
nonReentrant
returns (uint256 shares)
{
_checkPredicateProxy(predicateMessage);
uint256 supportedAssetAmount =
_oneInchHelper(supportedAsset, address(teller), executor, desc, data, nativeValueToWrap);
// Deposit into the vault
shares = teller.deposit(supportedAsset, supportedAssetAmount, minimumMint);
// Get vault address
address vaultAddress = address(teller.vault());
if (vaultAddress == address(0)) {
// Handle error: Vault address cannot be zero if we need to transfer shares
revert("DexAggregatorWrapper: Invalid vault address");
}
// Use safeTransfer to send shares to msg.sender
ERC20(vaultAddress).safeTransfer(msg.sender, shares);
_calcSharesAndEmitEvent(
supportedAsset,
CrossChainTellerBase(address(teller)),
address(desc.srcToken),
desc.amount,
supportedAssetAmount
);
}
function depositAndBridgeOneInch(
ERC20 supportedAsset,
CrossChainTellerBase teller,
uint256 minimumMint,
BridgeData calldata bridgeData,
address executor,
AggregationRouterV6.SwapDescription calldata desc,
bytes calldata data,
uint256 nativeValueToWrap,
PredicateMessage calldata predicateMessage
)
external
payable
nonReentrant
{
_checkPredicateProxy(predicateMessage);
uint256 supportedAssetAmount =
_oneInchHelper(supportedAsset, address(teller), executor, desc, data, nativeValueToWrap);
// Deposit and bridge assets
teller.depositAndBridge{ value: msg.value - nativeValueToWrap }(
supportedAsset, supportedAssetAmount, minimumMint, bridgeData
);
// Refund any excess ETH
_refundExcessEth(payable(msg.sender));
_calcSharesAndEmitEvent(
supportedAsset,
teller,
address(desc.srcToken),
desc.amount,
supportedAssetAmount
);
}
function depositOkxUniversal(
ERC20 supportedAsset,
TellerWithMultiAssetSupport teller,
uint256 minimumMint,
address fromToken,
uint256 fromTokenAmount,
bytes calldata okxCallData,
uint256 nativeValueToWrap,
PredicateMessage calldata predicateMessage
)
external
payable
nonReentrant
returns (uint256 shares)
{
_checkPredicateProxy(predicateMessage);
uint256 supportedAssetAmount =
_okxHelper(supportedAsset, address(teller), fromToken, fromTokenAmount, okxCallData, nativeValueToWrap);
// Deposit assets
shares = teller.deposit(supportedAsset, supportedAssetAmount, minimumMint);
// Get vault address
address vaultAddress = address(teller.vault());
if (vaultAddress == address(0)) {
revert("DexAggregatorWrapper: Invalid vault address");
}
// Use safeTransfer to send shares to msg.sender
ERC20(vaultAddress).safeTransfer(msg.sender, shares);
_calcSharesAndEmitEvent(
supportedAsset,
CrossChainTellerBase(address(teller)),
fromToken,
fromTokenAmount,
supportedAssetAmount
);
}
function depositAndBridgeOkxUniversal(
ERC20 supportedAsset,
CrossChainTellerBase teller,
uint256 minimumMint,
BridgeData calldata bridgeData,
address fromToken,
uint256 fromTokenAmount,
bytes calldata okxCallData,
uint256 nativeValueToWrap,
PredicateMessage calldata predicateMessage
)
external
payable
nonReentrant
{
_checkPredicateProxy(predicateMessage);
uint256 supportedAssetAmount =
_okxHelper(supportedAsset, address(teller), fromToken, fromTokenAmount, okxCallData, nativeValueToWrap);
// Deposit and bridge the assets
teller.depositAndBridge{ value: msg.value - nativeValueToWrap }(
supportedAsset, supportedAssetAmount, minimumMint, bridgeData
);
// Refund any excess ETH
_refundExcessEth(payable(msg.sender));
_calcSharesAndEmitEvent(
supportedAsset,
teller,
fromToken,
fromTokenAmount,
supportedAssetAmount
);
}
// --- Internal Helper Functions ---
function _oneInchHelper(
ERC20 supportedAsset,
address teller,
address executor,
AggregationRouterV6.SwapDescription calldata desc,
bytes calldata data,
uint256 nativeValueToWrap
)
internal
returns (uint256 supportedAssetAmount)
{
bool useNative = _checkAndMintNativeAmount(nativeValueToWrap);
// Assume desc.dstToken is ERC20 type as per original code structure
if (desc.dstToken != supportedAsset || desc.dstReceiver != address(this)) {
revert DexAggregatorWrapper__InvalidSwapDescription();
}
if (useNative) {
// Ensure desc.srcToken matches canonicalWrapToken address
if (address(desc.srcToken) != address(canonicalWrapToken) || desc.amount != nativeValueToWrap) {
revert DexAggregatorWrapper__InvalidSwapDescription();
}
// Use standard approve (as requested) - potential risk if WETH impl changes non-standardly
canonicalWrapToken.approve(address(aggregator), nativeValueToWrap);
} else {
ERC20 depositAsset = desc.srcToken; // Assumes desc.srcToken is ERC20 type
uint256 depositAmount = desc.amount;
// Use safeTransferFrom
depositAsset.safeTransferFrom(msg.sender, address(this), depositAmount);
// Approve agregator to take tokens from this contract
depositAsset.safeApprove(address(aggregator), depositAmount);
}
// Perform swap
(supportedAssetAmount,) = aggregator.swap(executor, desc, data);
// Approve teller's vault to spend the supported asset
// Cast teller address to TellerWithMultiAssetSupport to call vault()
address vaultAddress = address(TellerWithMultiAssetSupport(payable(teller)).vault());
if (vaultAddress == address(0)) {
revert("DexAggregatorWrapper: Invalid vault address for approval");
}
supportedAsset.safeApprove(vaultAddress, supportedAssetAmount);
return supportedAssetAmount;
}
function _okxHelper(
ERC20 supportedAsset,
address teller,
address fromToken,
uint256 fromTokenAmount,
bytes calldata okxCallData,
uint256 nativeValueToWrap
)
internal
returns (uint256 supportedAssetAmount)
{
bytes4 selector;
assembly {
selector := calldataload(okxCallData.offset)
}
if (
selector == SMART_SWAP_BY_ORDER_ID_SELECTOR || selector == SMART_SWAP_TO_SELECTOR
|| selector == UNISWAP_V3_SWAP_TO_SELECTOR || selector == UNISWAP_V3_SWAP_TO_WITH_PERMIT_SELECTOR
|| selector == UNXSWAP_BY_ORDER_ID_SELECTOR || selector == UNXSWAP_TO_SELECTOR
) {
bool useNative = _checkAndMintNativeAmount(nativeValueToWrap);
if (useNative) {
if (fromToken != address(canonicalWrapToken) || fromTokenAmount != nativeValueToWrap) {
revert DexAggregatorWrapper__OkxSwapFailed();
}
// Use standard approve (as requested)
canonicalWrapToken.approve(okxApprover, nativeValueToWrap);
} else {
// Cast fromToken address to ERC20 to use the library
ERC20 depositAsset = ERC20(fromToken);
// Use safeTransferFrom
depositAsset.safeTransferFrom(msg.sender, address(this), fromTokenAmount);
// Use standard approve (as requested) for the OKX approver
depositAsset.safeApprove(okxApprover, fromTokenAmount);
}
// Execute the swap with the provided calldata
(bool success, bytes memory result) = address(okxRouter).call(okxCallData);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
// Decode the return value
supportedAssetAmount = abi.decode(result, (uint256));
// Approve teller's vault to spend the supported asset
// Cast teller address to TellerWithMultiAssetSupport to call vault()
address vaultAddress = address(TellerWithMultiAssetSupport(payable(teller)).vault());
if (vaultAddress == address(0)) {
revert("DexAggregatorWrapper: Invalid vault address for approval");
}
// Use standard approve (as requested)
supportedAsset.safeApprove(vaultAddress, supportedAssetAmount);
// Return value needs to be here since it's declared in the function signature
return supportedAssetAmount;
} else {
revert DexAggregatorWrapper__UnsupportedOkxFunction();
}
// Note: If the selector doesn't match, the function will revert above, so no explicit return needed here.
}
function _checkAndMintNativeAmount(uint256 nativeAmount) internal returns (bool useNative) {
if (nativeAmount > msg.value) {
revert DexAggregatorWrapper__InsufficientEthForSwap();
}
if (nativeAmount > 0) {
// Direct WETH call, no SafeTransferLib needed here
canonicalWrapToken.deposit{ value: nativeAmount }();
useNative = true;
}
// Implicitly returns false if nativeAmount is 0
}
function _checkPredicateProxy(PredicateMessage calldata predicateMessage) internal {
if (!predicateProxy.genericUserCheckPredicate(msg.sender, predicateMessage)) {
revert DexAggregatorWrapper__PredicateUnauthorizedTransaction();
}
}
/**
* @notice Transfers the entire current ETH balance of this contract to the specified recipient.
* @param _recipient The address to receive the ETH refund.
* @dev Uses a low-level call and reverts if the transfer fails. This ensures atomicity,
* either the whole operation succeeds including refund, or it fails.
*/
function _refundExcessEth(address payable _recipient) internal {
uint256 balance = address(this).balance;
if (balance > 0) {
(bool success,) = _recipient.call{ value: balance }("");
if (!success) {
revert DexAggregatorWrapper__EthRefundFailed();
}
}
// If balance is 0, do nothing.
}
function _calcSharesAndEmitEvent(
ERC20 supportedAsset,
CrossChainTellerBase teller,
address fromToken,
uint256 fromTokenAmount,
uint256 supportedAssetAmount
)
internal
{
// Get vault address
address vaultAddress = address(teller.vault());
if (vaultAddress == address(0)) {
revert("DexAggregatorWrapper: Invalid vault address");
}
uint256 shares = supportedAssetAmount.mulDivDown(
10 ** teller.vault().decimals(),
AccountantWithRateProviders(teller.accountant()).getRateInQuoteSafe(supportedAsset)
);
emit Deposit(
fromToken,
msg.sender,
address(supportedAsset),
fromTokenAmount,
supportedAssetAmount,
shares,
address(teller),
address(teller.vault())
);
}
receive() external payable { }
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "./ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
/// @notice Minimalist and modern Wrapped Ether implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol)
/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol)
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
using SafeTransferLib for address;
event Deposit(address indexed from, uint256 amount);
event Withdrawal(address indexed to, uint256 amount);
function deposit() public payable virtual {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 amount) public virtual {
_burn(msg.sender, amount);
emit Withdrawal(msg.sender, amount);
msg.sender.safeTransferETH(amount);
}
receive() external payable virtual {
deposit();
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { ERC20 } from "@solmate/tokens/ERC20.sol";
interface AggregationRouterV6 {
struct SwapDescription {
ERC20 srcToken;
ERC20 dstToken;
address payable srcReceiver;
address payable dstReceiver;
uint256 amount;
uint256 minReturnAmount;
uint256 flags;
}
function swap(
address executor,
SwapDescription calldata desc,
bytes calldata data
)
external
payable
returns (uint256 returnAmount, uint256 spentAmount);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { ERC20 } from "@solmate/tokens/ERC20.sol";
/**
* @title IOKXRouter Interface
* @notice Interface for OKX DEX Router contract
*/
interface IOKXRouter {
struct BaseRequest {
uint256 fromToken;
address toToken;
uint256 fromTokenAmount;
uint256 minReturnAmount;
uint256 deadLine;
}
struct RouterPath {
address[] mixAdapters;
address[] assetTo;
uint256[] rawData;
bytes[] extraData;
uint256 fromToken;
}
/**
* @notice Executes a smart swap directly to a specified receiver address
* @param orderId Unique identifier for the swap order
* @param receiver Address to receive the output tokens
* @param baseRequest Contains parameters like tokens, amounts, deadline
* @param batchesAmount Array of amounts for each batch in the swap
* @param batches Detailed routing info for executing swap across different paths
* @param extraData Additional data for certain swaps
* @return returnAmount Total amount of destination tokens received
*/
function smartSwapTo(
uint256 orderId,
address receiver,
BaseRequest calldata baseRequest,
uint256[] calldata batchesAmount,
RouterPath[][] calldata batches,
bytes[] calldata extraData
)
external
payable
returns (uint256 returnAmount);
/**
* @notice Executes a token swap using Unxswap protocol
* @param srcToken The source token to be swapped
* @param amount The amount of source token to swap
* @param minReturnAmount The minimum acceptable return amount
* @param receiver The address to receive the swapped tokens
* @param pools Array of pool identifiers for the swap route
* @return returnAmount The amount of tokens received
*/
function unxswapTo(
uint256 srcToken,
uint256 amount,
uint256 minReturnAmount,
address receiver,
bytes32[] calldata pools
)
external
payable
returns (uint256 returnAmount);
/**
* @notice Executes a token swap using Uniswap V3 protocol
* @param receiver Encoded recipient address
* @param amount The amount of source token to swap
* @param minReturnAmount The minimum acceptable return amount
* @param pools Array of pool identifiers for the swap route
* @return returnAmount The amount of tokens received
*/
function uniswapV3SwapTo(
uint256 receiver,
uint256 amount,
uint256 minReturnAmount,
uint256[] calldata pools
)
external
payable
returns (uint256 returnAmount);
/**
* @notice Executes a Uniswap V3 swap after obtaining a permit
* @param receiver Encoded recipient address
* @param srcToken The token to swap from
* @param amount The amount of tokens to swap
* @param minReturnAmount The minimum acceptable return amount
* @param pools Array of pool identifiers for the swap route
* @param permit The signed permit message for token approval
* @return returnAmount The amount of tokens received
*/
function uniswapV3SwapToWithPermit(
uint256 receiver,
ERC20 srcToken,
uint256 amount,
uint256 minReturnAmount,
uint256[] calldata pools,
bytes calldata permit
)
external
returns (uint256 returnAmount);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { TellerWithMultiAssetSupport } from "../TellerWithMultiAssetSupport.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
struct BridgeData {
uint32 chainSelector;
address destinationChainReceiver;
ERC20 bridgeFeeToken;
uint64 messageGas;
bytes data;
}
/**
* @title CrossChainTellerBase
* @notice Base contract for the CrossChainTeller, includes functions to overload with specific bridge method
*/
abstract contract CrossChainTellerBase is TellerWithMultiAssetSupport {
event MessageSent(bytes32 messageId, uint256 shareAmount, address to);
event MessageReceived(bytes32 messageId, uint256 shareAmount, address to);
constructor(
address _owner,
address _vault,
address _accountant
)
TellerWithMultiAssetSupport(_owner, _vault, _accountant)
{ }
/**
* @notice function to deposit into the vault AND bridge crosschain in 1 call
* @param depositAsset ERC20 to deposit
* @param depositAmount amount of deposit asset to deposit
* @param minimumMint minimum required shares to receive
* @param data Bridge Data
*/
function depositAndBridge(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint,
BridgeData calldata data
)
external
payable
requiresAuth
nonReentrant
{
if (!isSupported[depositAsset]) {
revert TellerWithMultiAssetSupport__AssetNotSupported();
}
uint256 shareAmount = _erc20Deposit(depositAsset, depositAmount, minimumMint, msg.sender);
_afterPublicDeposit(msg.sender, depositAsset, depositAmount, shareAmount, shareLockPeriod);
bridge(shareAmount, data);
}
/**
* @notice Preview fee required to bridge shares in a given feeToken.
*/
function previewFee(uint256 shareAmount, BridgeData calldata data) external view returns (uint256 fee) {
return _quote(shareAmount, data);
}
/**
* @notice bridging code to be done without deposit, for users who already have vault tokens
* @param shareAmount to bridge
* @param data bridge data
*/
function bridge(
uint256 shareAmount,
BridgeData calldata data
)
public
payable
requiresAuth
returns (bytes32 messageId)
{
if (isPaused) revert TellerWithMultiAssetSupport__Paused();
_beforeBridge(data);
// Since shares are directly burned, call `beforeTransfer` to enforce before transfer hooks.
beforeTransfer(msg.sender);
// Burn shares from sender
vault.exit(address(0), ERC20(address(0)), 0, msg.sender, shareAmount);
messageId = _bridge(shareAmount, data);
_afterBridge(shareAmount, data, messageId);
}
/**
* @notice the virtual bridge function to be overridden
* @param data bridge data
* @return messageId
*/
function _bridge(uint256 shareAmount, BridgeData calldata data) internal virtual returns (bytes32);
/**
* @notice the virtual function to override to get bridge fees
* @param shareAmount to send
* @param data bridge data
*/
function _quote(uint256 shareAmount, BridgeData calldata data) internal view virtual returns (uint256);
/**
* @notice after bridge code, just an emit but can be overridden
* @notice the before bridge hook to perform additional checks
* @param data bridge data
*/
function _beforeBridge(BridgeData calldata data) internal virtual;
/**
* @notice after bridge code, just an emit but can be overridden
* @param shareAmount share amount burned
* @param data bridge data
* @param messageId message id returned when bridged
*/
function _afterBridge(uint256 shareAmount, BridgeData calldata data, bytes32 messageId) internal virtual {
emit MessageSent(messageId, shareAmount, data.destinationChainReceiver);
}
/**
* @notice a before receive hook to call some logic before a receive is processed
*/
function _beforeReceive() internal virtual {
if (isPaused) revert TellerWithMultiAssetSupport__Paused();
}
/**
* @notice a hook to execute after receiving
* @param shareAmount the shareAmount that was minted
* @param destinationChainReceiver the receiver of the shares
* @param messageId the message ID
*/
function _afterReceive(uint256 shareAmount, address destinationChainReceiver, bytes32 messageId) internal virtual {
emit MessageReceived(messageId, shareAmount, destinationChainReceiver);
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { WETH } from "@solmate/tokens/WETH.sol";
import { BoringVault } from "src/base/BoringVault.sol";
import { AccountantWithRateProviders } from "src/base/Roles/AccountantWithRateProviders.sol";
import { FixedPointMathLib } from "@solmate/utils/FixedPointMathLib.sol";
import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol";
import { BeforeTransferHook } from "src/interfaces/BeforeTransferHook.sol";
import { Auth, Authority } from "@solmate/auth/Auth.sol";
import { ReentrancyGuard } from "@solmate/utils/ReentrancyGuard.sol";
/**
* @title TellerWithMultiAssetSupport
* @custom:security-contact [email protected]
*/
contract TellerWithMultiAssetSupport is Auth, BeforeTransferHook, ReentrancyGuard {
using FixedPointMathLib for uint256;
using SafeTransferLib for ERC20;
using SafeTransferLib for WETH;
// ========================================= CONSTANTS =========================================
/**
* @notice Native address used to tell the contract to handle native asset deposits.
*/
address internal constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/**
* @notice The maximum possible share lock period.
*/
uint256 internal constant MAX_SHARE_LOCK_PERIOD = 3 days;
// ========================================= STATE =========================================
/**
* @notice Mapping ERC20s to an isSupported bool.
*/
mapping(ERC20 => bool) public isSupported;
/**
* @notice The deposit nonce used to map to a deposit hash.
*/
uint96 public depositNonce = 1;
/**
* @notice After deposits, shares are locked to the msg.sender's address
* for `shareLockPeriod`.
* @dev During this time all transfers from msg.sender will revert, and
* deposits are refundable.
*/
uint64 public shareLockPeriod;
/**
* @notice Used to pause calls to `deposit` and `depositWithPermit`.
*/
bool public isPaused;
/**
* @dev Maps deposit nonce to keccak256(address receiver, address depositAsset, uint256 depositAmount, uint256
* shareAmount, uint256 timestamp, uint256 shareLockPeriod).
*/
mapping(uint256 => bytes32) public publicDepositHistory;
/**
* @notice Maps user address to the time their shares will be unlocked.
*/
mapping(address => uint256) public shareUnlockTime;
//============================== ERRORS ===============================
error TellerWithMultiAssetSupport__ShareLockPeriodTooLong();
error TellerWithMultiAssetSupport__SharesAreLocked();
error TellerWithMultiAssetSupport__SharesAreUnLocked();
error TellerWithMultiAssetSupport__BadDepositHash();
error TellerWithMultiAssetSupport__AssetNotSupported();
error TellerWithMultiAssetSupport__ZeroAssets();
error TellerWithMultiAssetSupport__MinimumMintNotMet();
error TellerWithMultiAssetSupport__MinimumAssetsNotMet();
error TellerWithMultiAssetSupport__PermitFailedAndAllowanceTooLow();
error TellerWithMultiAssetSupport__ZeroShares();
error TellerWithMultiAssetSupport__Paused();
//============================== EVENTS ===============================
event Paused();
event Unpaused();
event AssetAdded(address indexed asset);
event AssetRemoved(address indexed asset);
event Deposit(
uint256 indexed nonce,
address indexed receiver,
address indexed depositAsset,
uint256 depositAmount,
uint256 shareAmount,
uint256 depositTimestamp,
uint256 shareLockPeriodAtTimeOfDeposit
);
event BulkDeposit(address indexed asset, uint256 depositAmount);
event BulkWithdraw(address indexed asset, uint256 shareAmount);
event DepositRefunded(uint256 indexed nonce, bytes32 depositHash, address indexed user);
//============================== IMMUTABLES ===============================
/**
* @notice The BoringVault this contract is working with.
*/
BoringVault public immutable vault;
/**
* @notice The AccountantWithRateProviders this contract is working with.
*/
AccountantWithRateProviders public immutable accountant;
/**
* @notice One share of the BoringVault.
*/
uint256 internal immutable ONE_SHARE;
constructor(address _owner, address _vault, address _accountant) Auth(_owner, Authority(address(0))) {
vault = BoringVault(payable(_vault));
ONE_SHARE = 10 ** vault.decimals();
accountant = AccountantWithRateProviders(_accountant);
}
// ========================================= ADMIN FUNCTIONS =========================================
/**
* @notice Pause this contract, which prevents future calls to `deposit` and `depositWithPermit`.
* @dev Callable by MULTISIG_ROLE.
*/
function pause() external requiresAuth {
isPaused = true;
emit Paused();
}
/**
* @notice Unpause this contract, which allows future calls to `deposit` and `depositWithPermit`.
* @dev Callable by MULTISIG_ROLE.
*/
function unpause() external requiresAuth {
isPaused = false;
emit Unpaused();
}
/**
* @notice Adds this asset as a deposit asset.
* @dev The accountant must also support pricing this asset, else the `deposit` call will revert.
* @dev Callable by OWNER_ROLE.
*/
function addAsset(ERC20 asset) external requiresAuth {
isSupported[asset] = true;
emit AssetAdded(address(asset));
}
/**
* @notice Removes this asset as a deposit asset.
* @dev Callable by OWNER_ROLE.
*/
function removeAsset(ERC20 asset) external requiresAuth {
isSupported[asset] = false;
emit AssetRemoved(address(asset));
}
/**
* @notice Sets the share lock period.
* @dev This not only locks shares to the user address, but also serves as the pending deposit period, where
* deposits can be reverted.
* @dev If a new shorter share lock period is set, users with pending share locks could make a new deposit to
* receive 1 wei shares,
* and have their shares unlock sooner than their original deposit allows. This state would allow for the user
* deposit to be refunded,
* but only if they have not transferred their shares out of there wallet. This is an accepted limitation, and
* should be known when decreasing
* the share lock period.
* @dev Callable by OWNER_ROLE.
*/
function setShareLockPeriod(uint64 _shareLockPeriod) external requiresAuth {
if (_shareLockPeriod > MAX_SHARE_LOCK_PERIOD) revert TellerWithMultiAssetSupport__ShareLockPeriodTooLong();
shareLockPeriod = _shareLockPeriod;
}
// ========================================= BeforeTransferHook FUNCTIONS =========================================
/**
* @notice Implement beforeTransfer hook to check if shares are locked.
*/
function beforeTransfer(address from) public view {
if (shareUnlockTime[from] > block.timestamp) revert TellerWithMultiAssetSupport__SharesAreLocked();
}
// ========================================= REVERT DEPOSIT FUNCTIONS =========================================
/**
* @notice Allows DEPOSIT_REFUNDER_ROLE to revert a pending deposit.
* @dev Once a deposit share lock period has passed, it can no longer be reverted.
* @dev It is possible the admin does not setup the BoringVault to call the transfer hook,
* but this contract can still be saving share lock state. In the event this happens
* deposits are still refundable if the user has not transferred their shares.
* But there is no guarantee that the user has not transferred their shares.
* @dev Callable by STRATEGIST_MULTISIG_ROLE.
*/
function refundDeposit(
uint256 nonce,
address receiver,
address depositAsset,
uint256 depositAmount,
uint256 shareAmount,
uint256 depositTimestamp,
uint256 shareLockUpPeriodAtTimeOfDeposit
)
external
requiresAuth
{
if ((block.timestamp - depositTimestamp) > shareLockUpPeriodAtTimeOfDeposit) {
// Shares are already unlocked, so we can not revert deposit.
revert TellerWithMultiAssetSupport__SharesAreUnLocked();
}
bytes32 depositHash = keccak256(
abi.encode(
receiver, depositAsset, depositAmount, shareAmount, depositTimestamp, shareLockUpPeriodAtTimeOfDeposit
)
);
if (publicDepositHistory[nonce] != depositHash) revert TellerWithMultiAssetSupport__BadDepositHash();
// Delete hash to prevent refund gas.
delete publicDepositHistory[nonce];
// Burn shares and refund assets to receiver.
vault.exit(receiver, ERC20(depositAsset), depositAmount, receiver, shareAmount);
emit DepositRefunded(nonce, depositHash, receiver);
}
// ========================================= USER FUNCTIONS =========================================
/**
* @notice Allows users to deposit into the BoringVault, if this contract is not paused.
* @dev Publicly callable.
*/
function deposit(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint
)
external
requiresAuth
nonReentrant
returns (uint256 shares)
{
if (isPaused) revert TellerWithMultiAssetSupport__Paused();
if (!isSupported[depositAsset]) revert TellerWithMultiAssetSupport__AssetNotSupported();
shares = _erc20Deposit(depositAsset, depositAmount, minimumMint, msg.sender);
_afterPublicDeposit(msg.sender, depositAsset, depositAmount, shares, shareLockPeriod);
}
/**
* @notice Allows users to deposit into BoringVault using permit.
* @dev Publicly callable.
*/
function depositWithPermit(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external
requiresAuth
nonReentrant
returns (uint256 shares)
{
if (isPaused) revert TellerWithMultiAssetSupport__Paused();
if (!isSupported[depositAsset]) revert TellerWithMultiAssetSupport__AssetNotSupported();
// solhint-disable-next-line no-empty-blocks
try depositAsset.permit(msg.sender, address(vault), depositAmount, deadline, v, r, s) { }
catch {
if (depositAsset.allowance(msg.sender, address(vault)) < depositAmount) {
revert TellerWithMultiAssetSupport__PermitFailedAndAllowanceTooLow();
}
}
shares = _erc20Deposit(depositAsset, depositAmount, minimumMint, msg.sender);
_afterPublicDeposit(msg.sender, depositAsset, depositAmount, shares, shareLockPeriod);
}
/**
* @notice Allows on ramp role to deposit into this contract.
* @dev Does NOT support native deposits.
* @dev Callable by SOLVER_ROLE.
*/
function bulkDeposit(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint,
address to
)
external
requiresAuth
nonReentrant
returns (uint256 shares)
{
if (!isSupported[depositAsset]) revert TellerWithMultiAssetSupport__AssetNotSupported();
shares = _erc20Deposit(depositAsset, depositAmount, minimumMint, to);
emit BulkDeposit(address(depositAsset), depositAmount);
}
/**
* @notice Allows off ramp role to withdraw from this contract.
* @dev Callable by SOLVER_ROLE.
*/
function bulkWithdraw(
ERC20 withdrawAsset,
uint256 shareAmount,
uint256 minimumAssets,
address to
)
external
requiresAuth
returns (uint256 assetsOut)
{
if (!isSupported[withdrawAsset]) revert TellerWithMultiAssetSupport__AssetNotSupported();
if (shareAmount == 0) revert TellerWithMultiAssetSupport__ZeroShares();
assetsOut = shareAmount.mulDivDown(accountant.getRateInQuoteSafe(withdrawAsset), ONE_SHARE);
if (assetsOut < minimumAssets) revert TellerWithMultiAssetSupport__MinimumAssetsNotMet();
vault.exit(to, withdrawAsset, assetsOut, msg.sender, shareAmount);
emit BulkWithdraw(address(withdrawAsset), shareAmount);
}
// ========================================= INTERNAL HELPER FUNCTIONS =========================================
/**
* @notice Implements a common ERC20 deposit into BoringVault.
*/
function _erc20Deposit(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint,
address to
)
internal
returns (uint256 shares)
{
if (depositAmount == 0) revert TellerWithMultiAssetSupport__ZeroAssets();
shares = depositAmount.mulDivDown(ONE_SHARE, accountant.getRateInQuoteSafe(depositAsset));
if (shares < minimumMint) revert TellerWithMultiAssetSupport__MinimumMintNotMet();
vault.enter(msg.sender, depositAsset, depositAmount, to, shares);
}
/**
* @notice Handle share lock logic, and event.
*/
function _afterPublicDeposit(
address user,
ERC20 depositAsset,
uint256 depositAmount,
uint256 shares,
uint256 currentShareLockPeriod
)
internal
{
shareUnlockTime[user] = block.timestamp + currentShareLockPeriod;
uint256 nonce = depositNonce;
publicDepositHistory[nonce] =
keccak256(abi.encode(user, depositAsset, depositAmount, shares, block.timestamp, currentShareLockPeriod));
depositNonce++;
emit Deposit(nonce, user, address(depositAsset), depositAmount, shares, block.timestamp, currentShareLockPeriod);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {IPredicateManager} from "../interfaces/IPredicateManager.sol";
// @notice Struct that bundles together a task's parameters for validation
struct PredicateMessage {
// the unique identifier for the task
string taskId;
// the expiration block number for the task
uint256 expireByBlockNumber;
// the operators that have signed the task
address[] signerAddresses;
// the signatures of the operators that have signed the task
bytes[] signatures;
}
// @notice Struct to contain stateful values for PredicateClient-type contracts
// @custom:storage-location erc7201:predicate.storage.PredicateClient
struct PredicateClientStorage {
IPredicateManager serviceManager;
string policyID;
}
// @notice error type for unauthorized access
error PredicateClient__Unauthorized();
// @notice Interface for a PredicateClient-type contract that enables clients to define execution rules or parameters for tasks they submit
interface IPredicateClient {
/**
* @notice Sets a policy for the calling address, associating it with a policy document stored on IPFS.
* @param _policyID A string representing the policyID from on chain.
* @dev This function enables clients to define execution rules or parameters for tasks they submit.
* The policy governs how tasks submitted by the caller are executed, ensuring compliance with predefined rules.
*/
function setPolicy(
string memory _policyID
) external;
/**
* @notice Retrieves the policy for the calling address.
* @return The policyID associated with the calling address.
*/
function getPolicy() external view returns (string memory);
/**
* @notice Function for setting the Predicate ServiceManager
* @param _predicateManager address of the service manager
*/
function setPredicateManager(
address _predicateManager
) external;
/**
* @notice Function for getting the Predicate ServiceManager
* @return address of the service manager
*/
function getPredicateManager() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { ReentrancyGuard } from "@solmate/utils/ReentrancyGuard.sol";
import { PredicateClient } from "@predicate/src/mixins/PredicateClient.sol";
import { PredicateMessage } from "@predicate/src/interfaces/IPredicateClient.sol";
import { IPredicateManager } from "@predicate/src/interfaces/IPredicateManager.sol";
import { BridgeData, CrossChainTellerBase } from "src/base/Roles/CrossChain/CrossChainTellerBase.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol";
import { FixedPointMathLib } from "@solmate/utils/FixedPointMathLib.sol";
import { AccountantWithRateProviders } from "src/base/Roles/AccountantWithRateProviders.sol";
/**
* @title TellerWithMultiAssetSupportPredicateProxy
* @custom:security-contact [email protected]
*/
contract TellerWithMultiAssetSupportPredicateProxy is Ownable, ReentrancyGuard, PredicateClient, Pausable {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
//============================== ERRORS ===============================
error TellerWithMultiAssetSupportPredicateProxy__PredicateUnauthorizedTransaction();
error TellerWithMultiAssetSupportPredicateProxy__Paused();
error TellerWithMultiAssetSupportPredicateProxy__ETHTransferFailed();
event Deposit(
address indexed teller,
address indexed receiver,
address indexed depositAsset,
uint256 depositAmount,
uint256 shareAmount,
uint256 depositTimestamp,
uint256 shareLockPeriodAtTimeOfDeposit,
uint256 nonce,
address vault
);
//============================== IMMUTABLES ===============================
/**
* @notice Stores the last sender who called the contract
* This is used to route refunds to the correct user on deposit and bridge
*/
address private lastSender;
constructor(address _owner, address _serviceManager, string memory _policyID) Ownable(_owner) {
_initPredicateClient(_serviceManager, _policyID);
}
// ========================================= USER FUNCTIONS =========================================
/**
* @notice Allows users to deposit into the BoringVault, if the teller contract is not paused.
* @dev Publicly callable. Uses the predicate authorization pattern to validate the transaction
* @param depositAsset ERC20 to deposit
* @param depositAmount Amount of deposit asset to deposit
* @param minimumMint Minimum required shares to receive
* @param recipient Address which to forward shares
* @param teller CrossChainTellerBase contract to deposit into
* @param predicateMessage Predicate message to authorize the transaction
*/
function deposit(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint,
address recipient,
CrossChainTellerBase teller,
PredicateMessage calldata predicateMessage
)
external
nonReentrant
returns (uint256 shares)
{
if (paused()) {
revert TellerWithMultiAssetSupportPredicateProxy__Paused();
}
//@dev This is NOT the actual function that is called, it is the against which the predicate is authorized
bytes memory encodedSigAndArgs = abi.encodeWithSignature("deposit()");
if (!_authorizeTransaction(predicateMessage, encodedSigAndArgs, msg.sender, 0)) {
revert TellerWithMultiAssetSupportPredicateProxy__PredicateUnauthorizedTransaction();
}
ERC20 vault = ERC20(teller.vault());
//approve vault to take assets from proxy
depositAsset.safeApprove(address(vault), depositAmount);
//transfer deposit assets from sender to this contract
depositAsset.safeTransferFrom(msg.sender, address(this), depositAmount);
// mint shares
shares = teller.deposit(depositAsset, depositAmount, minimumMint);
vault.safeTransfer(recipient, shares);
uint96 nonce = teller.depositNonce();
//get the current share lock period
uint64 currentShareLockPeriod = teller.shareLockPeriod();
emit Deposit(
address(teller),
msg.sender,
address(depositAsset),
depositAmount,
shares,
block.timestamp,
currentShareLockPeriod,
nonce > 0 ? nonce - 1 : 0,
address(vault)
);
}
/**
* @notice function to deposit into the vault AND bridge crosschain in 1 call
* @dev Uses the predicate authorization pattern to validate the transaction
* @param depositAsset ERC20 to deposit
* @param depositAmount amount of deposit asset to deposit
* @param minimumMint minimum required shares to receive
* @param teller CrossChainTellerBase contract to deposit into
* @param data Bridge Data
* @param predicateMessage Predicate message to authorize the transaction
*/
function depositAndBridge(
ERC20 depositAsset,
uint256 depositAmount,
uint256 minimumMint,
BridgeData calldata data,
CrossChainTellerBase teller,
PredicateMessage calldata predicateMessage
)
external
payable
nonReentrant
{
if (paused()) {
revert TellerWithMultiAssetSupportPredicateProxy__Paused();
}
//@dev This is NOT the actual function that is called, it is the against which the predicate is authorized
bytes memory encodedSigAndArgs = abi.encodeWithSignature("depositAndBridge()");
//still use 0 for msg.value since we only need validation against sender address
if (!_authorizeTransaction(predicateMessage, encodedSigAndArgs, msg.sender, 0)) {
revert TellerWithMultiAssetSupportPredicateProxy__PredicateUnauthorizedTransaction();
}
lastSender = msg.sender;
ERC20 vault = ERC20(teller.vault());
//approve vault to take assets from proxy
depositAsset.safeApprove(address(vault), depositAmount);
//transfer deposit assets from sender to this contract
depositAsset.safeTransferFrom(msg.sender, address(this), depositAmount);
// mint shares
teller.depositAndBridge{ value: msg.value }(depositAsset, depositAmount, minimumMint, data);
lastSender = address(0);
uint96 nonce = teller.depositNonce();
//get the current share lock period
uint64 currentShareLockPeriod = teller.shareLockPeriod();
AccountantWithRateProviders accountant = AccountantWithRateProviders(teller.accountant());
//get the share amount
uint256 shares = depositAmount.mulDivDown(10 ** vault.decimals(), accountant.getRateInQuoteSafe(depositAsset));
emit Deposit(
address(teller),
msg.sender,
address(depositAsset),
depositAmount,
shares,
block.timestamp,
currentShareLockPeriod,
nonce > 0 ? nonce - 1 : 0,
address(vault)
);
}
/**
* @notice Function to check if the user is authorized to call the predicate
* @dev This is NOT an actual function that is called, it serves as a function to allow any contract to check a user
* against the predicate
* @param user address of the user
* @param predicateMessage Predicate message to authorize the transaction
*/
function genericUserCheckPredicate(
address user,
PredicateMessage calldata predicateMessage
)
external
returns (bool)
{
//@dev This is NOT an actual function that is called, it is the against which the predicate is authorized
bytes memory encodedSigAndArgs = abi.encodeWithSignature("accessCheck(address)", user);
//still use 0 for msg.value since we only need validation against sender and user address
if (!_authorizeTransaction(predicateMessage, encodedSigAndArgs, msg.sender, 0)) {
return false;
}
return true;
}
/**
* @notice Updates the policy ID
* @param _policyID policy ID from onchain
*/
function setPolicy(string memory _policyID) external onlyOwner {
_setPolicy(_policyID);
}
/**
* @notice Function for setting the ServiceManager
* @param _predicateManager address of the service manager
*/
function setPredicateManager(address _predicateManager) public onlyOwner {
_setPredicateManager(_predicateManager);
}
/**
* @notice Allows the contract to receive ETH refunds and forwards them to the original sender
*/
receive() external payable {
// If we have a lastSender and receive ETH, forward it
if (lastSender != address(0) && msg.value > 0) {
// Forward the ETH to the last sender
(bool success,) = lastSender.call{ value: msg.value }("");
if (!success) revert TellerWithMultiAssetSupportPredicateProxy__ETHTransferFailed();
}
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=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)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(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)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(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))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// 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
pragma solidity 0.8.21;
import { FixedPointMathLib } from "@solmate/utils/FixedPointMathLib.sol";
import { IRateProvider } from "src/interfaces/IRateProvider.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol";
import { BoringVault } from "src/base/BoringVault.sol";
import { Auth, Authority } from "@solmate/auth/Auth.sol";
/**
* @title AccountantWithRateProviders
* @custom:security-contact [email protected]
*/
contract AccountantWithRateProviders is Auth, IRateProvider {
using FixedPointMathLib for uint256;
using SafeTransferLib for ERC20;
// ========================================= STRUCTS =========================================
/**
* @param payoutAddress the address `claimFees` sends fees to
* @param feesOwedInBase total pending fees owed in terms of base
* @param totalSharesLastUpdate total amount of shares the last exchange rate update
* @param exchangeRate the current exchange rate in terms of base
* @param allowedExchangeRateChangeUpper the max allowed change to exchange rate from an update
* @param allowedExchangeRateChangeLower the min allowed change to exchange rate from an update
* @param lastUpdateTimestamp the block timestamp of the last exchange rate update
* @param isPaused whether or not this contract is paused
* @param minimumUpdateDelayInSeconds the minimum amount of time that must pass between
* exchange rate updates, such that the update won't trigger the contract to be paused
* @param managementFee the management fee
*/
struct AccountantState {
address payoutAddress;
uint128 feesOwedInBase;
uint128 totalSharesLastUpdate;
uint96 exchangeRate;
uint16 allowedExchangeRateChangeUpper;
uint16 allowedExchangeRateChangeLower;
uint64 lastUpdateTimestamp;
bool isPaused;
uint32 minimumUpdateDelayInSeconds;
uint16 managementFee;
}
/**
* @param isPeggedToBase whether or not the asset is 1:1 with the base asset
* @param rateProvider the rate provider for this asset if `isPeggedToBase` is false
*/
struct RateProviderData {
bool isPeggedToBase;
IRateProvider rateProvider;
}
// ========================================= STATE =========================================
/**
* @notice Store the accountant state in 3 packed slots.
*/
AccountantState public accountantState;
/**
* @notice Maps ERC20s to their RateProviderData.
*/
mapping(ERC20 => RateProviderData) public rateProviderData;
//============================== ERRORS ===============================
error AccountantWithRateProviders__UpperBoundTooSmall();
error AccountantWithRateProviders__LowerBoundTooLarge();
error AccountantWithRateProviders__ManagementFeeTooLarge();
error AccountantWithRateProviders__Paused();
error AccountantWithRateProviders__ZeroFeesOwed();
error AccountantWithRateProviders__OnlyCallableByBoringVault();
error AccountantWithRateProviders__UpdateDelayTooLarge();
//============================== EVENTS ===============================
event Paused();
event Unpaused();
event DelayInSecondsUpdated(uint32 oldDelay, uint32 newDelay);
event UpperBoundUpdated(uint16 oldBound, uint16 newBound);
event LowerBoundUpdated(uint16 oldBound, uint16 newBound);
event ManagementFeeUpdated(uint16 oldFee, uint16 newFee);
event PayoutAddressUpdated(address oldPayout, address newPayout);
event RateProviderUpdated(address asset, bool isPegged, address rateProvider);
event ExchangeRateUpdated(uint96 oldRate, uint96 newRate, uint64 currentTime);
event FeesClaimed(address indexed feeAsset, uint256 amount);
//============================== IMMUTABLES ===============================
/**
* @notice The base asset rates are provided in.
*/
ERC20 public immutable base;
/**
* @notice The decimals rates are provided in.
*/
uint8 public immutable decimals;
/**
* @notice The BoringVault this accountant is working with.
* Used to determine share supply for fee calculation.
*/
BoringVault public immutable vault;
/**
* @notice One share of the BoringVault.
*/
uint256 internal immutable ONE_SHARE;
constructor(
address _owner,
address _vault,
address payoutAddress,
uint96 startingExchangeRate,
address _base,
uint16 allowedExchangeRateChangeUpper,
uint16 allowedExchangeRateChangeLower,
uint32 minimumUpdateDelayInSeconds,
uint16 managementFee
)
Auth(_owner, Authority(address(0)))
{
base = ERC20(_base);
decimals = ERC20(_base).decimals();
vault = BoringVault(payable(_vault));
ONE_SHARE = 10 ** vault.decimals();
accountantState = AccountantState({
payoutAddress: payoutAddress,
feesOwedInBase: 0,
totalSharesLastUpdate: uint128(vault.totalSupply()),
exchangeRate: startingExchangeRate,
allowedExchangeRateChangeUpper: allowedExchangeRateChangeUpper,
allowedExchangeRateChangeLower: allowedExchangeRateChangeLower,
lastUpdateTimestamp: uint64(block.timestamp),
isPaused: false,
minimumUpdateDelayInSeconds: minimumUpdateDelayInSeconds,
managementFee: managementFee
});
}
// ========================================= ADMIN FUNCTIONS =========================================
/**
* @notice Pause this contract, which prevents future calls to `updateExchangeRate`, and any safe rate
* calls will revert.
* @dev Callable by MULTISIG_ROLE.
*/
function pause() external requiresAuth {
accountantState.isPaused = true;
emit Paused();
}
/**
* @notice Unpause this contract, which allows future calls to `updateExchangeRate`, and any safe rate
* calls will stop reverting.
* @dev Callable by MULTISIG_ROLE.
*/
function unpause() external requiresAuth {
accountantState.isPaused = false;
emit Unpaused();
}
/**
* @notice Update the minimum time delay between `updateExchangeRate` calls.
* @dev There are no input requirements, as it is possible the admin would want
* the exchange rate updated as frequently as needed.
* @dev Callable by OWNER_ROLE.
*/
function updateDelay(uint32 minimumUpdateDelayInSeconds) external requiresAuth {
if (minimumUpdateDelayInSeconds > 14 days) revert AccountantWithRateProviders__UpdateDelayTooLarge();
uint32 oldDelay = accountantState.minimumUpdateDelayInSeconds;
accountantState.minimumUpdateDelayInSeconds = minimumUpdateDelayInSeconds;
emit DelayInSecondsUpdated(oldDelay, minimumUpdateDelayInSeconds);
}
/**
* @notice Update the allowed upper bound change of exchange rate between `updateExchangeRateCalls`.
* @dev Callable by OWNER_ROLE.
*/
function updateUpper(uint16 allowedExchangeRateChangeUpper) external requiresAuth {
if (allowedExchangeRateChangeUpper < 1e4) revert AccountantWithRateProviders__UpperBoundTooSmall();
uint16 oldBound = accountantState.allowedExchangeRateChangeUpper;
accountantState.allowedExchangeRateChangeUpper = allowedExchangeRateChangeUpper;
emit UpperBoundUpdated(oldBound, allowedExchangeRateChangeUpper);
}
/**
* @notice Update the allowed lower bound change of exchange rate between `updateExchangeRateCalls`.
* @dev Callable by OWNER_ROLE.
*/
function updateLower(uint16 allowedExchangeRateChangeLower) external requiresAuth {
if (allowedExchangeRateChangeLower > 1e4) revert AccountantWithRateProviders__LowerBoundTooLarge();
uint16 oldBound = accountantState.allowedExchangeRateChangeLower;
accountantState.allowedExchangeRateChangeLower = allowedExchangeRateChangeLower;
emit LowerBoundUpdated(oldBound, allowedExchangeRateChangeLower);
}
/**
* @notice Update the management fee to a new value.
* @dev Callable by OWNER_ROLE.
*/
function updateManagementFee(uint16 managementFee) external requiresAuth {
if (managementFee > 0.2e4) revert AccountantWithRateProviders__ManagementFeeTooLarge();
uint16 oldFee = accountantState.managementFee;
accountantState.managementFee = managementFee;
emit ManagementFeeUpdated(oldFee, managementFee);
}
/**
* @notice Update the payout address fees are sent to.
* @dev Callable by OWNER_ROLE.
*/
function updatePayoutAddress(address payoutAddress) external requiresAuth {
address oldPayout = accountantState.payoutAddress;
accountantState.payoutAddress = payoutAddress;
emit PayoutAddressUpdated(oldPayout, payoutAddress);
}
/**
* @notice Update the rate provider data for a specific `asset`.
* @dev Rate providers must return rates in terms of `base` or
* an asset pegged to base and they must use the same decimals
* as `asset`.
* @dev Callable by OWNER_ROLE.
*/
function setRateProviderData(ERC20 asset, bool isPeggedToBase, address rateProvider) external requiresAuth {
rateProviderData[asset] =
RateProviderData({ isPeggedToBase: isPeggedToBase, rateProvider: IRateProvider(rateProvider) });
emit RateProviderUpdated(address(asset), isPeggedToBase, rateProvider);
}
// ========================================= UPDATE EXCHANGE RATE/FEES FUNCTIONS
// =========================================
/**
* @notice Updates this contract exchangeRate.
* @dev If new exchange rate is outside of accepted bounds, or if not enough time has passed, this
* will pause the contract, and this function will NOT calculate fees owed.
* @dev Callable by UPDATE_EXCHANGE_RATE_ROLE.
*/
function updateExchangeRate(uint96 newExchangeRate) external requiresAuth {
AccountantState storage state = accountantState;
if (state.isPaused) revert AccountantWithRateProviders__Paused();
uint64 currentTime = uint64(block.timestamp);
uint256 currentExchangeRate = state.exchangeRate;
uint256 currentTotalShares = vault.totalSupply();
if (
currentTime < state.lastUpdateTimestamp + state.minimumUpdateDelayInSeconds
|| newExchangeRate > currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeUpper, 1e4)
|| newExchangeRate < currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeLower, 1e4)
) {
// Instead of reverting, pause the contract. This way the exchange rate updater is able to update the
// exchange rate
// to a better value, and pause it.
state.isPaused = true;
} else {
// Only update fees if we are not paused.
// Update fee accounting.
uint256 shareSupplyToUse = currentTotalShares;
// Use the minimum between current total supply and total supply for last update.
if (state.totalSharesLastUpdate < shareSupplyToUse) {
shareSupplyToUse = state.totalSharesLastUpdate;
}
// Determine management fees owned.
uint256 timeDelta = currentTime - state.lastUpdateTimestamp;
uint256 minimumAssets = newExchangeRate > currentExchangeRate
? shareSupplyToUse.mulDivDown(currentExchangeRate, ONE_SHARE)
: shareSupplyToUse.mulDivDown(newExchangeRate, ONE_SHARE);
uint256 managementFeesAnnual = minimumAssets.mulDivDown(state.managementFee, 1e4);
uint256 newFeesOwedInBase = managementFeesAnnual.mulDivDown(timeDelta, 365 days);
state.feesOwedInBase += uint128(newFeesOwedInBase);
}
state.exchangeRate = newExchangeRate;
state.totalSharesLastUpdate = uint128(currentTotalShares);
state.lastUpdateTimestamp = currentTime;
emit ExchangeRateUpdated(uint96(currentExchangeRate), newExchangeRate, currentTime);
}
/**
* @notice Claim pending fees.
* @dev This function must be called by the BoringVault.
* @dev This function will lose precision if the exchange rate
* decimals is greater than the feeAsset's decimals.
*/
function claimFees(ERC20 feeAsset) external {
if (msg.sender != address(vault)) revert AccountantWithRateProviders__OnlyCallableByBoringVault();
AccountantState storage state = accountantState;
if (state.isPaused) revert AccountantWithRateProviders__Paused();
if (state.feesOwedInBase == 0) revert AccountantWithRateProviders__ZeroFeesOwed();
// Determine amount of fees owed in feeAsset.
uint256 feesOwedInFeeAsset;
RateProviderData memory data = rateProviderData[feeAsset];
if (address(feeAsset) == address(base)) {
feesOwedInFeeAsset = state.feesOwedInBase;
} else {
uint8 feeAssetDecimals = ERC20(feeAsset).decimals();
uint256 feesOwedInBaseUsingFeeAssetDecimals =
changeDecimals(state.feesOwedInBase, decimals, feeAssetDecimals);
if (data.isPeggedToBase) {
feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals;
} else {
uint256 rate = data.rateProvider.getRate();
feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals.mulDivDown(10 ** feeAssetDecimals, rate);
}
}
// Zero out fees owed.
state.feesOwedInBase = 0;
// Transfer fee asset to payout address.
feeAsset.safeTransferFrom(msg.sender, state.payoutAddress, feesOwedInFeeAsset);
emit FeesClaimed(address(feeAsset), feesOwedInFeeAsset);
}
// ========================================= RATE FUNCTIONS =========================================
/**
* @notice Get this BoringVault's current rate in the base.
*/
function getRate() public view returns (uint256 rate) {
rate = accountantState.exchangeRate;
}
/**
* @notice Get this BoringVault's current rate in the base.
* @dev Revert if paused.
*/
function getRateSafe() external view returns (uint256 rate) {
if (accountantState.isPaused) revert AccountantWithRateProviders__Paused();
rate = getRate();
}
/**
* @notice Get this BoringVault's current rate in the provided quote.
* @dev `quote` must have its RateProviderData set, else this will revert.
* @dev This function will lose precision if the exchange rate
* decimals is greater than the quote's decimals.
*/
function getRateInQuote(ERC20 quote) public view returns (uint256 rateInQuote) {
if (address(quote) == address(base)) {
rateInQuote = accountantState.exchangeRate;
} else {
RateProviderData memory data = rateProviderData[quote];
uint8 quoteDecimals = ERC20(quote).decimals();
uint256 exchangeRateInQuoteDecimals = changeDecimals(accountantState.exchangeRate, decimals, quoteDecimals);
if (data.isPeggedToBase) {
rateInQuote = exchangeRateInQuoteDecimals;
} else {
uint256 quoteRate = data.rateProvider.getRate();
uint256 oneQuote = 10 ** quoteDecimals;
rateInQuote = oneQuote.mulDivDown(exchangeRateInQuoteDecimals, quoteRate);
}
}
}
/**
* @notice Get this BoringVault's current rate in the provided quote.
* @dev `quote` must have its RateProviderData set, else this will revert.
* @dev Revert if paused.
*/
function getRateInQuoteSafe(ERC20 quote) external view returns (uint256 rateInQuote) {
if (accountantState.isPaused) revert AccountantWithRateProviders__Paused();
rateInQuote = getRateInQuote(quote);
}
// ========================================= INTERNAL HELPER FUNCTIONS =========================================
/**
* @notice Used to change the decimals of precision used for an amount.
*/
function changeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) {
if (fromDecimals == toDecimals) {
return amount;
} else if (fromDecimals < toDecimals) {
return amount * 10 ** (toDecimals - fromDecimals);
} else {
return amount / 10 ** (fromDecimals - toDecimals);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { FixedPointMathLib } from "@solmate/utils/FixedPointMathLib.sol";
import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { BeforeTransferHook } from "src/interfaces/BeforeTransferHook.sol";
import { Auth, Authority } from "@solmate/auth/Auth.sol";
/**
* @title BoringVault
* @custom:security-contact [email protected]
*/
contract BoringVault is ERC20, Auth, ERC721Holder, ERC1155Holder {
using Address for address;
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
// ========================================= STATE =========================================
/**
* @notice Contract responsible for implementing `beforeTransfer`.
*/
BeforeTransferHook public hook;
//============================== EVENTS ===============================
event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares);
event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares);
//============================== CONSTRUCTOR ===============================
constructor(
address _owner,
string memory _name,
string memory _symbol,
uint8 _decimals
)
ERC20(_name, _symbol, _decimals)
Auth(_owner, Authority(address(0)))
{ }
//============================== MANAGE ===============================
/**
* @notice Allows manager to make an arbitrary function call from this contract.
* @dev Callable by MANAGER_ROLE.
*/
function manage(
address target,
bytes calldata data,
uint256 value
)
external
requiresAuth
returns (bytes memory result)
{
result = target.functionCallWithValue(data, value);
}
/**
* @notice Allows manager to make arbitrary function calls from this contract.
* @dev Callable by MANAGER_ROLE.
*/
function manage(
address[] calldata targets,
bytes[] calldata data,
uint256[] calldata values
)
external
requiresAuth
returns (bytes[] memory results)
{
uint256 targetsLength = targets.length;
results = new bytes[](targetsLength);
for (uint256 i; i < targetsLength; ++i) {
results[i] = targets[i].functionCallWithValue(data[i], values[i]);
}
}
//============================== ENTER ===============================
/**
* @notice Allows minter to mint shares, in exchange for assets.
* @dev If assetAmount is zero, no assets are transferred in.
* @dev Callable by MINTER_ROLE.
*/
function enter(
address from,
ERC20 asset,
uint256 assetAmount,
address to,
uint256 shareAmount
)
external
requiresAuth
{
// Transfer assets in
if (assetAmount > 0) asset.safeTransferFrom(from, address(this), assetAmount);
// Mint shares.
_mint(to, shareAmount);
emit Enter(from, address(asset), assetAmount, to, shareAmount);
}
//============================== EXIT ===============================
/**
* @notice Allows burner to burn shares, in exchange for assets.
* @dev If assetAmount is zero, no assets are transferred out.
* @dev Callable by BURNER_ROLE.
*/
function exit(
address to,
ERC20 asset,
uint256 assetAmount,
address from,
uint256 shareAmount
)
external
requiresAuth
{
// Burn shares.
_burn(from, shareAmount);
// Transfer assets out.
if (assetAmount > 0) asset.safeTransfer(to, assetAmount);
emit Exit(to, address(asset), assetAmount, from, shareAmount);
}
//============================== BEFORE TRANSFER HOOK ===============================
/**
* @notice Sets the share locker.
* @notice If set to zero address, the share locker logic is disabled.
* @dev Callable by OWNER_ROLE.
*/
function setBeforeTransferHook(address _hook) external requiresAuth {
hook = BeforeTransferHook(_hook);
}
/**
* @notice Check if from addresses shares are locked, reverting if so.
*/
function _callBeforeTransfer(address from) internal view {
if (address(hook) != address(0)) hook.beforeTransfer(from);
}
function transfer(address to, uint256 amount) public override returns (bool) {
_callBeforeTransfer(msg.sender);
return super.transfer(to, amount);
}
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
_callBeforeTransfer(from);
return super.transferFrom(from, to, amount);
}
//============================== RECEIVE ===============================
receive() external payable { }
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface BeforeTransferHook {
function beforeTransfer(address from) external view;
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
abstract contract Auth {
event OwnershipTransferred(address indexed user, address indexed newOwner);
event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
address public owner;
Authority public authority;
constructor(address _owner, Authority _authority) {
owner = _owner;
authority = _authority;
emit OwnershipTransferred(msg.sender, _owner);
emit AuthorityUpdated(msg.sender, _authority);
}
modifier requiresAuth() virtual {
require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
_;
}
function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
// Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
// aware that this makes protected functions uncallable even to the owner if the authority is out of order.
return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
}
function setAuthority(Authority newAuthority) public virtual {
// We check if the caller is the owner first because we want to ensure they can
// always swap out the authority even if it's reverting or using up a lot of gas.
require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
authority = newAuthority;
emit AuthorityUpdated(msg.sender, newAuthority);
}
function transferOwnership(address newOwner) public virtual requiresAuth {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
/// @notice A generic interface for a contract which provides authorization data to an Auth instance.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
interface Authority {
function canCall(
address user,
address target,
bytes4 functionSig
) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
// @notice Struct that bundles together a task's parameters for validation
struct Task {
// the unique identifier for the task
string taskId;
// the address of the sender of the task
address msgSender;
// the address of the target contract for the task
address target;
// the value to send with the task
uint256 value;
// the encoded signature and arguments for the task
bytes encodedSigAndArgs;
// the policy ID associated with the task
string policyID;
// the number of signatures required to authorize the task
uint32 quorumThresholdCount;
// the block number by which the task must be executed
uint256 expireByBlockNumber;
}
// @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management.
struct SignatureWithSaltAndExpiry {
// the signature itself, formatted as a single bytes object
bytes signature;
// the salt used to generate the signature
bytes32 salt;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}
/**
* @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer
* @author Predicate Labs, Inc
*/
interface IPredicateManager {
/**
* @notice Sets the metadata URI for the AVS
* @param _metadataURI is the metadata URI for the AVS
*/
function setMetadataURI(
string memory _metadataURI
) external;
/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
* @param operatorSigningKey The address of the operator's signing key.
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
*/
function registerOperatorToAVS(
address operatorSigningKey,
SignatureWithSaltAndExpiry memory operatorSignature
) external;
/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
* @param operator The address of the operator to deregister.
*/
function deregisterOperatorFromAVS(
address operator
) external;
/**
* @notice Returns the list of strategies that the operator has potentially restaked on the AVS
* @param operator The address of the operator to get restaked strategies for
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness
* of each element in the returned array. The off-chain service should do that validation separately
*/
function getOperatorRestakedStrategies(
address operator
) external view returns (address[] memory);
/**
* @notice Returns the list of strategies that the AVS supports for restaking
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on uniqueness of each element in the returned array.
* The off-chain service should do that validation separately
*/
function getRestakeableStrategies() external view returns (address[] memory);
/**
* @notice Sets a policy ID for the sender, defining execution rules or parameters for tasks
* @param policyID string pointing to the policy details
* @dev Only callable by client contracts or EOAs to associate a policy with their address
* @dev Emits a SetPolicy event upon successful association
*/
function setPolicy(
string memory policyID
) external;
/**
* @notice Removes a policy ID for the sender, removing execution rules or parameters for tasks
* @param policyID string pointing to the policy details
* @dev Only callable by client contracts or EOAs to disassociate a policy with their address
* @dev Emits a RemovedPolicy event upon successful association
*/
function removePolicy(
string memory policyID
) external;
/**
* @notice Deploys a policy with ID with execution rules or parameters for tasks
* @param _policyID string pointing to the policy details
* @param _policy string containing the policy details
* @param _quorumThreshold The number of signatures required to authorize a task
* @dev Only callable by service manager deployer
* @dev Emits a DeployedPolicy event upon successful deployment
*/
function deployPolicy(string memory _policyID, string memory _policy, uint256 _quorumThreshold) external;
/**
* @notice Gets array of deployed policies
*/
function getDeployedPolicies() external view returns (string[] memory);
/**
* @notice Deploys a social graph which clients can use in policy
* @param _socialGraphID is a unique identifier
* @param _socialGraphConfig is the config for the social graph
* @dev Only callable by service manager deployer
* @dev Emits a SocialGraphDeployed event upon successful deployment
*/
function deploySocialGraph(string memory _socialGraphID, string memory _socialGraphConfig) external;
/**
* @notice Returns the list of social graph IDs that the AVS supports
*/
function getSocialGraphIDs() external view returns (string[] memory);
/**
* @notice Verifies if a task is authorized by the required number of operators
* @param _task Parameters of the task including sender, target, function signature, arguments, quorum count, and expiry block
* @param signerAddresses Array of addresses of the operators who signed the task
* @param signatures Array of signatures from the operators authorizing the task
* @return isVerified Boolean indicating if the task has been verified by the required number of operators
* @dev This function checks the signatures against the hash of the task parameters to ensure task authenticity and authorization
*/
function validateSignatures(
Task memory _task,
address[] memory signerAddresses,
bytes[] memory signatures
) external returns (bool isVerified);
/**
* @notice Adds a new strategy to the Service Manager
* @dev Only callable by the contract owner. Adds a strategy that operators can stake on.
* @param _strategy The address of the strategy contract to add
* @param quorumNumber The quorum number associated with the strategy
* @param index The index of the strategy within the quorum
* @dev Emits a StrategyAdded event upon successful addition of the strategy
* @dev Reverts if the strategy does not exist or is already added
*/
function addStrategy(address _strategy, uint8 quorumNumber, uint256 index) external;
/**
* @notice Removes an existing strategy from the Service Manager
* @dev Only callable by the contract owner. Removes a strategy that operators are currently able to stake on.
* @param _strategy The address of the strategy contract to remove
* @dev Emits a StrategyRemoved event upon successful removal of the strategy
* @dev Reverts if the strategy is not currently added or if the address is invalid
*/
function removeStrategy(
address _strategy
) external;
/**
* @notice Enables the rotation of Predicate Signing Key for an operator
* @param _oldSigningKey address of the old signing key to remove
* @param _newSigningKey address of the new signing key to add
*/
function rotatePredicateSigningKey(address _oldSigningKey, address _newSigningKey) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import {IPredicateManager, Task} from "../interfaces/IPredicateManager.sol";
import "../interfaces/IPredicateClient.sol";
abstract contract PredicateClient is IPredicateClient {
// @notice the storage slot for the PredicateClientStorage struct
// @dev keccak256(abi.encode(uint256(keccak256("predicate.storage.PredicateClient")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant _PREDICATE_CLIENT_STORAGE_SLOT =
0x804776a84f3d03ad8442127b1451e2fbbb6a715c681d6a83c9e9fca787b99300;
// @notice retrieves the PredicateClientStorage struct from the configured storage slot
function _getPredicateClientStorage() private pure returns (PredicateClientStorage storage $) {
assembly {
$.slot := _PREDICATE_CLIENT_STORAGE_SLOT
}
}
/**
* @notice Sets a policy and serviceManager for the predicate client.
* @param _serviceManagerAddress Address of the associated PredicateManager contract.
* @param _policyID A string representing the predicate policyID.
* @dev This function enables clients to define execution rules or parameters for tasks they submit.
* The policy governs how tasks submitted by the caller are executed, ensuring compliance with predefined rules.
*/
function _initPredicateClient(address _serviceManagerAddress, string memory _policyID) internal {
PredicateClientStorage storage $ = _getPredicateClientStorage();
$.serviceManager = IPredicateManager(_serviceManagerAddress);
$.policyID = _policyID;
}
// @notice internal function to set the policyID
function _setPolicy(
string memory _policyID
) internal {
PredicateClientStorage storage $ = _getPredicateClientStorage();
$.policyID = _policyID;
}
// @inheritdoc IPredicateClient
function getPolicy() external view override returns (string memory) {
return _getPolicy();
}
// @notice internal function to get the policyID from PredicateClientStorage
function _getPolicy() internal view returns (string memory) {
PredicateClientStorage storage $ = _getPredicateClientStorage();
return $.policyID;
}
// @notice internal function to set the Predicate ServiceManager
function _setPredicateManager(
address _predicateManager
) internal {
PredicateClientStorage storage $ = _getPredicateClientStorage();
$.serviceManager = IPredicateManager(_predicateManager);
}
// @inheritdoc IPredicateClient
function getPredicateManager() external view override returns (address) {
return _getPredicateManager();
}
// @notice internal function to get the Predicate ServiceManager address from PredicateClientStorage
function _getPredicateManager() internal view returns (address) {
PredicateClientStorage storage $ = _getPredicateClientStorage();
return address($.serviceManager);
}
/**
* @notice Restricts access to the Predicate ServiceManager
*/
modifier onlyPredicateServiceManager() {
PredicateClientStorage storage $ = _getPredicateClientStorage();
if (msg.sender != address($.serviceManager)) {
revert PredicateClient__Unauthorized();
}
_;
}
function _authorizeTransaction(
PredicateMessage memory _predicateMessage,
bytes memory _encodedSigAndArgs,
address _msgSender,
uint256 _value
) internal returns (bool) {
PredicateClientStorage storage $ = _getPredicateClientStorage();
Task memory task = Task({
msgSender: _msgSender,
target: address(this),
value: _value,
encodedSigAndArgs: _encodedSigAndArgs,
policyID: $.policyID,
quorumThresholdCount: uint32(_predicateMessage.signerAddresses.length),
taskId: _predicateMessage.taskId,
expireByBlockNumber: _predicateMessage.expireByBlockNumber
});
return
$.serviceManager.validateSignatures(task, _predicateMessage.signerAddresses, _predicateMessage.signatures);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.8.0;
interface IRateProvider {
function getRate() external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert Errors.FailedCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.20;
import {IERC721Receiver} from "../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
* {IERC721-setApprovalForAll}.
*/
abstract contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
return this.onERC721Received.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol)
pragma solidity ^0.8.20;
import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
import {IERC1155Receiver} from "../IERC1155Receiver.sol";
/**
* @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens.
*
* IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
* stuck.
*/
abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @title ERC-721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC-721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Interface that must be implemented by smart contracts in order to receive
* ERC-1155 token transfers.
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC-1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC-1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}{
"remappings": [
"@solmate/=lib/solmate/src/",
"@forge-std/=lib/forge-std/src/",
"forge-std/=lib/forge-std/src/",
"@ds-test/=lib/forge-std/lib/ds-test/src/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"@ion-protocol/=lib/ion-protocol/src/",
"@layerzerolabs/=node_modules/@layerzerolabs/",
"@predicate/=lib/predicate-contracts/",
"@uniswap/v3-core/=lib/v3-core/",
"@axelar-network/=node_modules/@axelar-network/",
"@balancer-labs/v2-interfaces/=lib/ion-protocol/lib/balancer-v2-monorepo/pkg/interfaces/",
"@balancer-labs/v2-pool-stable/=lib/ion-protocol/lib/balancer-v2-monorepo/pkg/pool-stable/",
"@chainlink/=node_modules/@chainlink/",
"@chainlink/contracts/=lib/ion-protocol/lib/chainlink/contracts/",
"@eth-optimism/=node_modules/@eth-optimism/",
"@openzeppelin-upgrades-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/",
"@openzeppelin-upgrades/=lib/predicate-contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/",
"@openzeppelin-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/",
"@openzeppelin/contracts-upgradeable/=lib/ion-protocol/lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@uniswap/v3-periphery/=lib/predicate-contracts/lib/v3-periphery/",
"balancer-v2-monorepo/=lib/ion-protocol/lib/",
"chainlink/=lib/ion-protocol/lib/chainlink/",
"createx/=lib/createx/src/",
"eigenlayer-contracts/=lib/predicate-contracts/lib/eigenlayer-contracts/",
"eigenlayer-middleware/=lib/predicate-contracts/lib/eigenlayer-middleware/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-safe/=lib/ion-protocol/lib/forge-safe/",
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
"hardhat-deploy/=node_modules/hardhat-deploy/",
"ion-protocol/=lib/ion-protocol/",
"openzeppelin-contracts-upgradeable-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/",
"openzeppelin-contracts-upgradeable/=lib/predicate-contracts/lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts-v4.9.0/=lib/predicate-contracts/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin-upgradeable/=lib/predicate-contracts/lib/openzeppelin-contracts-upgradeable/contracts/",
"openzeppelin/=lib/createx/lib/openzeppelin-contracts/contracts/",
"pancake-v3-contracts/=lib/pancake-v3-contracts/",
"pendle-core-v2-public/=lib/ion-protocol/lib/pendle-core-v2-public/contracts/",
"predicate-contracts/=lib/predicate-contracts/src/",
"solady/=lib/ion-protocol/lib/solady/",
"solarray/=lib/ion-protocol/lib/solarray/src/",
"solidity-bytes-utils/=node_modules/solidity-bytes-utils/",
"solidity-stringutils/=lib/ion-protocol/lib/forge-safe/lib/surl/lib/solidity-stringutils/",
"solmate/=lib/solmate/src/",
"surl/=lib/ion-protocol/lib/forge-safe/lib/surl/",
"utils/=lib/predicate-contracts/lib/utils/",
"v3-core/=lib/v3-core/",
"v3-periphery/=lib/v3-periphery/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "shanghai",
"viaIR": false,
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"contract AggregationRouterV6","name":"_aggregator","type":"address"},{"internalType":"contract IOKXRouter","name":"_okxRouter","type":"address"},{"internalType":"address","name":"_okxApprover","type":"address"},{"internalType":"contract WETH","name":"_canonicalWrapToken","type":"address"},{"internalType":"contract TellerWithMultiAssetSupportPredicateProxy","name":"_predicateProxy","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DexAggregatorWrapper__EthRefundFailed","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__InsufficientEthForSwap","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__InvalidFromToken","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__InvalidOkxSwapDescription","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__InvalidSwapDescription","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__OkxSwapFailed","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__PredicateUnauthorizedTransaction","type":"error"},{"inputs":[],"name":"DexAggregatorWrapper__UnsupportedOkxFunction","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositAsset","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"supportedAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"supportedAssetAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shareAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"teller","type":"address"},{"indexed":false,"internalType":"address","name":"vaultAddress","type":"address"}],"name":"Deposit","type":"event"},{"inputs":[{"internalType":"contract ERC20","name":"supportedAsset","type":"address"},{"internalType":"contract CrossChainTellerBase","name":"teller","type":"address"},{"internalType":"uint256","name":"minimumMint","type":"uint256"},{"components":[{"internalType":"uint32","name":"chainSelector","type":"uint32"},{"internalType":"address","name":"destinationChainReceiver","type":"address"},{"internalType":"contract ERC20","name":"bridgeFeeToken","type":"address"},{"internalType":"uint64","name":"messageGas","type":"uint64"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct BridgeData","name":"bridgeData","type":"tuple"},{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"uint256","name":"fromTokenAmount","type":"uint256"},{"internalType":"bytes","name":"okxCallData","type":"bytes"},{"internalType":"uint256","name":"nativeValueToWrap","type":"uint256"},{"components":[{"internalType":"string","name":"taskId","type":"string"},{"internalType":"uint256","name":"expireByBlockNumber","type":"uint256"},{"internalType":"address[]","name":"signerAddresses","type":"address[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"}],"internalType":"struct PredicateMessage","name":"predicateMessage","type":"tuple"}],"name":"depositAndBridgeOkxUniversal","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"supportedAsset","type":"address"},{"internalType":"contract CrossChainTellerBase","name":"teller","type":"address"},{"internalType":"uint256","name":"minimumMint","type":"uint256"},{"components":[{"internalType":"uint32","name":"chainSelector","type":"uint32"},{"internalType":"address","name":"destinationChainReceiver","type":"address"},{"internalType":"contract ERC20","name":"bridgeFeeToken","type":"address"},{"internalType":"uint64","name":"messageGas","type":"uint64"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct BridgeData","name":"bridgeData","type":"tuple"},{"internalType":"address","name":"executor","type":"address"},{"components":[{"internalType":"contract ERC20","name":"srcToken","type":"address"},{"internalType":"contract ERC20","name":"dstToken","type":"address"},{"internalType":"address payable","name":"srcReceiver","type":"address"},{"internalType":"address payable","name":"dstReceiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturnAmount","type":"uint256"},{"internalType":"uint256","name":"flags","type":"uint256"}],"internalType":"struct AggregationRouterV6.SwapDescription","name":"desc","type":"tuple"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"nativeValueToWrap","type":"uint256"},{"components":[{"internalType":"string","name":"taskId","type":"string"},{"internalType":"uint256","name":"expireByBlockNumber","type":"uint256"},{"internalType":"address[]","name":"signerAddresses","type":"address[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"}],"internalType":"struct PredicateMessage","name":"predicateMessage","type":"tuple"}],"name":"depositAndBridgeOneInch","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"supportedAsset","type":"address"},{"internalType":"contract TellerWithMultiAssetSupport","name":"teller","type":"address"},{"internalType":"uint256","name":"minimumMint","type":"uint256"},{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"uint256","name":"fromTokenAmount","type":"uint256"},{"internalType":"bytes","name":"okxCallData","type":"bytes"},{"internalType":"uint256","name":"nativeValueToWrap","type":"uint256"},{"components":[{"internalType":"string","name":"taskId","type":"string"},{"internalType":"uint256","name":"expireByBlockNumber","type":"uint256"},{"internalType":"address[]","name":"signerAddresses","type":"address[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"}],"internalType":"struct PredicateMessage","name":"predicateMessage","type":"tuple"}],"name":"depositOkxUniversal","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"supportedAsset","type":"address"},{"internalType":"contract TellerWithMultiAssetSupport","name":"teller","type":"address"},{"internalType":"uint256","name":"minimumMint","type":"uint256"},{"internalType":"address","name":"executor","type":"address"},{"components":[{"internalType":"contract ERC20","name":"srcToken","type":"address"},{"internalType":"contract ERC20","name":"dstToken","type":"address"},{"internalType":"address payable","name":"srcReceiver","type":"address"},{"internalType":"address payable","name":"dstReceiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturnAmount","type":"uint256"},{"internalType":"uint256","name":"flags","type":"uint256"}],"internalType":"struct AggregationRouterV6.SwapDescription","name":"desc","type":"tuple"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"nativeValueToWrap","type":"uint256"},{"components":[{"internalType":"string","name":"taskId","type":"string"},{"internalType":"uint256","name":"expireByBlockNumber","type":"uint256"},{"internalType":"address[]","name":"signerAddresses","type":"address[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"}],"internalType":"struct PredicateMessage","name":"predicateMessage","type":"tuple"}],"name":"depositOneInch","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
61012060405260015f5534801562000015575f80fd5b5060405162001da238038062001da283398101604081905262000038916200007a565b6001600160a01b0394851660805292841660a05290831660c052821660e0521661010052620000f6565b6001600160a01b038116811462000077575f80fd5b50565b5f805f805f60a086880312156200008f575f80fd5b85516200009c8162000062565b6020870151909550620000af8162000062565b6040870151909450620000c28162000062565b6060870151909350620000d58162000062565b6080870151909250620000e88162000062565b809150509295509295909350565b60805160a05160c05160e05161010051611c3d620001655f395f61055c01525f81816106aa0152818161074d01528181610e0701528181610eb201526110ed01525f818161071e01526107e201525f61080d01525f8181610e8301528181610f5b0152610f9a0152611c3d5ff3fe608060405260043610610041575f3560e01c80630d598ef91461004c5780634add5061146100615780637e3eb8b214610086578063acb9669a14610099575f80fd5b3661004857005b5f80fd5b61005f61005a36600461131e565b6100ac565b005b61007461006f3660046113f3565b610186565b60405190815260200160405180910390f35b6100746100943660046114b9565b6102ff565b61005f6100a736600461155d565b610475565b5f546001146100d65760405162461bcd60e51b81526004016100cd9061161d565b60405180910390fd5b60025f556100e381610545565b5f6100f38b8b89898989896105f3565b90506001600160a01b038a1663bfe1a0f261010e8534611655565b8d848d8d6040518663ffffffff1660e01b815260040161013194939291906116ef565b5f604051808303818588803b158015610148575f80fd5b505af115801561015a573d5f803e3d5ffd5b505050505061016833610976565b6101758b8b8989856109f3565b505060015f55505050505050505050565b5f80546001146101a85760405162461bcd60e51b81526004016100cd9061161d565b60025f556101b582610545565b5f6101c58b8b8a8a8a8a8a6105f3565b604051630efe6a8b60e01b81526001600160a01b038d8116600483015260248201839052604482018c9052919250908b1690630efe6a8b906064016020604051808303815f875af115801561021c573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061024091906117a1565b91505f8a6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561027f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102a391906117b8565b90506001600160a01b0381166102cb5760405162461bcd60e51b81526004016100cd906117da565b6102df6001600160a01b0382163385610d01565b6102ec8c8c8b8b866109f3565b505060015f559998505050505050505050565b5f80546001146103215760405162461bcd60e51b81526004016100cd9061161d565b60025f5561032e82610545565b5f61033e8b8b8a8a8a8a8a610d84565b604051630efe6a8b60e01b81526001600160a01b038d8116600483015260248201839052604482018c9052919250908b1690630efe6a8b906064016020604051808303815f875af1158015610395573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b991906117a1565b91505f8a6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103f8573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061041c91906117b8565b90506001600160a01b0381166104445760405162461bcd60e51b81526004016100cd906117da565b6104586001600160a01b0382163385610d01565b6102ec8c8c61046a60208c018c611825565b8b60800135866109f3565b5f546001146104965760405162461bcd60e51b81526004016100cd9061161d565b60025f556104a381610545565b5f6104b38b8b8989898989610d84565b90506001600160a01b038a1663bfe1a0f26104ce8534611655565b8d848d8d6040518663ffffffff1660e01b81526004016104f194939291906116ef565b5f604051808303818588803b158015610508575f80fd5b505af115801561051a573d5f803e3d5ffd5b505050505061052833610976565b6101758b8b61053a60208a018a611825565b8960800135856109f3565b60405163445a396760e11b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906388b472ce9061059390339085906004016118e0565b6020604051808303815f875af11580156105af573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105d391906119a3565b6105f057604051631d32c93b60e31b815260040160405180910390fd5b50565b5f83356001600160e01b0319811663b80c2f0960e01b148061062557506001600160e01b031981166303b87e5f60e01b145b8061064057506001600160e01b03198116630d5f0e3b60e01b145b8061065b57506001600160e01b031981166379f0a25b60e11b145b8061067657506001600160e01b0319811663261c7be960e21b145b8061069157506001600160e01b03198116630414c5ad60e11b145b15610952575f6106a0846110c3565b905080156107bd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316886001600160a01b03161415806106e95750838714155b1561070757604051630734d5e960e21b815260040160405180910390fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018690527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303815f875af1158015610793573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107b791906119a3565b50610809565b876107d36001600160a01b03821633308b611165565b6108076001600160a01b0382167f00000000000000000000000000000000000000000000000000000000000000008a6111fd565b505b5f807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031688886040516108459291906119c2565b5f604051808303815f865af19150503d805f811461087e576040519150601f19603f3d011682016040523d82523d5f602084013e610883565b606091505b50915091508161089557805160208201fd5b808060200190518101906108a991906117a1565b94505f8b6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108e8573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061090c91906117b8565b90506001600160a01b0381166109345760405162461bcd60e51b81526004016100cd906119d1565b6109486001600160a01b038e1682886111fd565b505050505061096b565b604051633a136c6360e21b815260040160405180910390fd5b979650505050505050565b4780156109ef575f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146109c6576040519150601f19603f3d011682016040523d82523d5f602084013e6109cb565b606091505b50509050806109ed5760405163cd7587ff60e01b815260040160405180910390fd5b505b5050565b5f846001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5491906117b8565b90506001600160a01b038116610a7c5760405162461bcd60e51b81526004016100cd906117da565b5f610c1c866001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610abc573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ae091906117b8565b6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b1b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b3f9190611a2e565b610b4a90600a611b2e565b876001600160a01b0316634fb3ccc56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b86573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610baa91906117b8565b604051634104b9ed60e11b81526001600160a01b038b81166004830152919091169063820973da90602401602060405180830381865afa158015610bf0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c1491906117a1565b859190611279565b9050866001600160a01b0316336001600160a01b0316866001600160a01b03167f84626c98b43b4838f8ac8b7fc3b6bd913fd3f36c297c7f3da5eeb7b2528ce81c8787868c8d6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c9d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cc191906117b8565b604080519586526020860194909452928401919091526001600160a01b03908116606084015216608082015260a00160405180910390a450505050505050565b5f60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610d7e5760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b60448201526064016100cd565b50505050565b5f80610d8f836110c3565b90506001600160a01b038916610dab6040880160208901611825565b6001600160a01b0316141580610dd9575030610dcd6080880160608901611825565b6001600160a01b031614155b15610df7576040516347cc1c0160e01b815260040160405180910390fd5b8015610f22576001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016610e346020880188611825565b6001600160a01b0316141580610e4e575082866080013514155b15610e6c576040516347cc1c0160e01b815260040160405180910390fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018590527f0000000000000000000000000000000000000000000000000000000000000000169063095ea7b3906044016020604051808303815f875af1158015610ef8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1c91906119a3565b50610f83565b5f610f306020880188611825565b90506080870135610f4c6001600160a01b038316333084611165565b610f806001600160a01b0383167f0000000000000000000000000000000000000000000000000000000000000000836111fd565b50505b6040516307ed237960e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906307ed237990610fd5908a908a908a908a90600401611b3c565b60408051808303815f875af1158015610ff0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110149190611be5565b50809250505f886001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611056573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061107a91906117b8565b90506001600160a01b0381166110a25760405162461bcd60e51b81526004016100cd906119d1565b6110b66001600160a01b038b1682856111fd565b5050979650505050505050565b5f348211156110e55760405163d8d6ea2d60e01b815260040160405180910390fd5b8115611160577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004015f604051808303818588803b158015611144575f80fd5b505af1158015611156573d5f803e3d5ffd5b5050505050600190505b919050565b5f6040516323b872dd60e01b81526001600160a01b03851660048201526001600160a01b038416602482015282604482015260205f6064835f8a5af13d15601f3d1160015f5114161716915050806111f65760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b60448201526064016100cd565b5050505050565b5f60405163095ea7b360e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610d7e5760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b60448201526064016100cd565b5f825f19048411830215820261128d575f80fd5b5091020490565b6001600160a01b03811681146105f0575f80fd5b803561116081611294565b5f60a082840312156112c3575f80fd5b50919050565b5f8083601f8401126112d9575f80fd5b50813567ffffffffffffffff8111156112f0575f80fd5b602083019150836020828501011115611307575f80fd5b9250929050565b5f608082840312156112c3575f80fd5b5f805f805f805f805f806101208b8d031215611338575f80fd5b6113418b6112a8565b995061134f60208c016112a8565b985060408b0135975060608b013567ffffffffffffffff80821115611372575f80fd5b61137e8e838f016112b3565b985061138c60808e016112a8565b975060a08d0135965060c08d01359150808211156113a8575f80fd5b6113b48e838f016112c9565b909650945060e08d013593506101008d01359150808211156113d4575f80fd5b506113e18d828e0161130e565b9150509295989b9194979a5092959850565b5f805f805f805f805f6101008a8c03121561140c575f80fd5b893561141781611294565b985060208a013561142781611294565b975060408a0135965060608a013561143e81611294565b955060808a0135945060a08a013567ffffffffffffffff80821115611461575f80fd5b61146d8d838e016112c9565b909650945060c08c0135935060e08c013591508082111561148c575f80fd5b506114998c828d0161130e565b9150509295985092959850929598565b5f60e082840312156112c3575f80fd5b5f805f805f805f805f6101c08a8c0312156114d2575f80fd5b89356114dd81611294565b985060208a01356114ed81611294565b975060408a0135965060608a013561150481611294565b95506115138b60808c016114a9565b94506101608a013567ffffffffffffffff80821115611530575f80fd5b61153c8d838e016112c9565b90965094506101808c013593506101a08c013591508082111561148c575f80fd5b5f805f805f805f805f806101e08b8d031215611577575f80fd5b6115808b6112a8565b995061158e60208c016112a8565b985060408b0135975060608b013567ffffffffffffffff808211156115b1575f80fd5b6115bd8e838f016112b3565b98506115cb60808e016112a8565b97506115da8e60a08f016114a9565b96506101808d01359150808211156115f0575f80fd5b6115fc8e838f016112c9565b90965094506101a08d013593506101c08d01359150808211156113d4575f80fd5b6020808252600a90820152695245454e5452414e435960b01b604082015260600190565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561166857611668611641565b92915050565b803567ffffffffffffffff81168114611160575f80fd5b5f808335601e1984360301811261169a575f80fd5b830160208101925035905067ffffffffffffffff8111156116b9575f80fd5b803603821315611307575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b0385811682526020820185905260408201849052608060608301525f90833563ffffffff8116808214611727575f80fd5b608085015250602084013561173b81611294565b811660a0840152604084013561175081611294565b1660c08301526117626060840161166e565b67ffffffffffffffff1660e083015261177e6080840184611685565b60a0610100850152611795610120850182846116c7565b98975050505050505050565b5f602082840312156117b1575f80fd5b5051919050565b5f602082840312156117c8575f80fd5b81516117d381611294565b9392505050565b6020808252602b908201527f44657841676772656761746f72577261707065723a20496e76616c696420766160408201526a756c74206164647265737360a81b606082015260800190565b5f60208284031215611835575f80fd5b81356117d381611294565b5f808335601e19843603018112611855575f80fd5b830160208101925035905067ffffffffffffffff811115611874575f80fd5b8060051b3603821315611307575f80fd5b8183525f6020808501808196508560051b81019150845f5b878110156118d35782840389526118b48288611685565b6118bf8682846116c7565b9a87019a955050509084019060010161189d565b5091979650505050505050565b5f60018060a01b03808516835260206040818501526118ff8586611685565b6080604087015261191460c0870182846116c7565b91505081860135606086015261192d6040870187611840565b603f198784038101608089015281845291925f929085015b8284101561196e57843561195881611294565b8716815293850193600193909301928501611945565b61197b60608b018b611840565b97509550818982030160a08a0152611994818888611885565b9b9a5050505050505050505050565b5f602082840312156119b3575f80fd5b815180151581146117d3575f80fd5b818382375f9101908152919050565b60208082526038908201527f44657841676772656761746f72577261707065723a20496e76616c696420766160408201527f756c74206164647265737320666f7220617070726f76616c0000000000000000606082015260800190565b5f60208284031215611a3e575f80fd5b815160ff811681146117d3575f80fd5b600181815b80851115611a8857815f1904821115611a6e57611a6e611641565b80851615611a7b57918102915b93841c9390800290611a53565b509250929050565b5f82611a9e57506001611668565b81611aaa57505f611668565b8160018114611ac05760028114611aca57611ae6565b6001915050611668565b60ff841115611adb57611adb611641565b50506001821b611668565b5060208310610133831016604e8410600b8410161715611b09575081810a611668565b611b138383611a4e565b805f1904821115611b2657611b26611641565b029392505050565b5f6117d360ff841683611a90565b6001600160a01b0385811682525f90610120908635611b5a81611294565b8181166020860152506020870135611b7181611294565b16604084810191909152860135611b8781611294565b6001600160a01b038116606085015250611ba3606087016112a8565b6001600160a01b038116608085015250608086013560a084015260a086013560c084015260c086013560e08401528061010084015261096b81840185876116c7565b5f8060408385031215611bf6575f80fd5b50508051602090910151909290915056fea264697066735822122091c9005b309c8e51a94b2ed9d65a7dda60953667b2c1f0102c7114e90c18993f64736f6c63430008150033000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000007d0ccaa3fac1e5a943c5168b6ced828691b46b3600000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006104fe10ca937a086ba7adbd0910a4733d380cb6
Deployed Bytecode
0x608060405260043610610041575f3560e01c80630d598ef91461004c5780634add5061146100615780637e3eb8b214610086578063acb9669a14610099575f80fd5b3661004857005b5f80fd5b61005f61005a36600461131e565b6100ac565b005b61007461006f3660046113f3565b610186565b60405190815260200160405180910390f35b6100746100943660046114b9565b6102ff565b61005f6100a736600461155d565b610475565b5f546001146100d65760405162461bcd60e51b81526004016100cd9061161d565b60405180910390fd5b60025f556100e381610545565b5f6100f38b8b89898989896105f3565b90506001600160a01b038a1663bfe1a0f261010e8534611655565b8d848d8d6040518663ffffffff1660e01b815260040161013194939291906116ef565b5f604051808303818588803b158015610148575f80fd5b505af115801561015a573d5f803e3d5ffd5b505050505061016833610976565b6101758b8b8989856109f3565b505060015f55505050505050505050565b5f80546001146101a85760405162461bcd60e51b81526004016100cd9061161d565b60025f556101b582610545565b5f6101c58b8b8a8a8a8a8a6105f3565b604051630efe6a8b60e01b81526001600160a01b038d8116600483015260248201839052604482018c9052919250908b1690630efe6a8b906064016020604051808303815f875af115801561021c573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061024091906117a1565b91505f8a6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561027f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102a391906117b8565b90506001600160a01b0381166102cb5760405162461bcd60e51b81526004016100cd906117da565b6102df6001600160a01b0382163385610d01565b6102ec8c8c8b8b866109f3565b505060015f559998505050505050505050565b5f80546001146103215760405162461bcd60e51b81526004016100cd9061161d565b60025f5561032e82610545565b5f61033e8b8b8a8a8a8a8a610d84565b604051630efe6a8b60e01b81526001600160a01b038d8116600483015260248201839052604482018c9052919250908b1690630efe6a8b906064016020604051808303815f875af1158015610395573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b991906117a1565b91505f8a6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103f8573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061041c91906117b8565b90506001600160a01b0381166104445760405162461bcd60e51b81526004016100cd906117da565b6104586001600160a01b0382163385610d01565b6102ec8c8c61046a60208c018c611825565b8b60800135866109f3565b5f546001146104965760405162461bcd60e51b81526004016100cd9061161d565b60025f556104a381610545565b5f6104b38b8b8989898989610d84565b90506001600160a01b038a1663bfe1a0f26104ce8534611655565b8d848d8d6040518663ffffffff1660e01b81526004016104f194939291906116ef565b5f604051808303818588803b158015610508575f80fd5b505af115801561051a573d5f803e3d5ffd5b505050505061052833610976565b6101758b8b61053a60208a018a611825565b8960800135856109f3565b60405163445a396760e11b81526001600160a01b037f0000000000000000000000006104fe10ca937a086ba7adbd0910a4733d380cb616906388b472ce9061059390339085906004016118e0565b6020604051808303815f875af11580156105af573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105d391906119a3565b6105f057604051631d32c93b60e31b815260040160405180910390fd5b50565b5f83356001600160e01b0319811663b80c2f0960e01b148061062557506001600160e01b031981166303b87e5f60e01b145b8061064057506001600160e01b03198116630d5f0e3b60e01b145b8061065b57506001600160e01b031981166379f0a25b60e11b145b8061067657506001600160e01b0319811663261c7be960e21b145b8061069157506001600160e01b03198116630414c5ad60e11b145b15610952575f6106a0846110c3565b905080156107bd577f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316886001600160a01b03161415806106e95750838714155b1561070757604051630734d5e960e21b815260040160405180910390fd5b60405163095ea7b360e01b81526001600160a01b037f00000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7f81166004830152602482018690527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2169063095ea7b3906044016020604051808303815f875af1158015610793573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107b791906119a3565b50610809565b876107d36001600160a01b03821633308b611165565b6108076001600160a01b0382167f00000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7f8a6111fd565b505b5f807f0000000000000000000000007d0ccaa3fac1e5a943c5168b6ced828691b46b366001600160a01b031688886040516108459291906119c2565b5f604051808303815f865af19150503d805f811461087e576040519150601f19603f3d011682016040523d82523d5f602084013e610883565b606091505b50915091508161089557805160208201fd5b808060200190518101906108a991906117a1565b94505f8b6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108e8573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061090c91906117b8565b90506001600160a01b0381166109345760405162461bcd60e51b81526004016100cd906119d1565b6109486001600160a01b038e1682886111fd565b505050505061096b565b604051633a136c6360e21b815260040160405180910390fd5b979650505050505050565b4780156109ef575f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146109c6576040519150601f19603f3d011682016040523d82523d5f602084013e6109cb565b606091505b50509050806109ed5760405163cd7587ff60e01b815260040160405180910390fd5b505b5050565b5f846001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5491906117b8565b90506001600160a01b038116610a7c5760405162461bcd60e51b81526004016100cd906117da565b5f610c1c866001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610abc573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ae091906117b8565b6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b1b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b3f9190611a2e565b610b4a90600a611b2e565b876001600160a01b0316634fb3ccc56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b86573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610baa91906117b8565b604051634104b9ed60e11b81526001600160a01b038b81166004830152919091169063820973da90602401602060405180830381865afa158015610bf0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c1491906117a1565b859190611279565b9050866001600160a01b0316336001600160a01b0316866001600160a01b03167f84626c98b43b4838f8ac8b7fc3b6bd913fd3f36c297c7f3da5eeb7b2528ce81c8787868c8d6001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c9d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cc191906117b8565b604080519586526020860194909452928401919091526001600160a01b03908116606084015216608082015260a00160405180910390a450505050505050565b5f60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610d7e5760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b60448201526064016100cd565b50505050565b5f80610d8f836110c3565b90506001600160a01b038916610dab6040880160208901611825565b6001600160a01b0316141580610dd9575030610dcd6080880160608901611825565b6001600160a01b031614155b15610df7576040516347cc1c0160e01b815260040160405180910390fd5b8015610f22576001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216610e346020880188611825565b6001600160a01b0316141580610e4e575082866080013514155b15610e6c576040516347cc1c0160e01b815260040160405180910390fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000111111125421ca6dc452d289314280a0f8842a6581166004830152602482018590527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2169063095ea7b3906044016020604051808303815f875af1158015610ef8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1c91906119a3565b50610f83565b5f610f306020880188611825565b90506080870135610f4c6001600160a01b038316333084611165565b610f806001600160a01b0383167f000000000000000000000000111111125421ca6dc452d289314280a0f8842a65836111fd565b50505b6040516307ed237960e01b81526001600160a01b037f000000000000000000000000111111125421ca6dc452d289314280a0f8842a6516906307ed237990610fd5908a908a908a908a90600401611b3c565b60408051808303815f875af1158015610ff0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110149190611be5565b50809250505f886001600160a01b031663fbfa77cf6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611056573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061107a91906117b8565b90506001600160a01b0381166110a25760405162461bcd60e51b81526004016100cd906119d1565b6110b66001600160a01b038b1682856111fd565b5050979650505050505050565b5f348211156110e55760405163d8d6ea2d60e01b815260040160405180910390fd5b8115611160577f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004015f604051808303818588803b158015611144575f80fd5b505af1158015611156573d5f803e3d5ffd5b5050505050600190505b919050565b5f6040516323b872dd60e01b81526001600160a01b03851660048201526001600160a01b038416602482015282604482015260205f6064835f8a5af13d15601f3d1160015f5114161716915050806111f65760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b60448201526064016100cd565b5050505050565b5f60405163095ea7b360e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080610d7e5760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b60448201526064016100cd565b5f825f19048411830215820261128d575f80fd5b5091020490565b6001600160a01b03811681146105f0575f80fd5b803561116081611294565b5f60a082840312156112c3575f80fd5b50919050565b5f8083601f8401126112d9575f80fd5b50813567ffffffffffffffff8111156112f0575f80fd5b602083019150836020828501011115611307575f80fd5b9250929050565b5f608082840312156112c3575f80fd5b5f805f805f805f805f806101208b8d031215611338575f80fd5b6113418b6112a8565b995061134f60208c016112a8565b985060408b0135975060608b013567ffffffffffffffff80821115611372575f80fd5b61137e8e838f016112b3565b985061138c60808e016112a8565b975060a08d0135965060c08d01359150808211156113a8575f80fd5b6113b48e838f016112c9565b909650945060e08d013593506101008d01359150808211156113d4575f80fd5b506113e18d828e0161130e565b9150509295989b9194979a5092959850565b5f805f805f805f805f6101008a8c03121561140c575f80fd5b893561141781611294565b985060208a013561142781611294565b975060408a0135965060608a013561143e81611294565b955060808a0135945060a08a013567ffffffffffffffff80821115611461575f80fd5b61146d8d838e016112c9565b909650945060c08c0135935060e08c013591508082111561148c575f80fd5b506114998c828d0161130e565b9150509295985092959850929598565b5f60e082840312156112c3575f80fd5b5f805f805f805f805f6101c08a8c0312156114d2575f80fd5b89356114dd81611294565b985060208a01356114ed81611294565b975060408a0135965060608a013561150481611294565b95506115138b60808c016114a9565b94506101608a013567ffffffffffffffff80821115611530575f80fd5b61153c8d838e016112c9565b90965094506101808c013593506101a08c013591508082111561148c575f80fd5b5f805f805f805f805f806101e08b8d031215611577575f80fd5b6115808b6112a8565b995061158e60208c016112a8565b985060408b0135975060608b013567ffffffffffffffff808211156115b1575f80fd5b6115bd8e838f016112b3565b98506115cb60808e016112a8565b97506115da8e60a08f016114a9565b96506101808d01359150808211156115f0575f80fd5b6115fc8e838f016112c9565b90965094506101a08d013593506101c08d01359150808211156113d4575f80fd5b6020808252600a90820152695245454e5452414e435960b01b604082015260600190565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561166857611668611641565b92915050565b803567ffffffffffffffff81168114611160575f80fd5b5f808335601e1984360301811261169a575f80fd5b830160208101925035905067ffffffffffffffff8111156116b9575f80fd5b803603821315611307575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b0385811682526020820185905260408201849052608060608301525f90833563ffffffff8116808214611727575f80fd5b608085015250602084013561173b81611294565b811660a0840152604084013561175081611294565b1660c08301526117626060840161166e565b67ffffffffffffffff1660e083015261177e6080840184611685565b60a0610100850152611795610120850182846116c7565b98975050505050505050565b5f602082840312156117b1575f80fd5b5051919050565b5f602082840312156117c8575f80fd5b81516117d381611294565b9392505050565b6020808252602b908201527f44657841676772656761746f72577261707065723a20496e76616c696420766160408201526a756c74206164647265737360a81b606082015260800190565b5f60208284031215611835575f80fd5b81356117d381611294565b5f808335601e19843603018112611855575f80fd5b830160208101925035905067ffffffffffffffff811115611874575f80fd5b8060051b3603821315611307575f80fd5b8183525f6020808501808196508560051b81019150845f5b878110156118d35782840389526118b48288611685565b6118bf8682846116c7565b9a87019a955050509084019060010161189d565b5091979650505050505050565b5f60018060a01b03808516835260206040818501526118ff8586611685565b6080604087015261191460c0870182846116c7565b91505081860135606086015261192d6040870187611840565b603f198784038101608089015281845291925f929085015b8284101561196e57843561195881611294565b8716815293850193600193909301928501611945565b61197b60608b018b611840565b97509550818982030160a08a0152611994818888611885565b9b9a5050505050505050505050565b5f602082840312156119b3575f80fd5b815180151581146117d3575f80fd5b818382375f9101908152919050565b60208082526038908201527f44657841676772656761746f72577261707065723a20496e76616c696420766160408201527f756c74206164647265737320666f7220617070726f76616c0000000000000000606082015260800190565b5f60208284031215611a3e575f80fd5b815160ff811681146117d3575f80fd5b600181815b80851115611a8857815f1904821115611a6e57611a6e611641565b80851615611a7b57918102915b93841c9390800290611a53565b509250929050565b5f82611a9e57506001611668565b81611aaa57505f611668565b8160018114611ac05760028114611aca57611ae6565b6001915050611668565b60ff841115611adb57611adb611641565b50506001821b611668565b5060208310610133831016604e8410600b8410161715611b09575081810a611668565b611b138383611a4e565b805f1904821115611b2657611b26611641565b029392505050565b5f6117d360ff841683611a90565b6001600160a01b0385811682525f90610120908635611b5a81611294565b8181166020860152506020870135611b7181611294565b16604084810191909152860135611b8781611294565b6001600160a01b038116606085015250611ba3606087016112a8565b6001600160a01b038116608085015250608086013560a084015260a086013560c084015260c086013560e08401528061010084015261096b81840185876116c7565b5f8060408385031215611bf6575f80fd5b50508051602090910151909290915056fea264697066735822122091c9005b309c8e51a94b2ed9d65a7dda60953667b2c1f0102c7114e90c18993f64736f6c63430008150033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000007d0ccaa3fac1e5a943c5168b6ced828691b46b3600000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006104fe10ca937a086ba7adbd0910a4733d380cb6
-----Decoded View---------------
Arg [0] : _aggregator (address): 0x111111125421cA6dc452d289314280a0f8842A65
Arg [1] : _okxRouter (address): 0x7D0CcAa3Fac1e5A943c5168b6CEd828691b46B36
Arg [2] : _okxApprover (address): 0x40aA958dd87FC8305b97f2BA922CDdCa374bcD7f
Arg [3] : _canonicalWrapToken (address): 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Arg [4] : _predicateProxy (address): 0x6104fe10ca937a086ba7AdbD0910A4733d380cB6
-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 000000000000000000000000111111125421ca6dc452d289314280a0f8842a65
Arg [1] : 0000000000000000000000007d0ccaa3fac1e5a943c5168b6ced828691b46b36
Arg [2] : 00000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7f
Arg [3] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Arg [4] : 0000000000000000000000006104fe10ca937a086ba7adbd0910a4733d380cb6
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.