Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
| Transaction Hash |
Method
|
Block
|
From
|
To
|
Amount
|
||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
| Parent Transaction Hash | Block | From | To | Amount | ||
|---|---|---|---|---|---|---|
| 36725045 | 54 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Name:
SweepWithdrawERC4626Action
Compiler Version
v0.8.26+commit.8a97fa7a
Optimization Enabled:
Yes with 1000 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
import {IERC4626} from "@openzeppelin-contracts/interfaces/IERC4626.sol";
import {InstructionLib} from "../libraries/Instruction.sol";
import {OtimFee} from "./fee-models/OtimFee.sol";
import {IAction} from "./interfaces/IAction.sol";
import {
ISweepWithdrawERC4626Action,
INSTRUCTION_TYPEHASH,
ARGUMENTS_TYPEHASH
} from "./interfaces/ISweepWithdrawERC4626Action.sol";
import {InvalidArguments, BalanceUnderThreshold, MaxWithdrawTooLow} from "./errors/Errors.sol";
/// @title SweepWithdrawERC4626Action
/// @author Otim Labs, Inc.
/// @notice an Action that withdraws ERC20 tokens from an ERC4626 vault to a recipient when the maxWithdraw balance is greater than or equal to a threshold
contract SweepWithdrawERC4626Action is IAction, ISweepWithdrawERC4626Action, OtimFee {
constructor(address feeTokenRegistryAddress, address treasuryAddress, uint256 gasConstant_)
OtimFee(feeTokenRegistryAddress, treasuryAddress, gasConstant_)
{}
/// @inheritdoc IAction
function argumentsHash(bytes calldata arguments) public pure returns (bytes32, bytes32) {
return (INSTRUCTION_TYPEHASH, hash(abi.decode(arguments, (SweepWithdrawERC4626))));
}
/// @inheritdoc ISweepWithdrawERC4626Action
function hash(SweepWithdrawERC4626 memory arguments) public pure returns (bytes32) {
return keccak256(
abi.encode(
ARGUMENTS_TYPEHASH,
arguments.vault,
arguments.recipient,
arguments.threshold,
arguments.endBalance,
arguments.minWithdraw,
hash(arguments.fee)
)
);
}
/// @inheritdoc IAction
function execute(
InstructionLib.Instruction calldata instruction,
InstructionLib.Signature calldata,
InstructionLib.ExecutionState calldata executionState
) external override returns (bool) {
// initial gas measurement for fee calculation
uint256 startGas = gasleft();
// decode the arguments from the instruction
SweepWithdrawERC4626 memory arguments = abi.decode(instruction.arguments, (SweepWithdrawERC4626));
// if first execution, validate the input
if (executionState.executionCount == 0) {
if (
arguments.vault == address(0) || arguments.recipient == address(0)
|| arguments.endBalance > arguments.threshold
) {
revert InvalidArguments();
}
}
// get the user's vault asset balance
uint256 assetBalance =
IERC4626(arguments.vault).convertToAssets(IERC4626(arguments.vault).balanceOf(address(this)));
// if the asset balance is under the threshold or equal to the endBalance, revert
// slither-disable-next-line incorrect-equality
if (assetBalance < arguments.threshold || assetBalance == arguments.endBalance) {
revert BalanceUnderThreshold();
}
// get the max withdraw amount
uint256 maxWithdraw = IERC4626(arguments.vault).maxWithdraw(address(this));
// if the max withdraw is zero or less than the minimum withdraw amount, revert
if (maxWithdraw == 0 || maxWithdraw < arguments.minWithdraw) {
revert MaxWithdrawTooLow();
}
// calculate the amount to withdraw
uint256 withdrawAmount = assetBalance - arguments.endBalance;
// if the withdraw amount is greater than the max withdraw amount,
// set the withdraw amount to the max withdraw amount and emit an event
if (withdrawAmount > maxWithdraw) {
withdrawAmount = maxWithdraw;
emit MaxWithdrawReached(maxWithdraw, assetBalance - maxWithdraw);
}
// withdraw from the vault
// slither-disable-next-line unused-return
IERC4626(arguments.vault).withdraw(withdrawAmount, arguments.recipient, address(this));
// charge the fee
chargeFee(startGas - gasleft(), arguments.fee);
// this action has no auto-deactivation cases
return false;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC4626.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
import {Constants} from "./Constants.sol";
/// @title InstructionLib
/// @author Otim Labs, Inc.
/// @notice a library defining the Instruction datatype and util functions
library InstructionLib {
/// @notice defines a signature
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
/// @notice defines the ExecutionState datatype
/// @param deactivated - whether the Instruction has been deactivated
/// @param executionCount - the number of times the Instruction has been executed
/// @param lastExecuted - the unix timestamp of the last time the Instruction was executed
struct ExecutionState {
bool deactivated;
uint120 executionCount;
uint120 lastExecuted;
}
/// @notice defines the Instruction datatype
/// @param salt - a number to ensure the uniqueness of the Instruction
/// @param maxExecutions - the maximum number of times the Instruction can be executed
/// @param action - the address of the Action contract to be executed
/// @param arguments - the arguments to be passed to the Action contract
struct Instruction {
uint256 salt;
uint256 maxExecutions;
address action;
bytes arguments;
}
/// @notice abi.encodes and hashes an Instruction struct to create a unique Instruction identifier
/// @param instruction - an Instruction struct to hash
/// @return instructionId - unique identifier for the Instruction
function id(Instruction calldata instruction) internal pure returns (bytes32) {
return keccak256(abi.encode(instruction));
}
/// @notice calculates the EIP-712 hash for activating an Instruction
/// @param instruction - an Instruction struct to hash
/// @param domainSeparator - the EIP-712 domain separator for the verifying contract
/// @return hash - EIP-712 hash for activating `instruction`
function signingHash(
Instruction calldata instruction,
bytes32 domainSeparator,
bytes32 instructionTypeHash,
bytes32 argumentsHash
) internal pure returns (bytes32) {
return keccak256(
abi.encodePacked(
Constants.EIP712_PREFIX,
domainSeparator,
keccak256(
abi.encode(
instructionTypeHash,
instruction.salt,
instruction.maxExecutions,
instruction.action,
argumentsHash
)
)
)
);
}
/// @notice defines a deactivation instruction
/// @param instructionId - the unique identifier of the Instruction to deactivate
struct InstructionDeactivation {
bytes32 instructionId;
}
/// @notice the EIP-712 type-hash for an InstructionDeactivation
bytes32 public constant DEACTIVATION_TYPEHASH = keccak256("InstructionDeactivation(bytes32 instructionId)");
/// @notice calculates the EIP-712 hash for a InstructionDeactivation
/// @param deactivation - an InstructionDeactivation struct to hash
/// @param domainSeparator - the EIP-712 domain separator for the verifying contract
/// @return hash - EIP-712 hash for the `deactivation`
function signingHash(InstructionDeactivation calldata deactivation, bytes32 domainSeparator)
internal
pure
returns (bytes32)
{
return keccak256(
abi.encodePacked(
Constants.EIP712_PREFIX,
domainSeparator,
keccak256(abi.encode(DEACTIVATION_TYPEHASH, deactivation.instructionId))
)
);
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {IFeeTokenRegistry} from "../../infrastructure/interfaces/IFeeTokenRegistry.sol";
import {ITreasury} from "../../infrastructure/interfaces/ITreasury.sol";
import {IOtimFee, FEE_TYPEHASH} from "./interfaces/IOtimFee.sol";
/// @title OtimFee
/// @author Otim Labs, Inc.
/// @notice abstract contract for the Otim centralized fee model
abstract contract OtimFee is IOtimFee {
using SafeERC20 for IERC20;
/// @notice the Otim fee token registry contract for converting wei to ERC20 tokens
IFeeTokenRegistry public immutable feeTokenRegistry;
/// @notice the Otim treasury contract that receives fees
ITreasury public immutable treasury;
/// @notice the gas constant used to calculate the fee
uint256 public immutable gasConstant;
constructor(address feeTokenRegistryAddress, address treasuryAddress, uint256 gasConstant_) {
feeTokenRegistry = IFeeTokenRegistry(feeTokenRegistryAddress);
treasury = ITreasury(treasuryAddress);
gasConstant = gasConstant_;
}
/// @inheritdoc IOtimFee
function hash(Fee memory fee) public pure returns (bytes32) {
return keccak256(
abi.encode(FEE_TYPEHASH, fee.token, fee.maxBaseFeePerGas, fee.maxPriorityFeePerGas, fee.executionFee)
);
}
/// @inheritdoc IOtimFee
function chargeFee(uint256 gasUsed, Fee memory fee) public override {
// fee.executionFee == 0 is a magic value signifying a sponsored Instruction
if (fee.executionFee == 0) return;
// check if the base fee is too high
if (block.basefee > fee.maxBaseFeePerGas && fee.maxBaseFeePerGas != 0) {
revert BaseFeePerGasTooHigh();
}
// check if the priority fee is too high
if (tx.gasprice - block.basefee > fee.maxPriorityFeePerGas) {
revert PriorityFeePerGasTooHigh();
}
// calculate the total cost of the gas used in the transaction
uint256 weiGasCost = (gasUsed + gasConstant) * tx.gasprice;
// if fee.token is address(0), the fee is paid in native currency
if (fee.token == address(0)) {
// calculate the fee cost based on the gas used and the additional fee
uint256 weiTotalCost = weiGasCost + fee.executionFee;
// check if the user has enough balance to pay the fee
if (address(this).balance < weiTotalCost) {
revert InsufficientFeeBalance();
}
// transfer to the treasury contract
// slither-disable-next-line arbitrary-send-eth
treasury.deposit{value: weiTotalCost}();
} else {
// calculate the fee cost based on the cost of the gas used (denominated in the fee token) and the execution fee
uint256 tokenTotalCost = feeTokenRegistry.weiToToken(fee.token, weiGasCost) + fee.executionFee;
// check if the user has enough balance to pay the fee
if (IERC20(fee.token).balanceOf(address(this)) < tokenTotalCost) {
revert InsufficientFeeBalance();
}
// transfer to the treasury contract
IERC20(fee.token).safeTransfer(address(treasury), tokenTotalCost);
}
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
import {InstructionLib} from "../../libraries/Instruction.sol";
/// @title IAction
/// @author Otim Labs, Inc.
/// @notice interface for Action contracts
interface IAction {
/// @notice returns the EIP-712 type hash for the Action-specific Instruction and the EIP-712 hash of the Action-specific Instruction arguments
/// @param arguments - encoded Instruction arguments
/// @return instructionTypeHash - EIP-712 type hash for the Action-specific Instruction
/// @return argumentsTypeHash - EIP-712 hash of the Action-specific Instruction arguments
function argumentsHash(bytes calldata arguments) external returns (bytes32, bytes32);
/// @notice execute Action logic with Instruction arguments
/// @param instruction - Instruction
/// @param signature - Signature over the Instruction signing hash
/// @param executionState - ExecutionState
/// @return deactivate - whether the Instruction should be automatically deactivated
function execute(
InstructionLib.Instruction calldata instruction,
InstructionLib.Signature calldata signature,
InstructionLib.ExecutionState calldata executionState
) external returns (bool);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
import {IOtimFee} from "../fee-models/interfaces/IOtimFee.sol";
bytes32 constant INSTRUCTION_TYPEHASH = keccak256(
"Instruction(uint256 salt,uint256 maxExecutions,address action,SweepWithdrawERC4626 sweepWithdrawERC4626)Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)SweepWithdrawERC4626(address vault,address recipient,uint256 threshold,uint256 endBalance,uint256 minWithdraw,Fee fee)"
);
bytes32 constant ARGUMENTS_TYPEHASH = keccak256(
"SweepWithdrawERC4626(address vault,address recipient,uint256 threshold,uint256 endBalance,uint256 minWithdraw,Fee fee)Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)"
);
/// @title ISweepWithdrawERC4626Action
/// @author Otim Labs, Inc.
/// @notice interface for SweepWithdrawERC4626Action contract
interface ISweepWithdrawERC4626Action is IOtimFee {
/// @notice arguments for the SweepWithdrawERC4626Action contract
/// @param vault - the address of the ERC4626 vault to withdraw from
/// @param recipient - the address to receive shares
/// @param threshold - the account's maxWithdraw threshold to trigger the sweep
/// @param endBalance - the account's maxWithdraw balance after the sweep
/// @param minWithdraw - the minimum withdraw amount to trigger the sweep
/// @param fee - the fee Otim will charge for the withdraw
struct SweepWithdrawERC4626 {
address vault;
address recipient;
uint256 threshold;
uint256 endBalance;
uint256 minWithdraw;
Fee fee;
}
/// @notice emitted when the max withdraw is reached
event MaxWithdrawReached(uint256 maxWithdraw, uint256 newEndBalance);
/// @notice calculates the EIP-712 hash of the SweepWithdrawERC4626 struct
function hash(SweepWithdrawERC4626 memory arguments) external pure returns (bytes32);
}// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.26; error InvalidArguments(); error InsufficientBalance(); error BalanceOverThreshold(); error BalanceUnderThreshold(); error UniswapV3PoolDoesNotExist(); error InstructionAlreadyDeactivated(); error CallOnceFailed(address target, bytes4 selector, bytes result); error CCTPTokenNotSupported(); error MaxDepositTooLow(); error MaxWithdrawTooLow(); error TotalSharesTooLow();
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
/// @title Constants
/// @author Otim Labs, Inc.
/// @notice a library defining constants used throughout the protocol
library Constants {
/// @notice the EIP-712 signature prefix
bytes2 public constant EIP712_PREFIX = 0x1901;
/// @notice the EIP-7702 delegation designator prefix
bytes3 public constant EIP7702_PREFIX = 0xef0100;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
/// @title FeeTokenRegistry
/// @author Otim Labs, Inc.
/// @notice interface for the FeeTokenRegistry contract
interface IFeeTokenRegistry {
/// @notice fee token data struct
/// @param priceFeed - a price feed of the form <token>/ETH
/// @param heartbeat - the time in seconds between price feed updates
/// @param priceFeedDecimals - the number of decimals for the price feed
/// @param tokenDecimals - the number of decimals for the token
/// @param registered - whether the token is registered
struct FeeTokenData {
address priceFeed;
uint40 heartbeat;
uint8 priceFeedDecimals;
uint8 tokenDecimals;
bool registered;
}
/// @notice emitted when a fee token is added
event FeeTokenAdded(
address indexed token, address indexed priceFeed, uint40 heartbeat, uint8 priceFeedDecimals, uint8 tokenDecimals
);
/// @notice emitted when a fee token is removed
event FeeTokenRemoved(
address indexed token, address indexed priceFeed, uint40 heartbeat, uint8 priceFeedDecimals, uint8 tokenDecimals
);
error InvalidFeeTokenData();
error PriceFeedNotInitialized();
error FeeTokenAlreadyRegistered();
error FeeTokenNotRegistered();
error InvalidPrice();
error StalePrice();
/// @notice adds a fee token to the registry
/// @param token - the ERC20 token address
/// @param priceFeed - the price feed address
/// @param heartbeat - the time in seconds between price feed updates
function addFeeToken(address token, address priceFeed, uint40 heartbeat) external;
/// @notice removes a fee token from the registry
/// @param token - the ERC20 token address
function removeFeeToken(address token) external;
/// @notice converts a wei amount to a token amount
/// @param token - the ERC20 token address to convert to
/// @param weiAmount - the amount of wei to convert
/// @return tokenAmount - converted token amount
function weiToToken(address token, uint256 weiAmount) external view returns (uint256);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
/// @title ITreasury
/// @author Otim Labs, Inc.
/// @notice interface for Treasury contract
interface ITreasury {
/// @notice thrown when the owner tries to withdraw to the zero address
error InvalidTarget();
/// @notice thrown when the withdrawl fails
error WithdrawalFailed(bytes result);
/// @notice thrown when the owner tries to withdraw more than the contract balance
error InsufficientBalance();
/// @notice deposit ether into the treasury
function deposit() external payable;
/// @notice withdraw ether from the treasury
/// @param to - the address to withdraw to
/// @param value - the amount to withdraw
function withdraw(address to, uint256 value) external;
/// @notice withdraw ERC20 tokens from the treasury
/// @param token - the ERC20 token to withdraw
/// @param to - the address to withdraw to
/// @param value - the amount to withdraw
function withdrawERC20(address token, address to, uint256 value) external;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.26;
bytes32 constant FEE_TYPEHASH =
keccak256("Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)");
/// @title IOtimFee
/// @author Otim Labs, Inc.
/// @notice interface for the OtimFee contract
interface IOtimFee {
/// @notice fee struct
/// @param token - the token to be used for the fee (address(0) for native currency)
/// @param maxBaseFeePerGas - the maximum basefee per gas the user is willing to pay
/// @param maxPriorityFeePerGas - the maximum priority fee per gas the user is willing to pay
/// @param executionFee - fixed fee to be paid for each execution
struct Fee {
address token;
uint256 maxBaseFeePerGas;
uint256 maxPriorityFeePerGas;
uint256 executionFee;
}
/// @notice calculates the EIP-712 hash of the Fee struct
function hash(Fee memory fee) external pure returns (bytes32);
/// @notice charges a fee for the Instruction execution
/// @param gasUsed - amount of gas used during the Instruction execution
/// @param fee - additional fee to be paid
function chargeFee(uint256 gasUsed, Fee memory fee) external;
error InsufficientFeeBalance();
error BaseFeePerGasTooHigh();
error PriorityFeePerGasTooHigh();
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @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": [
"@chainlink-contracts/=dependencies/smartcontractkit-chainlink-evm-1.5.0-beta.1/contracts/",
"@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.4.0/",
"@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.4.0/",
"@uniswap-universal-router/=dependencies/@uniswap-universal-router-2.0.0/",
"@uniswap-v3-core/=dependencies/@uniswap-v3-core-1.0.2-solc-0.8-simulate/",
"@uniswap-v3-periphery/=dependencies/@uniswap-v3-periphery-1.4.4/",
"forge-std/=dependencies/forge-std-1.11.0/",
"smartcontractkit-chainlink-evm/=dependencies/smartcontractkit-chainlink-evm-1.5.0-beta.1/",
"@openzeppelin-contracts-5.4.0/=dependencies/@openzeppelin-contracts-5.4.0/",
"@uniswap-universal-router-2.0.0/=dependencies/@uniswap-universal-router-2.0.0/",
"@uniswap-v3-core-1.0.2-solc-0.8-simulate/=dependencies/@uniswap-v3-core-1.0.2-solc-0.8-simulate/contracts/",
"@uniswap-v3-periphery-1.4.4/=dependencies/@uniswap-v3-periphery-1.4.4/contracts/",
"@uniswap/=dependencies/@uniswap-v3-periphery-1.4.4/node_modules/@uniswap/",
"ds-test/=dependencies/@uniswap-universal-router-2.0.0/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=dependencies/@uniswap-universal-router-2.0.0/lib/v4-periphery/lib/v4-core/lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-gas-snapshot/=dependencies/@uniswap-universal-router-2.0.0/lib/permit2/lib/forge-gas-snapshot/src/",
"forge-std-1.11.0/=dependencies/forge-std-1.11.0/src/",
"openzeppelin-contracts/=dependencies/@uniswap-universal-router-2.0.0/lib/permit2/lib/openzeppelin-contracts/",
"permit2/=dependencies/@uniswap-universal-router-2.0.0/lib/permit2/",
"smartcontractkit-chainlink-evm-1.5.0-beta.1/=dependencies/smartcontractkit-chainlink-evm-1.5.0-beta.1/",
"solmate/=dependencies/@uniswap-universal-router-2.0.0/lib/solmate/src/",
"v3-periphery/=dependencies/@uniswap-universal-router-2.0.0/lib/v3-periphery/contracts/",
"v4-core/=dependencies/@uniswap-universal-router-2.0.0/lib/v4-periphery/lib/v4-core/src/",
"v4-periphery/=dependencies/@uniswap-universal-router-2.0.0/lib/v4-periphery/"
],
"optimizer": {
"enabled": true,
"runs": 1000
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": false
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false
}Contract ABI
API[{"inputs":[{"internalType":"address","name":"feeTokenRegistryAddress","type":"address"},{"internalType":"address","name":"treasuryAddress","type":"address"},{"internalType":"uint256","name":"gasConstant_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BalanceUnderThreshold","type":"error"},{"inputs":[],"name":"BaseFeePerGasTooHigh","type":"error"},{"inputs":[],"name":"InsufficientFeeBalance","type":"error"},{"inputs":[],"name":"InvalidArguments","type":"error"},{"inputs":[],"name":"MaxWithdrawTooLow","type":"error"},{"inputs":[],"name":"PriorityFeePerGasTooHigh","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxWithdraw","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newEndBalance","type":"uint256"}],"name":"MaxWithdrawReached","type":"event"},{"inputs":[{"internalType":"bytes","name":"arguments","type":"bytes"}],"name":"argumentsHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"maxBaseFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"internalType":"struct IOtimFee.Fee","name":"fee","type":"tuple"}],"name":"chargeFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"uint256","name":"maxExecutions","type":"uint256"},{"internalType":"address","name":"action","type":"address"},{"internalType":"bytes","name":"arguments","type":"bytes"}],"internalType":"struct InstructionLib.Instruction","name":"instruction","type":"tuple"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct InstructionLib.Signature","name":"","type":"tuple"},{"components":[{"internalType":"bool","name":"deactivated","type":"bool"},{"internalType":"uint120","name":"executionCount","type":"uint120"},{"internalType":"uint120","name":"lastExecuted","type":"uint120"}],"internalType":"struct InstructionLib.ExecutionState","name":"executionState","type":"tuple"}],"name":"execute","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeTokenRegistry","outputs":[{"internalType":"contract IFeeTokenRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasConstant","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"threshold","type":"uint256"},{"internalType":"uint256","name":"endBalance","type":"uint256"},{"internalType":"uint256","name":"minWithdraw","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"maxBaseFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"internalType":"struct IOtimFee.Fee","name":"fee","type":"tuple"}],"internalType":"struct ISweepWithdrawERC4626Action.SweepWithdrawERC4626","name":"arguments","type":"tuple"}],"name":"hash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"maxBaseFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"},{"internalType":"uint256","name":"executionFee","type":"uint256"}],"internalType":"struct IOtimFee.Fee","name":"fee","type":"tuple"}],"name":"hash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"contract ITreasury","name":"","type":"address"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60e060405234801561000f575f80fd5b50604051610ef4380380610ef483398101604081905261002e91610064565b6001600160a01b03928316608052911660a05260c05261009d565b80516001600160a01b038116811461005f575f80fd5b919050565b5f805f60608486031215610076575f80fd5b61007f84610049565b925061008d60208501610049565b9150604084015190509250925092565b60805160a05160c051610e136100e15f395f8181608e015261061301525f818160c80152818161068a015261085801525f818161016701526107470152610e135ff3fe608060405234801561000f575f80fd5b5060043610610085575f3560e01c80639fcd91b3116100585780639fcd91b31461013a578063aebfab1b14610162578063ef40881014610189578063fd89ea0c1461019c575f80fd5b80632cb0f8a21461008957806361d027b3146100c357806369179de31461010257806399d0dd3714610125575b5f80fd5b6100b07f000000000000000000000000000000000000000000000000000000000000000081565b6040519081526020015b60405180910390f35b6100ea7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100ba565b610115610110366004610af2565b6101af565b60405190151581526020016100ba565b610138610133366004610be2565b610569565b005b61014d610148366004610c0d565b610884565b604080519283526020830191909152016100ba565b6100ea7f000000000000000000000000000000000000000000000000000000000000000081565b6100b0610197366004610c7b565b6108c2565b6100b06101aa366004610d13565b610966565b5f805a90505f6101c26060870187610d34565b8101906101cf9190610c7b565b90506101e16040850160208601610d77565b6effffffffffffffffffffffffffffff165f036102635780516001600160a01b0316158061021a575060208101516001600160a01b0316155b8061022c575080604001518160600151115b15610263576040517f5f6f132c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80516040516370a0823160e01b81523060048201525f916001600160a01b0316906307a2d13a9082906370a0823190602401602060405180830381865afa1580156102b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102d49190610da5565b6040518263ffffffff1660e01b81526004016102f291815260200190565b602060405180830381865afa15801561030d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103319190610da5565b905081604001518110806103485750816060015181145b1561037f576040517fdddf9d3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81516040517fce96cb770000000000000000000000000000000000000000000000000000000081523060048201525f916001600160a01b03169063ce96cb7790602401602060405180830381865afa1580156103dd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104019190610da5565b90508015806104135750826080015181105b1561044a576040517f9199d1d300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f83606001518361045b9190610dd0565b9050818111156104ac5750807fa1613a65612b317107ba1542142682d65c963d6138f33ec89d5b5b4fbffb1176816104938186610dd0565b6040805192835260208301919091520160405180910390a15b835160208501516040517fb460af94000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03918216602482015230604482015291169063b460af94906064016020604051808303815f875af115801561051e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105429190610da5565b5061055b5a6105519087610dd0565b8560a00151610569565b505f98975050505050505050565b80606001515f03610578575050565b80602001514811801561058e5750602081015115155b156105c5576040517f2f3d0a5c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408101516105d4483a610dd0565b111561060c576040517f8ef482f600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f3a6106387f000000000000000000000000000000000000000000000000000000000000000085610de9565b6106429190610dfc565b82519091506001600160a01b03166106fe575f8260600151826106659190610de9565b90508047101561068857604051637806a4f560e11b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156106e1575f80fd5b505af11580156106f3573d5f803e3d5ffd5b505050505050505050565b606082015182516040517fd251f7ba0000000000000000000000000000000000000000000000000000000081526001600160a01b039182166004820152602481018490525f92917f0000000000000000000000000000000000000000000000000000000000000000169063d251f7ba90604401602060405180830381865afa15801561078c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107b09190610da5565b6107ba9190610de9565b83516040516370a0823160e01b815230600482015291925082916001600160a01b03909116906370a0823190602401602060405180830381865afa158015610804573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108289190610da5565b101561084757604051637806a4f560e11b815260040160405180910390fd5b825161087d906001600160a01b03167f0000000000000000000000000000000000000000000000000000000000000000836109d6565b505b505050565b5f807f4630e17240572b38a6347bbefe570db8ac4265893f644c139c67bea2fbb5fe356108b661019785870187610c7b565b915091505b9250929050565b5f7fabc6b0784f2c5a640f50464a0b92d6af71a62c77f4ec04f25baa75c82f607406825f015183602001518460400151856060015186608001516109098860a00151610966565b6040805160208101989098526001600160a01b0396871690880152949093166060860152608085019190915260a084015260c083015260e0820152610100015b604051602081830303815290604052805190602001209050919050565b5f7f7aff6c7b9b8aafff555894be6cfd4a6211fc7adbcd97db18b05d4a51ed7482a0825f01518360200151846040015185606001516040516020016109499594939291909485526001600160a01b0393909316602085015260408401919091526060830152608082015260a00190565b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000178152825161087f93879390925f9283929183919082885af180610a72576040513d5f823e3d81fd5b50505f513d91508115610a89578060011415610a96565b6001600160a01b0384163b155b1561087d576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240160405180910390fd5b5f60608284031215610aec575f80fd5b50919050565b5f805f60e08486031215610b04575f80fd5b833567ffffffffffffffff811115610b1a575f80fd5b840160808187031215610b2b575f80fd5b9250610b3a8560208601610adc565b9150610b498560808601610adc565b90509250925092565b80356001600160a01b0381168114610b68575f80fd5b919050565b5f60808284031215610b7d575f80fd5b6040516080810167ffffffffffffffff81118282101715610bac57634e487b7160e01b5f52604160045260245ffd5b604052905080610bbb83610b52565b81526020838101359082015260408084013590820152606092830135920191909152919050565b5f8060a08385031215610bf3575f80fd5b82359150610c048460208501610b6d565b90509250929050565b5f8060208385031215610c1e575f80fd5b823567ffffffffffffffff811115610c34575f80fd5b8301601f81018513610c44575f80fd5b803567ffffffffffffffff811115610c5a575f80fd5b856020828401011115610c6b575f80fd5b6020919091019590945092505050565b5f610120828403128015610c8d575f80fd5b5060405160c0810167ffffffffffffffff81118282101715610cbd57634e487b7160e01b5f52604160045260245ffd5b604052610cc983610b52565b8152610cd760208401610b52565b6020820152604083810135908201526060808401359082015260808084013590820152610d078460a08501610b6d565b60a08201529392505050565b5f60808284031215610d23575f80fd5b610d2d8383610b6d565b9392505050565b5f808335601e19843603018112610d49575f80fd5b83018035915067ffffffffffffffff821115610d63575f80fd5b6020019150368190038213156108bb575f80fd5b5f60208284031215610d87575f80fd5b81356effffffffffffffffffffffffffffff81168114610d2d575f80fd5b5f60208284031215610db5575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610de357610de3610dbc565b92915050565b80820180821115610de357610de3610dbc565b8082028115828204841417610de357610de3610dbc5600000000000000000000000019c370a2e300429f98c17803984dc5413faac02e000000000000000000000000c7536a1e8205c139d25165f6305e20299b7925110000000000000000000000000000000000000000000000000000000000019834
Deployed Bytecode
0x608060405234801561000f575f80fd5b5060043610610085575f3560e01c80639fcd91b3116100585780639fcd91b31461013a578063aebfab1b14610162578063ef40881014610189578063fd89ea0c1461019c575f80fd5b80632cb0f8a21461008957806361d027b3146100c357806369179de31461010257806399d0dd3714610125575b5f80fd5b6100b07f000000000000000000000000000000000000000000000000000000000001983481565b6040519081526020015b60405180910390f35b6100ea7f000000000000000000000000c7536a1e8205c139d25165f6305e20299b79251181565b6040516001600160a01b0390911681526020016100ba565b610115610110366004610af2565b6101af565b60405190151581526020016100ba565b610138610133366004610be2565b610569565b005b61014d610148366004610c0d565b610884565b604080519283526020830191909152016100ba565b6100ea7f00000000000000000000000019c370a2e300429f98c17803984dc5413faac02e81565b6100b0610197366004610c7b565b6108c2565b6100b06101aa366004610d13565b610966565b5f805a90505f6101c26060870187610d34565b8101906101cf9190610c7b565b90506101e16040850160208601610d77565b6effffffffffffffffffffffffffffff165f036102635780516001600160a01b0316158061021a575060208101516001600160a01b0316155b8061022c575080604001518160600151115b15610263576040517f5f6f132c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80516040516370a0823160e01b81523060048201525f916001600160a01b0316906307a2d13a9082906370a0823190602401602060405180830381865afa1580156102b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102d49190610da5565b6040518263ffffffff1660e01b81526004016102f291815260200190565b602060405180830381865afa15801561030d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103319190610da5565b905081604001518110806103485750816060015181145b1561037f576040517fdddf9d3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81516040517fce96cb770000000000000000000000000000000000000000000000000000000081523060048201525f916001600160a01b03169063ce96cb7790602401602060405180830381865afa1580156103dd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104019190610da5565b90508015806104135750826080015181105b1561044a576040517f9199d1d300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f83606001518361045b9190610dd0565b9050818111156104ac5750807fa1613a65612b317107ba1542142682d65c963d6138f33ec89d5b5b4fbffb1176816104938186610dd0565b6040805192835260208301919091520160405180910390a15b835160208501516040517fb460af94000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b03918216602482015230604482015291169063b460af94906064016020604051808303815f875af115801561051e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105429190610da5565b5061055b5a6105519087610dd0565b8560a00151610569565b505f98975050505050505050565b80606001515f03610578575050565b80602001514811801561058e5750602081015115155b156105c5576040517f2f3d0a5c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408101516105d4483a610dd0565b111561060c576040517f8ef482f600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f3a6106387f000000000000000000000000000000000000000000000000000000000001983485610de9565b6106429190610dfc565b82519091506001600160a01b03166106fe575f8260600151826106659190610de9565b90508047101561068857604051637806a4f560e11b815260040160405180910390fd5b7f000000000000000000000000c7536a1e8205c139d25165f6305e20299b7925116001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156106e1575f80fd5b505af11580156106f3573d5f803e3d5ffd5b505050505050505050565b606082015182516040517fd251f7ba0000000000000000000000000000000000000000000000000000000081526001600160a01b039182166004820152602481018490525f92917f00000000000000000000000019c370a2e300429f98c17803984dc5413faac02e169063d251f7ba90604401602060405180830381865afa15801561078c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107b09190610da5565b6107ba9190610de9565b83516040516370a0823160e01b815230600482015291925082916001600160a01b03909116906370a0823190602401602060405180830381865afa158015610804573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108289190610da5565b101561084757604051637806a4f560e11b815260040160405180910390fd5b825161087d906001600160a01b03167f000000000000000000000000c7536a1e8205c139d25165f6305e20299b792511836109d6565b505b505050565b5f807f4630e17240572b38a6347bbefe570db8ac4265893f644c139c67bea2fbb5fe356108b661019785870187610c7b565b915091505b9250929050565b5f7fabc6b0784f2c5a640f50464a0b92d6af71a62c77f4ec04f25baa75c82f607406825f015183602001518460400151856060015186608001516109098860a00151610966565b6040805160208101989098526001600160a01b0396871690880152949093166060860152608085019190915260a084015260c083015260e0820152610100015b604051602081830303815290604052805190602001209050919050565b5f7f7aff6c7b9b8aafff555894be6cfd4a6211fc7adbcd97db18b05d4a51ed7482a0825f01518360200151846040015185606001516040516020016109499594939291909485526001600160a01b0393909316602085015260408401919091526060830152608082015260a00190565b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000178152825161087f93879390925f9283929183919082885af180610a72576040513d5f823e3d81fd5b50505f513d91508115610a89578060011415610a96565b6001600160a01b0384163b155b1561087d576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015260240160405180910390fd5b5f60608284031215610aec575f80fd5b50919050565b5f805f60e08486031215610b04575f80fd5b833567ffffffffffffffff811115610b1a575f80fd5b840160808187031215610b2b575f80fd5b9250610b3a8560208601610adc565b9150610b498560808601610adc565b90509250925092565b80356001600160a01b0381168114610b68575f80fd5b919050565b5f60808284031215610b7d575f80fd5b6040516080810167ffffffffffffffff81118282101715610bac57634e487b7160e01b5f52604160045260245ffd5b604052905080610bbb83610b52565b81526020838101359082015260408084013590820152606092830135920191909152919050565b5f8060a08385031215610bf3575f80fd5b82359150610c048460208501610b6d565b90509250929050565b5f8060208385031215610c1e575f80fd5b823567ffffffffffffffff811115610c34575f80fd5b8301601f81018513610c44575f80fd5b803567ffffffffffffffff811115610c5a575f80fd5b856020828401011115610c6b575f80fd5b6020919091019590945092505050565b5f610120828403128015610c8d575f80fd5b5060405160c0810167ffffffffffffffff81118282101715610cbd57634e487b7160e01b5f52604160045260245ffd5b604052610cc983610b52565b8152610cd760208401610b52565b6020820152604083810135908201526060808401359082015260808084013590820152610d078460a08501610b6d565b60a08201529392505050565b5f60808284031215610d23575f80fd5b610d2d8383610b6d565b9392505050565b5f808335601e19843603018112610d49575f80fd5b83018035915067ffffffffffffffff821115610d63575f80fd5b6020019150368190038213156108bb575f80fd5b5f60208284031215610d87575f80fd5b81356effffffffffffffffffffffffffffff81168114610d2d575f80fd5b5f60208284031215610db5575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610de357610de3610dbc565b92915050565b80820180821115610de357610de3610dbc565b8082028115828204841417610de357610de3610dbc56
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000019c370a2e300429f98c17803984dc5413faac02e000000000000000000000000c7536a1e8205c139d25165f6305e20299b7925110000000000000000000000000000000000000000000000000000000000019834
-----Decoded View---------------
Arg [0] : feeTokenRegistryAddress (address): 0x19c370A2e300429F98c17803984DC5413faAc02E
Arg [1] : treasuryAddress (address): 0xc7536a1e8205C139d25165F6305e20299b792511
Arg [2] : gasConstant_ (uint256): 104500
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 00000000000000000000000019c370a2e300429f98c17803984dc5413faac02e
Arg [1] : 000000000000000000000000c7536a1e8205c139d25165f6305e20299b792511
Arg [2] : 0000000000000000000000000000000000000000000000000000000000019834
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ 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.