V4UtilsRouter
0xcb3d2a42022741b06f9b38459e3dd1ee9a64d129
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
14,558 bytes
ABI entries
56
12 read · 18 write
License
none
Contract information
- Address
- 0xcb3d2a42022741b06f9b38459e3dd1ee9a64d129
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x4e59b44847…26c0B4956C
- Creation tx
- 0xf2305c5309…0fe4c9ba05
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (12)
ADMIN_ROLE() → bytes32
AUTOMATION_OPERATOR_ROLE() → bytes32
DEFAULT_ADMIN_ROLE() → bytes32
DOMAIN_SEPARATOR() → bytes32
WITHDRAWER_ROLE() → bytes32
getRoleAdmin(bytes32) → bytes32
hasRole(bytes32, address) → bool
implementationLocked() → bool
isOrderCancelled(bytes) → bool
paused() → bool
supportsInterface(bytes4) → bool
v4UtilsImplementations(address) → address
Events (9)
CancelOrderDelegatedCallImplementationSetInitializedPausedRoleAdminChangedRoleGrantedRoleRevokedUnpaused
ABI
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AccessControlBadConfirmation",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "bytes32",
"name": "neededRole",
"type": "bytes32"
}
],
"name": "AccessControlUnauthorizedAccount",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "DelegateCallFailed",
"type": "error"
},
{
"inputs": [],
"name": "ECDSAInvalidSignature",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "length",
"type": "uint256"
}
],
"name": "ECDSAInvalidSignatureLength",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "ECDSAInvalidSignatureS",
"type": "error"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "ImplementationLocked",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInitialization",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSelector",
"type": "error"
},
{
"inputs": [],
"name": "NotInitializing",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "posm",
"type": "address"
}
],
"name": "UnsupportedPositionManager",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes",
"name": "order",
"type": "bytes"
},
{
"indexed": false,
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "CancelOrder",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "target",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes4",
"name": "selector",
"type": "bytes4"
},
{
"indexed": true,
"internalType": "address",
"name": "posm",
"type": "address"
}
],
"name": "DelegatedCall",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "posm",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "implementation",
"type": "address"
}
],
"name": "ImplementationSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "version",
"type": "uint64"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "AUTOMATION_OPERATOR_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WITHDRAWER_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "abiEncodedUserOrder",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "orderSignature",
"type": "bytes"
}
],
"name": "cancelOrder",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "posm",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "execute",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "posm",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "instructionData",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "abiEncodedUserOrder",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "orderSignature",
"type": "bytes"
}
],
"name": "executeAutomation",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleAdmin",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "grantOperatorRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "hasRole",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "implementationLocked",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_swapRouter",
"type": "address"
},
{
"internalType": "address",
"name": "_feeTaker",
"type": "address"
},
{
"internalType": "address",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "address",
"name": "withdrawer",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "orderSignature",
"type": "bytes"
}
],
"name": "isOrderCancelled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lockImplementation",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "onERC721Received",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "callerConfirmation",
"type": "address"
}
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "revokeOperatorRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_feeTaker",
"type": "address"
}
],
"name": "setFeeTaker",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "posm",
"type": "address"
},
{
"internalType": "address",
"name": "implementation",
"type": "address"
}
],
"name": "setImplementation",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "posm",
"type": "address"
}
],
"name": "v4UtilsImplementations",
"outputs": [
{
"internalType": "address",
"name": "implementation",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IERC20[]",
"name": "tokens",
"type": "address[]"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "withdrawERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IERC721",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "withdrawERC721",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "withdrawNative",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.26;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { SharedStorage } from "./libraries/Storage.sol";
import { FeeType } from "./libraries/FeeConstants.sol";
import { EIP712 } from "./base/EIP712.sol";
import { Withdrawable } from "./base/Withdrawable.sol";
/**
* @title V4UtilsRouter
* @notice Router contract that delegates calls to the appropriate V4Utils implementation
* based on the position manager (posm) address provided
*/
contract V4UtilsRouter is AccessControl, Pausable, Initializable, EIP712, IERC721Receiver, Withdrawable {
bytes32 public constant WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE");
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant AUTOMATION_OPERATOR_ROLE = keccak256("AUTOMATION_OPERATOR_ROLE");
/// @notice Mapping from position manager address to V4Utils implementation address
mapping(address posm => address implementation) public v4UtilsImplementations;
mapping(bytes32 => bool) _cancelledOrder;
bool public implementationLocked;
error ZeroAddress();
error ImplementationLocked();
error UnsupportedPositionManager(address posm);
error DelegateCallFailed(bytes data);
error InvalidSelector();
event ImplementationSet(address indexed posm, address indexed implementation);
event DelegatedCall(address indexed target, bytes4 indexed selector, address indexed posm);
event CancelOrder(address user, bytes order, bytes signature);
constructor() EIP712("V3AutomationOrder", "5.0") { }
/**
* @notice Initialize the contract with the swap router, fee taker, WETH address, admin, and withdrawer
* @param _swapRouter The address of the swap router
* @param _feeTaker The address that will take fees
* @param _weth The address of the WETH token
* @param admin The address of the admin
* @param withdrawer The address that can withdraw tokens
*/
function initialize(address _swapRouter, address _feeTaker, address _weth, address admin, address withdrawer)
public
initializer
{
require(_feeTaker != address(0), ZeroAddress());
SharedStorage.Storage storage ss = SharedStorage.getStorage();
ss.swapRouter = _swapRouter;
ss.feeTaker = _feeTaker;
ss.weth = _weth;
ss.maxFeeX64[uint8(FeeType.GAS_FEE)] = 5_534_023_222_112_865_280; // 30%
ss.maxFeeX64[uint8(FeeType.PROTOCOL_FEE)] = 5_534_023_222_112_865_280; // 30%
ss.maxFeeX64[uint8(FeeType.PERFORMANCE_FEE)] = 3_689_348_814_741_910_528; // 20%
_grantRole(ADMIN_ROLE, admin);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(WITHDRAWER_ROLE, withdrawer);
_grantRole(DEFAULT_ADMIN_ROLE, withdrawer);
}
/**
* @notice Set the V4Utils implementation for a specific position manager
* @param posm The position manager address
* @param implementation The V4Utils implementation address
*/
function setImplementation(address posm, address implementation) external onlyRole(ADMIN_ROLE) {
require(!implementationLocked, ImplementationLocked());
require(posm != address(0) && implementation != address(0), ZeroAddress());
v4UtilsImplementations[posm] = implementation;
emit ImplementationSet(posm, implementation);
}
/**
* @notice Execute a function call on the appropriate V4Utils implementation
* @param posm The position manager address to determine which V4Utils to use
* @param data The encoded function call data including selector
*/
function execute(address posm, bytes calldata data) external payable whenNotPaused {
address implementation = v4UtilsImplementations[posm];
require(implementation != address(0), UnsupportedPositionManager(posm));
// Extract selector from data for event emission
bytes4 selector;
assembly {
selector := calldataload(data.offset)
}
// Make sure we don't delegatecall to onERC721Received
require(selector != IERC721Receiver.onERC721Received.selector, InvalidSelector());
_delegateCall(implementation, data, posm, selector);
}
/**
* @notice Execute an automation order by transferring the NFT to this contract
* @param posm The position manager address
* @param tokenId The NFT identifier
* @param instructionData Additional data for the instruction
* @param abiEncodedUserOrder The user order data encoded in ABI format
* @param orderSignature The signature of the user order
*/
function executeAutomation(
address posm,
uint256 tokenId,
bytes calldata instructionData,
bytes calldata abiEncodedUserOrder,
bytes calldata orderSignature
) external payable onlyRole(AUTOMATION_OPERATOR_ROLE) whenNotPaused {
address positionOwner = IERC721(posm).ownerOf(tokenId);
_validateOrder(abiEncodedUserOrder, orderSignature, positionOwner);
IERC721(posm).safeTransferFrom(positionOwner, address(this), tokenId, instructionData);
}
/**
* @notice ERC721 callback function that routes to the appropriate V4Utils implementation
* @param operator The address which called `safeTransferFrom`
* @param from The address which previously owned the token
* @param tokenId The NFT identifier which is being transferred
* @param data Additional data with no specified format
* @return bytes4 The selector to confirm token transfer
*/
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)
external
override
returns (bytes4)
{
address posm = msg.sender; // The position manager is the caller
address implementation = v4UtilsImplementations[posm];
require(implementation != address(0), UnsupportedPositionManager(posm));
bytes memory callData =
abi.encodeWithSelector(IERC721Receiver.onERC721Received.selector, operator, from, tokenId, data);
(bool success, bytes memory result) = implementation.delegatecall(callData);
require(success, DelegateCallFailed(result));
emit DelegatedCall(implementation, IERC721Receiver.onERC721Received.selector, posm);
return abi.decode(result, (bytes4));
}
/**
* @notice Execute a delegate call and handle errors
* @param target The target contract address
* @param data The call data to execute (includes selector)
* @param posm The position manager address for event emission
* @param selector The function selector for event emission
*/
function _delegateCall(address target, bytes calldata data, address posm, bytes4 selector) internal {
(bool success, bytes memory result) = target.delegatecall(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result)) // Revert with the original reason
}
}
emit DelegatedCall(target, selector, posm);
}
function _validateOrder(bytes memory abiEncodedUserOrder, bytes memory orderSignature, address actor) internal view {
address userAddress = _recover(abiEncodedUserOrder, orderSignature);
require(userAddress == actor);
require(!_cancelledOrder[keccak256(orderSignature)]);
}
/**
* @notice Cancel an order by marking it as cancelled
* @param abiEncodedUserOrder The user order data encoded in ABI format
* @param orderSignature The signature of the user order
*/
function cancelOrder(bytes calldata abiEncodedUserOrder, bytes calldata orderSignature) external {
_validateOrder(abiEncodedUserOrder, orderSignature, _msgSender());
_cancelledOrder[keccak256(orderSignature)] = true;
emit CancelOrder(_msgSender(), abiEncodedUserOrder, orderSignature);
}
/// @notice Check if an order has been cancelled
/// @param orderSignature The signature of the order to check
/// @return bool True if the order is cancelled, false otherwise
function isOrderCancelled(bytes calldata orderSignature) external view returns (bool) {
return _cancelledOrder[keccak256(orderSignature)];
}
/// @notice grant operator role to the address
/// @param _address The address to which the operator role is granted
function grantOperatorRole(address _address) external onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(AUTOMATION_OPERATOR_ROLE, _address);
}
/// @notice revoke operator role from the address
/// @param _address The address from which the operator role is revoked
function revokeOperatorRole(address _address) external onlyRole(DEFAULT_ADMIN_ROLE) {
revokeRole(AUTOMATION_OPERATOR_ROLE, _address);
}
/// @notice Set the fee taker address
/// @param _feeTaker The fee taker address
function setFeeTaker(address _feeTaker) external onlyRole(ADMIN_ROLE) {
SharedStorage.getStorage().feeTaker = _feeTaker;
}
function pause() external onlyRole(ADMIN_ROLE) {
_pause();
}
function unpause() external onlyRole(ADMIN_ROLE) {
_unpause();
}
function withdrawERC20(IERC20[] calldata tokens, address to) public virtual override onlyRole(WITHDRAWER_ROLE) {
super.withdrawERC20(tokens, to);
}
function withdrawNative(address to) public virtual override onlyRole(WITHDRAWER_ROLE) {
super.withdrawNative(to);
}
function withdrawERC721(IERC721 token, uint256 tokenId, address to)
public
virtual
override
onlyRole(WITHDRAWER_ROLE)
{
super.withdrawERC721(token, tokenId, to);
}
/**
* @notice Lock the implementation to prevent further changes
* This function can only be called by an account with the ADMIN_ROLE
*/
function lockImplementation() external onlyRole(ADMIN_ROLE) {
implementationLocked = true;
}
/**
* @notice Allow contract to receive ETH
*/
receive() external payable { }
}
Chain explorer2819msChain node84ms