Loading…
Contract
Native balance
0 tZROAR
Live from the node · no monetary value
Activity
6 transactions
First seen block 16,981 · last · nonce 1
Contract
Verified — exact match
The compiled bytecode matches the deployed bytecode exactly, including metadata.0x0000000000000000000000008fd1accb358c6f2551c6ca0f590b709c0653e0e4000000000000000000000000de8fc3b73e75987a7ff93300dc0b8abb6c26d4b1
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IYieldStrategy} from "../IYieldStrategy.sol";
import {IPrincipalProtected} from "../IPrincipalProtected.sol";
/**
* @title TieredYieldStrategy
* @notice Keeps a borrower's pledged principal out of market risk while still
* allowing multi-asset upside — funded only by interest already earned.
*
* @dev The problem this solves:
*
* A multi-asset strategy SELLS the USDC it receives. Handed a borrower's
* pledged collateral, a drawdown means the vault can return less than was
* pledged — principal risk on money securing a home loan. A borrower who
* pledges 100,000 can be recalled 60,000, and no amount of disclosure
* makes that an appropriate default.
*
* The structure here separates the two pools of money:
*
* PRINCIPAL sits in the preservation sleeve (a lending market). It is
* never sold, never routed to a DEX, never exposed to a
* basket. Its nominal value does not move with token prices.
*
* INTEREST earned above principal may be moved into the growth sleeve,
* where it is traded. Losing all of it returns the borrower
* to exactly the principal they pledged.
*
* The separation is an on-chain invariant, not a policy: `harvestToGrowth`
* reverts unless the preservation sleeve still covers `principalSupplied`
* in full afterwards. There is no admin path, no role, and no parameter
* that lets pledged principal reach the growth sleeve.
*
* What this does NOT remove: the preservation sleeve is a lending market,
* so smart-contract and bad-debt risk remain, and a sleeve loss can still
* take principal below par. That risk is real, but it is a different and
* far smaller one than selling collateral into volatile assets.
*/
contract TieredYieldStrategy is IYieldStrategy, IPrincipalProtected, AccessControl, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
/// @notice The CollateralVault. The only caller that may move funds.
bytes32 public constant VAULT_ROLE = keccak256("VAULT_ROLE");
/// @notice May move earned interest into the growth sleeve.
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST_ROLE");
/// @notice May halt harvesting and force an unwind.
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
IERC20 public immutable usdc;
/// @notice Holds principal. Must be principal-preserving in nominal terms.
IYieldStrategy public preservation;
/// @notice Holds harvested interest only. May trade.
IYieldStrategy public growth;
/// @notice Total principal the vault has deposited, net of withdrawals.
uint256 public principalSupplied;
/// @notice Cumulative interest moved into the growth sleeve.
uint256 public harvestedToGrowth;
/**
* @notice Ceiling on the growth sleeve, in basis points of principal.
*
* @dev A second, independent bound. Even where interest has genuinely been
* earned, concentrating a large multiple of it into a traded basket
* changes the product's character. Capped so that cannot happen by
* accretion over a long loan.
*/
uint256 public maxGrowthBps = 1000; // 10%
/// @notice Hard ceiling on `maxGrowthBps`, so it cannot be widened away.
uint256 public constant MAX_GROWTH_BPS_LIMIT = 2500; // 25%
/**
* @notice Drawdown at which the growth sleeve is unwound, in basis points.
* @dev Measured against the high-water mark of the growth sleeve.
*/
uint256 public drawdownBreakerBps = 3000; // 30%
/// @notice Highest value the growth sleeve has reached, for the breaker.
uint256 public growthHighWater;
event PreservationSleeveSet(address indexed sleeve);
event GrowthSleeveSet(address indexed sleeve);
event HarvestedToGrowth(uint256 amount, uint256 principalCovered);
event GrowthUnwound(uint256 recovered, string reason);
event DrawdownBreakerTripped(uint256 highWater, uint256 current, uint256 recovered);
event MaxGrowthBpsChanged(uint256 bps);
event DrawdownBreakerChanged(uint256 bps);
error ZeroAddress();
error ZeroAmount();
error NoPreservationSleeve();
error NoGrowthSleeve();
/// @dev The defining refusal: a harvest that would dip into principal.
error WouldBreachPrincipal(uint256 principalRequired, uint256 preservationAfter);
error ExceedsGrowthCeiling(uint256 requested, uint256 ceiling);
error BpsOutOfRange(uint256 provided, uint256 maximum);
error WithdrawalShortfall(uint256 requested, uint256 delivered);
constructor(address usdcAddress, address admin) {
if (usdcAddress == address(0) || admin == address(0)) revert ZeroAddress();
usdc = IERC20(usdcAddress);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(GUARDIAN_ROLE, admin);
}
// -------------------------------------------------------- IYieldStrategy
/**
* @notice Accepts principal and routes ALL of it to the preservation sleeve.
* @dev There is deliberately no split parameter. Principal has exactly one
* destination, so no caller and no misconfiguration can send a
* fraction of it to the growth sleeve.
*/
function deposit(uint256 amount)
external
override
onlyRole(VAULT_ROLE)
nonReentrant
whenNotPaused
returns (uint256)
{
if (amount == 0) revert ZeroAmount();
if (address(preservation) == address(0)) revert NoPreservationSleeve();
uint256 before = usdc.balanceOf(address(this));
usdc.safeTransferFrom(msg.sender, address(this), amount);
uint256 received = usdc.balanceOf(address(this)) - before;
principalSupplied += received;
usdc.forceApprove(address(preservation), received);
preservation.deposit(received);
return received;
}
/**
* @notice Returns USDC to the vault, preservation sleeve first.
*
* @dev Growth is only touched when preservation alone cannot cover the
* request — which means the preservation sleeve itself lost value,
* not that trading did. Reverts rather than delivering short, because
* CollateralVault decrements its accounting by the amount requested.
*/
function withdraw(uint256 amount)
external
override
onlyRole(VAULT_ROLE)
nonReentrant
returns (uint256)
{
if (amount == 0) revert ZeroAmount();
uint256 recipientBefore = usdc.balanceOf(msg.sender);
uint256 fromPreservation = _preservationValue();
if (fromPreservation > amount) fromPreservation = amount;
if (fromPreservation > 0) {
preservation.withdraw(fromPreservation);
}
uint256 stillNeeded = amount > fromPreservation ? amount - fromPreservation : 0;
if (stillNeeded > 0 && address(growth) != address(0)) {
growth.withdraw(stillNeeded);
}
usdc.safeTransfer(msg.sender, amount);
uint256 delivered = usdc.balanceOf(msg.sender) - recipientBefore;
if (delivered < amount) revert WithdrawalShortfall(amount, delivered);
principalSupplied = principalSupplied > amount ? principalSupplied - amount : 0;
return delivered;
}
/// @notice Combined value of both sleeves.
function totalValue() public view override returns (uint256) {
return _preservationValue() + _growthValue();
}
/// @notice Value above principal. Zero when the position is under water.
function accruedYield() external view override returns (uint256) {
uint256 value = totalValue();
return value > principalSupplied ? value - principalSupplied : 0;
}
function marketName() external view override returns (string memory) {
return "Tiered: preservation principal + growth on earned interest";
}
// --------------------------------------------------------------- harvest
/**
* @notice Moves earned interest into the growth sleeve.
*
* @dev The single place the two pools of money meet, and the only function
* whose correctness protects the borrower's principal. Both checks
* below are load-bearing:
*
* 1. Preservation must STILL cover principal in full afterwards.
* 2. The growth sleeve must stay under its ceiling.
*
* Value is read after the withdrawal rather than predicted, so a
* sleeve that returns less than asked cannot leave principal short.
*/
function harvestToGrowth(uint256 amount)
external
onlyRole(STRATEGIST_ROLE)
nonReentrant
whenNotPaused
returns (uint256)
{
if (amount == 0) revert ZeroAmount();
if (address(preservation) == address(0)) revert NoPreservationSleeve();
if (address(growth) == address(0)) revert NoGrowthSleeve();
uint256 ceiling = (principalSupplied * maxGrowthBps) / 10_000;
if (_growthValue() + amount > ceiling) {
revert ExceedsGrowthCeiling(_growthValue() + amount, ceiling);
}
preservation.withdraw(amount);
// Measured, not assumed. If the sleeve delivered short, principal is
// no longer covered and the whole harvest is reverted.
uint256 preservationAfter = _preservationValue();
if (preservationAfter < principalSupplied) {
revert WouldBreachPrincipal(principalSupplied, preservationAfter);
}
usdc.forceApprove(address(growth), amount);
growth.deposit(amount);
harvestedToGrowth += amount;
uint256 growthNow = _growthValue();
if (growthNow > growthHighWater) growthHighWater = growthNow;
emit HarvestedToGrowth(amount, preservationAfter);
return amount;
}
/**
* @notice Unwinds the growth sleeve back into preservation.
*
* @dev Callable by a guardian at any time, and deliberately NOT pausable:
* a pause must never prevent moving money back to safety.
*/
function unwindGrowth(string calldata reason)
external
onlyRole(GUARDIAN_ROLE)
nonReentrant
returns (uint256)
{
return _unwindGrowth(reason);
}
/**
* @notice Trips the drawdown breaker if the growth sleeve has fallen far
* enough from its high-water mark.
*
* @dev Permissionless on purpose. A protective action that only an operator
* can trigger is a protective action that fails at 3am; anyone may
* call this, and it does nothing unless the threshold is genuinely met.
*/
function checkDrawdownBreaker() external nonReentrant returns (bool tripped) {
if (address(growth) == address(0)) return false;
if (growthHighWater == 0) return false;
uint256 current = _growthValue();
if (current >= growthHighWater) return false;
uint256 fallenBps = ((growthHighWater - current) * 10_000) / growthHighWater;
if (fallenBps < drawdownBreakerBps) return false;
uint256 recovered = _unwindGrowth("drawdown breaker");
emit DrawdownBreakerTripped(growthHighWater, current, recovered);
return true;
}
function _unwindGrowth(string memory reason) private returns (uint256) {
uint256 value = _growthValue();
if (value == 0) return 0;
uint256 before = usdc.balanceOf(address(this));
growth.withdraw(value);
uint256 recovered = usdc.balanceOf(address(this)) - before;
if (recovered > 0) {
usdc.forceApprove(address(preservation), recovered);
preservation.deposit(recovered);
}
growthHighWater = 0;
emit GrowthUnwound(recovered, reason);
return recovered;
}
// ----------------------------------------------------------------- admin
function setPreservationSleeve(address sleeve) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (sleeve == address(0)) revert ZeroAddress();
preservation = IYieldStrategy(sleeve);
emit PreservationSleeveSet(sleeve);
}
function setGrowthSleeve(address sleeve) external onlyRole(DEFAULT_ADMIN_ROLE) {
growth = IYieldStrategy(sleeve);
emit GrowthSleeveSet(sleeve);
}
function setMaxGrowthBps(uint256 bps) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (bps > MAX_GROWTH_BPS_LIMIT) revert BpsOutOfRange(bps, MAX_GROWTH_BPS_LIMIT);
maxGrowthBps = bps;
emit MaxGrowthBpsChanged(bps);
}
function setDrawdownBreakerBps(uint256 bps) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (bps == 0 || bps > 10_000) revert BpsOutOfRange(bps, 10_000);
drawdownBreakerBps = bps;
emit DrawdownBreakerChanged(bps);
}
function pause() external onlyRole(GUARDIAN_ROLE) {
_pause();
}
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
// ----------------------------------------------------------------- views
/// @notice True when principal is fully covered by the preservation sleeve.
function principalIsCovered() external view returns (bool) {
return _preservationValue() >= principalSupplied;
}
/**
* @inheritdoc IPrincipalProtected
* @dev True by construction: principal has one destination and only earned
* interest can ever reach the growth sleeve.
*/
function isPrincipalProtected() external pure override returns (bool) {
return true;
}
/// @inheritdoc IPrincipalProtected
function principalAtMarketRisk() external pure override returns (uint256) {
return 0;
}
function _preservationValue() private view returns (uint256) {
if (address(preservation) == address(0)) return 0;
return preservation.totalValue();
}
function _growthValue() private view returns (uint256) {
if (address(growth) == address(0)) return 0;
return growth.totalValue();
}
}
Bytecode hash 0x4c41b36a9814f832f0421795dc02b986904f1d948fe52011b266e72a061036b1
[
{
"type": "constructor",
"inputs": [
{
"name": "usdcAddress",
"type": "address",
"internalType": "address"
},
{
"name": "admin",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"name": "AccessControlBadConfirmation",
"type": "error",
"inputs": []
},
{
"name": "AccessControlUnauthorizedAccount",
"type": "error",
"inputs": [
{
"name": "account",
"type": "address",
"internalType": "address"
},
{
"name": "neededRole",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "BpsOutOfRange",
"type": "error",
"inputs": [
{
"name": "provided",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "maximum",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "EnforcedPause",
"type": "error",
"inputs": []
},
{
"name": "ExceedsGrowthCeiling",
"type": "error",
"inputs": [
{
"name": "requested",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "ceiling",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ExpectedPause",
"type": "error",
"inputs": []
},
{
"name": "NoGrowthSleeve",
"type": "error",
"inputs": []
},
{
"name": "NoPreservationSleeve",
"type": "error",
"inputs": []
},
{
"name": "ReentrancyGuardReentrantCall",
"type": "error",
"inputs": []
},
{
"name": "SafeERC20FailedOperation",
"type": "error",
"inputs": [
{
"name": "token",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "WithdrawalShortfall",
"type": "error",
"inputs": [
{
"name": "requested",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "delivered",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "WouldBreachPrincipal",
"type": "error",
"inputs": [
{
"name": "principalRequired",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "preservationAfter",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ZeroAddress",
"type": "error",
"inputs": []
},
{
"name": "ZeroAmount",
"type": "error",
"inputs": []
},
{
"name": "DrawdownBreakerChanged",
"type": "event",
"inputs": [
{
"name": "bps",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "DrawdownBreakerTripped",
"type": "event",
"inputs": [
{
"name": "highWater",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "current",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "recovered",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "GrowthSleeveSet",
"type": "event",
"inputs": [
{
"name": "sleeve",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "GrowthUnwound",
"type": "event",
"inputs": [
{
"name": "recovered",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "reason",
"type": "string",
"indexed": false,
"internalType": "string"
}
],
"anonymous": false
},
{
"name": "HarvestedToGrowth",
"type": "event",
"inputs": [
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "principalCovered",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "MaxGrowthBpsChanged",
"type": "event",
"inputs": [
{
"name": "bps",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Paused",
"type": "event",
"inputs": [
{
"name": "account",
"type": "address",
"indexed": false,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "PreservationSleeveSet",
"type": "event",
"inputs": [
{
"name": "sleeve",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "RoleAdminChanged",
"type": "event",
"inputs": [
{
"name": "role",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "previousAdminRole",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "newAdminRole",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
}
],
"anonymous": false
},
{
"name": "RoleGranted",
"type": "event",
"inputs": [
{
"name": "role",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "sender",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "RoleRevoked",
"type": "event",
"inputs": [
{
"name": "role",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "sender",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "Unpaused",
"type": "event",
"inputs": [
{
"name": "account",
"type": "address",
"indexed": false,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "DEFAULT_ADMIN_ROLE",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "GUARDIAN_ROLE",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "MAX_GROWTH_BPS_LIMIT",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "STRATEGIST_ROLE",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "VAULT_ROLE",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "accruedYield",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "checkDrawdownBreaker",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "tripped",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "deposit",
"type": "function",
"inputs": [
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "drawdownBreakerBps",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "getRoleAdmin",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "grantRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "growth",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract IYieldStrategy"
}
],
"stateMutability": "view"
},
{
"name": "growthHighWater",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "harvestToGrowth",
"type": "function",
"inputs": [
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "harvestedToGrowth",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "hasRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "isPrincipalProtected",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "pure"
},
{
"name": "marketName",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "maxGrowthBps",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "pause",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "paused",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "preservation",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract IYieldStrategy"
}
],
"stateMutability": "view"
},
{
"name": "principalAtMarketRisk",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "pure"
},
{
"name": "principalIsCovered",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "principalSupplied",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "renounceRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "callerConfirmation",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "revokeRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setDrawdownBreakerBps",
"type": "function",
"inputs": [
{
"name": "bps",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setGrowthSleeve",
"type": "function",
"inputs": [
{
"name": "sleeve",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setMaxGrowthBps",
"type": "function",
"inputs": [
{
"name": "bps",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setPreservationSleeve",
"type": "function",
"inputs": [
{
"name": "sleeve",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "supportsInterface",
"type": "function",
"inputs": [
{
"name": "interfaceId",
"type": "bytes4",
"internalType": "bytes4"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "totalValue",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "unpause",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "unwindGrowth",
"type": "function",
"inputs": [
{
"name": "reason",
"type": "string",
"internalType": "string"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "usdc",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract IERC20"
}
],
"stateMutability": "view"
},
{
"name": "withdraw",
"type": "function",
"inputs": [
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
}
]