ZapToH33
0x9b3e3325d9c9ce09bd9e82cbc0b7ffffc3c47e30
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
4,249 bytes
ABI entries
20
3 read · 2 write
License
none
Contract information
- Address
- 0x9b3e3325d9c9ce09bd9e82cbc0b7ffffc3c47e30
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0xe49C113adF…48af1fadfb
- Creation tx
- 0xdf2d1509fc…3307a6133d
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (3)
H33() → address
HOOD() → address
XHOOD() → address
Events (2)
ZappedInZappedOut
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_hood",
"type": "address"
},
{
"internalType": "address",
"name": "_xHood",
"type": "address"
},
{
"internalType": "address",
"name": "_h33",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "currentTimestamp",
"type": "uint256"
}
],
"name": "DeadlineExpired",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "expectedAsset",
"type": "address"
},
{
"internalType": "address",
"name": "actualAsset",
"type": "address"
}
],
"name": "InvalidH33",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "expectedHood",
"type": "address"
},
{
"internalType": "address",
"name": "actualHood",
"type": "address"
}
],
"name": "InvalidXHood",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "minimum",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "actual",
"type": "uint256"
}
],
"name": "MinimumOutputNotMet",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "balanceBefore",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "balanceAfter",
"type": "uint256"
}
],
"name": "TokenBalanceDecreased",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "expected",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "actual",
"type": "uint256"
}
],
"name": "UnexpectedTokenAmount",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAmount",
"type": "error"
},
{
"inputs": [],
"name": "ZeroOutput",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "caller",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "hoodIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "xHoodDeposited",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "h33SharesOut",
"type": "uint256"
}
],
"name": "ZappedIn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "caller",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "h33SharesIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "xHoodExited",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "hoodOut",
"type": "uint256"
}
],
"name": "ZappedOut",
"type": "event"
},
{
"inputs": [],
"name": "H33",
"outputs": [
{
"internalType": "contract IH33Zap",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "HOOD",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "XHOOD",
"outputs": [
{
"internalType": "contract IXHoodZap",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "hoodAmount",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "minSharesOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "depositHood",
"outputs": [
{
"internalType": "uint256",
"name": "sharesOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "h33Shares",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "minHoodOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "redeemToHood",
"outputs": [
{
"internalType": "uint256",
"name": "hoodOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
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 {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface IXHoodZap is IERC20 {
function convertEmissionsToken(uint256 amount) external;
function exit(uint256 amount) external returns (uint256 exitedAmount);
function hood() external view returns (address);
}
interface IH33Zap is IERC20 {
function asset() external view returns (address);
function deposit(
uint256 assets,
address receiver
) external returns (uint256 shares);
function redeem(
uint256 shares,
address receiver,
address owner
) external returns (uint256 assets);
}
/// @title ZapToH33
/// @notice Permissionless router for entering H33 with HOOD and exiting H33 to HOOD.
/// @dev The contract is intentionally stateless and only grants exact, temporary allowances.
contract ZapToH33 is ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public immutable HOOD;
IXHoodZap public immutable XHOOD;
IH33Zap public immutable H33;
error ZeroAmount();
error ZeroOutput();
error ZeroAddress();
error InvalidReceiver(address receiver);
error InvalidXHood(address expectedHood, address actualHood);
error InvalidH33(address expectedAsset, address actualAsset);
error DeadlineExpired(uint256 deadline, uint256 currentTimestamp);
error MinimumOutputNotMet(uint256 minimum, uint256 actual);
error UnexpectedTokenAmount(
address token,
uint256 expected,
uint256 actual
);
error TokenBalanceDecreased(
address token,
uint256 balanceBefore,
uint256 balanceAfter
);
event ZappedIn(
address indexed caller,
address indexed receiver,
uint256 hoodIn,
uint256 xHoodDeposited,
uint256 h33SharesOut
);
event ZappedOut(
address indexed caller,
address indexed receiver,
uint256 h33SharesIn,
uint256 xHoodExited,
uint256 hoodOut
);
constructor(address _hood, address _xHood, address _h33) {
if (
_hood == address(0) ||
_xHood == address(0) ||
_h33 == address(0)
) revert ZeroAddress();
HOOD = IERC20(_hood);
XHOOD = IXHoodZap(_xHood);
H33 = IH33Zap(_h33);
address xHoodUnderlying = XHOOD.hood();
if (xHoodUnderlying != _hood) {
revert InvalidXHood(_hood, xHoodUnderlying);
}
address h33Asset = H33.asset();
if (h33Asset != _xHood) {
revert InvalidH33(_xHood, h33Asset);
}
}
modifier beforeDeadline(uint256 deadline) {
if (block.timestamp > deadline) {
revert DeadlineExpired(deadline, block.timestamp);
}
_;
}
/// @notice Converts HOOD into xHOOD and deposits the xHOOD into H33.
/// @dev The caller must approve this contract to spend at least `hoodAmount` HOOD.
/// @param hoodAmount Maximum HOOD pulled from the caller.
/// @param receiver Address receiving the newly minted H33 shares.
/// @param minSharesOut Minimum acceptable H33 shares minted to `receiver`.
/// @param deadline Last timestamp at which the transaction may execute.
function depositHood(
uint256 hoodAmount,
address receiver,
uint256 minSharesOut,
uint256 deadline
)
external
nonReentrant
beforeDeadline(deadline)
returns (uint256 sharesOut)
{
if (hoodAmount == 0) revert ZeroAmount();
_validateReceiver(receiver);
uint256 hoodBalanceBefore = HOOD.balanceOf(address(this));
HOOD.safeTransferFrom(msg.sender, address(this), hoodAmount);
uint256 hoodReceived = _balanceIncrease(
address(HOOD),
hoodBalanceBefore,
HOOD.balanceOf(address(this))
);
if (hoodReceived == 0) revert ZeroAmount();
uint256 xHoodBalanceBefore = XHOOD.balanceOf(address(this));
uint256 xHoodBackingBefore = HOOD.balanceOf(address(XHOOD));
HOOD.forceApprove(address(XHOOD), hoodReceived);
XHOOD.convertEmissionsToken(hoodReceived);
HOOD.forceApprove(address(XHOOD), 0);
uint256 xHoodMinted = _balanceIncrease(
address(XHOOD),
xHoodBalanceBefore,
XHOOD.balanceOf(address(this))
);
if (xHoodMinted != hoodReceived) {
revert UnexpectedTokenAmount(
address(XHOOD),
hoodReceived,
xHoodMinted
);
}
uint256 backingIncrease = _balanceIncrease(
address(HOOD),
xHoodBackingBefore,
HOOD.balanceOf(address(XHOOD))
);
if (backingIncrease != hoodReceived) {
revert UnexpectedTokenAmount(
address(HOOD),
hoodReceived,
backingIncrease
);
}
_requireBalance(address(HOOD), hoodBalanceBefore);
uint256 receiverSharesBefore = H33.balanceOf(receiver);
IERC20(address(XHOOD)).forceApprove(address(H33), xHoodMinted);
sharesOut = H33.deposit(xHoodMinted, receiver);
IERC20(address(XHOOD)).forceApprove(address(H33), 0);
uint256 receiverSharesMinted = _balanceIncrease(
address(H33),
receiverSharesBefore,
H33.balanceOf(receiver)
);
if (receiverSharesMinted != sharesOut) {
revert UnexpectedTokenAmount(
address(H33),
sharesOut,
receiverSharesMinted
);
}
if (sharesOut == 0) revert ZeroOutput();
if (sharesOut < minSharesOut) {
revert MinimumOutputNotMet(minSharesOut, sharesOut);
}
_requireBalance(address(XHOOD), xHoodBalanceBefore);
_requireBalance(address(HOOD), hoodBalanceBefore);
emit ZappedIn(
msg.sender,
receiver,
hoodReceived,
xHoodMinted,
sharesOut
);
}
/// @notice Redeems H33 shares, exits the received xHOOD, and sends HOOD to `receiver`.
/// @dev The caller must approve this contract to spend at least `h33Shares` H33.
/// The xHOOD exit penalty configured in the xHOOD contract applies.
/// @param h33Shares H33 shares burned from the caller.
/// @param receiver Address receiving the resulting HOOD.
/// @param minHoodOut Minimum acceptable HOOD received by `receiver`.
/// @param deadline Last timestamp at which the transaction may execute.
function redeemToHood(
uint256 h33Shares,
address receiver,
uint256 minHoodOut,
uint256 deadline
)
external
nonReentrant
beforeDeadline(deadline)
returns (uint256 hoodOut)
{
if (h33Shares == 0) revert ZeroAmount();
_validateReceiver(receiver);
uint256 xHoodBalanceBefore = XHOOD.balanceOf(address(this));
uint256 hoodBalanceBefore = HOOD.balanceOf(address(this));
uint256 xHoodReported = H33.redeem(
h33Shares,
address(this),
msg.sender
);
uint256 xHoodReceived = _balanceIncrease(
address(XHOOD),
xHoodBalanceBefore,
XHOOD.balanceOf(address(this))
);
if (xHoodReceived != xHoodReported) {
revert UnexpectedTokenAmount(
address(XHOOD),
xHoodReported,
xHoodReceived
);
}
if (xHoodReceived == 0) revert ZeroOutput();
uint256 hoodReported = XHOOD.exit(xHoodReceived);
uint256 hoodReceived = _balanceIncrease(
address(HOOD),
hoodBalanceBefore,
HOOD.balanceOf(address(this))
);
if (hoodReceived != hoodReported) {
revert UnexpectedTokenAmount(
address(HOOD),
hoodReported,
hoodReceived
);
}
_requireBalance(address(XHOOD), xHoodBalanceBefore);
uint256 receiverHoodBefore = HOOD.balanceOf(receiver);
HOOD.safeTransfer(receiver, hoodReceived);
hoodOut = _balanceIncrease(
address(HOOD),
receiverHoodBefore,
HOOD.balanceOf(receiver)
);
if (hoodOut == 0) revert ZeroOutput();
if (hoodOut < minHoodOut) {
revert MinimumOutputNotMet(minHoodOut, hoodOut);
}
_requireBalance(address(HOOD), hoodBalanceBefore);
emit ZappedOut(
msg.sender,
receiver,
h33Shares,
xHoodReceived,
hoodOut
);
}
function _validateReceiver(address receiver) internal view {
if (receiver == address(0) || receiver == address(this)) {
revert InvalidReceiver(receiver);
}
}
function _requireBalance(address token, uint256 expected) internal view {
uint256 actual = IERC20(token).balanceOf(address(this));
if (actual != expected) {
revert UnexpectedTokenAmount(token, expected, actual);
}
}
function _balanceIncrease(
address token,
uint256 balanceBefore,
uint256 balanceAfter
) internal pure returns (uint256 increase) {
if (balanceAfter < balanceBefore) {
revert TokenBalanceDecreased(token, balanceBefore, balanceAfter);
}
return balanceAfter - balanceBefore;
}
}
Chain explorer5320msChain node97ms