GoatVault

Documentation for eth_defi.erc_4626.vault_protocol.goat.vault.GoatVault Python class.

class GoatVault

Bases: eth_defi.erc_4626.vault.ERC4626Vault

Goat protocol vaults.

  • An example vault

  • Each goat can have multiple strategies with each strategy with different unlocked and locked profit and loss

  • Multistrategy contract on Github

  • Fees are internalised into the share price of the strategy (similar as Yearn, Harvest).

Withdraw logic (from _withdraw function):

/// @notice Handles withdrawals from the contract.
///
/// This function performs the following actions:
/// - If the caller is not the owner, it checks and spends the allowance for the withdrawal.
/// - Ensures that the amount to be withdrawn is greater than zero.
/// - If the requested withdrawal amount exceeds the available balance, it withdraws the necessary amount from the strategies in the withdrawal order.
///   - Iterates through the withdrawal queue, withdrawing from each strategy until the balance requirement is met or the queue is exhausted.
///   - Updates the total debt of both the strategy and the contract as assets are withdrawn.
///   - Requests the strategy to report, accounting for potential gains or losses.
/// - Reverts if the withdrawal process does not result in sufficient balance.
/// - Burns the corresponding shares and transfers the requested assets to the receiver.
/// - Emits a `Withdraw` event with the caller, receiver, owner, amount of assets withdrawn, and shares burned.
///
/// @param _caller The address of the entity initiating the withdrawal.
/// @param _receiver The address of the recipient to receive the withdrawn assets.
/// @param _owner The address of the owner of the shares being withdrawn.
/// @param _assets The amount of assets to withdraw.
/// @param _shares The amount of shares to burn.
/// @param _consumeAllShares True if all `_shares` should be used to withdraw. False if it should withdraw just `_assets`.
/// @return The number of assets withdrawn and the shares burned as a result of the withdrawal.
function _withdraw(
    address _caller,
    address _receiver,
    address _owner,
    uint256 _assets,
    uint256 _shares,
    bool _consumeAllShares
) internal returns (uint256, uint256) {
    require(_shares > 0, Errors.ZeroAmount(_shares));

    if (_caller != _owner) {
        _spendAllowance(_owner, _caller, _shares);
    }

    uint256 assets = _consumeAllShares ? _convertToAssets(_shares, Math.Rounding.Floor) : _assets;

    if(assets > _balance()) {
        for(uint8 i = 0; i <= withdrawOrder.length; ++i){
            address strategy = withdrawOrder[i];

            // We reached the end of the withdraw queue and assets are still higher than the balance
            require(strategy != address(0), Errors.InsufficientBalance(assets, _balance()));

            // We can't withdraw from a strategy more than what it has asked as credit.
            uint256 assetsToWithdraw = Math.min(assets - _balance(), strategies[strategy].totalDebt);
            if(assetsToWithdraw == 0) continue;

            uint256 withdrawn = IStrategyAdapter(strategy).withdraw(assetsToWithdraw);
            strategies[strategy].totalDebt -= withdrawn;
            totalDebt -= withdrawn;

            IStrategyAdapter(strategy).askReport();

            // Update assets, as a loss could have been reported and user should get less assets for
            // the same amount of shares.
            if(_consumeAllShares) assets = _convertToAssets(_shares, Math.Rounding.Floor);
            if(assets <= _balance()) break;
        }
    }

    uint256 shares = _consumeAllShares ? _shares : _convertToShares(assets, Math.Rounding.Ceil);
    _burn(_owner, shares);
    IERC20(asset()).safeTransfer(_receiver, assets);

    emit Withdraw(_caller, _receiver, _owner, assets, shares);

    return (assets, shares);
}

Fee calculation (from _report function):

/// @notice Reports the performance of a strategy.
///
/// This function performs the following actions:
/// - Validates that the reporting strategy does not claim both a gain and a loss simultaneously.
/// - Checks that the strategy has sufficient tokens to cover the debt repayment and the gain.
/// - If there is a loss, it realizes the loss.
/// - Calculates and deducts the performance fee from the gain.
/// - Determines the excess debt of the strategy.
/// - Adjusts the strategy's and contract's total debt accordingly.
/// - Calculates and updates the new locked profit after accounting for any losses.
/// - Updates the reporting timestamps for the strategy and the contract.
/// - Transfers the debt repayment and the gains to this contract.
///
/// Emits a `StrategyReported` event.
///
/// @param _debtRepayment The amount of debt being repaid by the strategy.
/// @param _gain The amount of profit reported by the strategy.
/// @param _loss The amount of loss reported by the strategy.
function _report(uint256 _debtRepayment, uint256 _gain, uint256 _loss) internal {
    uint256 strategyBalance = IERC20(asset()).balanceOf(msg.sender);
    require(!(_gain > 0 && _loss > 0), Errors.GainLossMismatch());
    require(strategyBalance >= _debtRepayment + _gain, Errors.InsufficientBalance(strategyBalance, _debtRepayment + _gain));

    uint256 profit = 0;
    uint256 feesCollected = 0;
    if(_loss > 0) _reportLoss(msg.sender, _loss);
    if(_gain > 0) {
        strategies[msg.sender].totalGain += _gain;
        feesCollected = _gain.mulDiv(performanceFee, MAX_BPS);
        profit = _gain - feesCollected;
    }

    uint256 debtToRepay = Math.min(_debtRepayment, _debtExcess(msg.sender));
    if(debtToRepay > 0) {
        strategies[msg.sender].totalDebt -= debtToRepay;
        totalDebt -= debtToRepay;
    }

    uint256 newLockedProfit = _calculateLockedProfit() + profit;
    if(newLockedProfit > _loss) {
        lockedProfit = newLockedProfit - _loss;
    } else {
        lockedProfit = 0;
    }

    strategies[msg.sender].lastReport = block.timestamp;
    lastReport = block.timestamp;

    if(debtToRepay + _gain > 0) IERC20(asset()).safeTransferFrom(msg.sender, address(this), debtToRepay + _gain);
    if(feesCollected > 0) IERC20(asset()).safeTransfer(protocolFeeRecipient, feesCollected);

    emit StrategyReported(msg.sender, debtToRepay, profit, _loss);
}
Parameters
  • web3 – Connection we bind this instance to

  • spec – Chain, address tuple

  • token_cache

    Cache used with fetch_erc20_details() to avoid multiple calls to the same token.

    Reduces the number of RPC calls when scanning multiple vaults.

  • features – Pass vault feature flags along, externally detected.

  • default_block_identifier

    Override block identifier for on-chain metadata reads.

    When None, use get_safe_cached_latest_block_number() (the default, safe for broken RPCs). Set to "latest" for freshly deployed vaults whose contracts do not exist at the safe-cached block.

Attributes summary

address

Get the vault smart contract address.

chain_id

Chain this vault is on

denomination_token

Get the token which denominates the vault valuation

deposit_manager

Deposit manager assocaited with this vault

erc_7540

Is this ERC-7540 vault with asynchronous deposits.

flow_manager

Flow manager associated with this vault

info

Get info dictionary related to this vault deployment.

name

Vault name.

share_token

ERC-20 that presents vault shares.

symbol

Vault share token symbol

underlying_token

Alias for denomination_token()

vault_address

vault_address_checksumless

vault_contract

Get vault deployment.

Methods summary

__init__(web3, spec[, token_cache, ...])

param web3

fetch_denomination_token()

Read denomination token from onchain.

fetch_denomination_token_address()

Get the address for the denomination token.

fetch_deposit_closed_reason()

Check ERC-4626 maxDeposit to determine if deposits are closed.

fetch_deposit_next_open()

Generic ERC-4626 - no timing information available.

fetch_info()

Use info() property for cached access.

fetch_nav([block_identifier])

Fetch the most recent onchain NAV value.

fetch_pnl()

Fetch profit and loss from the vault.

fetch_portfolio(universe[, ...])

Read the current token balances of a vault.

fetch_redemption_closed_reason()

Check ERC-4626 maxRedeem to determine if redemptions are closed.

fetch_redemption_next_open()

Generic ERC-4626 - no timing information available.

fetch_share_price(block_identifier)

Get the current share price.

fetch_share_token()

Read share token details onchain.

fetch_share_token_address([block_identifier])

Get share token of this vault.

fetch_total_assets(block_identifier)

What is the total NAV of the vault.

fetch_total_supply(block_identifier)

What is the current outstanding shares.

fetch_vault_info()

Get all information we can extract from the vault smart contracts.

get_deposit_fee(block_identifier)

Deposit fee is set to zero by default as vaults usually do not have deposit fees.

get_deposit_manager()

Get deposit manager to deposit/redeem from the vault.

get_estimated_lock_up()

ERC-4626 vaults do not have a lock up by fault.

get_fee_data()

Get fee data structure for this vault.

get_fee_mode()

Get how this vault accounts its fees.

get_flags()

Get various vault state flags from the smart contract.

get_flow_manager()

Get flow manager to read indiviaul settle events.

get_historical_reader(stateful)

Get share price reader to fetch historical returns.

get_link([referral])

Get a link to the vault dashboard on its native site.

get_management_fee(block_identifier)

Internalised to the share price

get_notes()

Get a human readable message if we know somethign special is going on with this vault.

get_performance_fee(block_identifier)

Internalised to the share price

get_protocol_name()

Return the name of the vault protocol.

get_risk()

Get risk profile of this vault.

get_spec()

get_withdraw_fee(block_identifier)

Withdraw fee is set to zero by default as vaults usually do not have withdraw fees.

has_block_range_event_support()

Does this vault support block range-based event queries for deposits and redemptions.

has_custom_fees()

Deposit/withdrawal fees.

has_deposit_distribution_to_all_positions()

Deposits go automatically to all open positions.

is_valid()

Check if this vault is valid.

property vault_contract: web3.contract.contract.Contract

Get vault deployment.

fetch_pnl()

Fetch profit and loss from the vault.

* This function performs the following actions:
* - Iterates through the `withdrawOrder` array, which defines the order in which strategies are withdrawn from.
* - For each strategy in the `withdrawOrder`:
*   - If the strategy address is zero, it breaks the loop, indicating the end of the list.
*   - If the strategy has no debt, it skips to the next strategy.
*   - Otherwise, it retrieves the current profit and loss (PnL) from the strategy by calling `currentPnL`.
*   - Adds the strategy's profit to the total profit, after deducting the performance fee.
*   - Adds the strategy's loss to the total loss.
* - Returns the total profit and total loss across all active strategies.
*
* @return totalProfit The total profit across all active strategies, after deducting the performance fee.
* @return totalLoss The total loss across all active strategies.
Returns

(locked, unlocked) PnL amounts

Return type

tuple[decimal.Decimal, decimal.Decimal]

has_custom_fees()

Deposit/withdrawal fees.

Return type

bool

get_management_fee(block_identifier)

Internalised to the share price

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float

get_performance_fee(block_identifier)

Internalised to the share price

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float | None

get_estimated_lock_up()

ERC-4626 vaults do not have a lock up by fault.

Note

Because of so many protocol specific lockups, this must be explicitly set to zero.

Return type

datetime.timedelta

__init__(web3, spec, token_cache=None, features=None, default_block_identifier=None)
Parameters
  • web3 (web3.main.Web3) – Connection we bind this instance to

  • spec (eth_defi.vault.base.VaultSpec) – Chain, address tuple

  • token_cache (dict | None) –

    Cache used with fetch_erc20_details() to avoid multiple calls to the same token.

    Reduces the number of RPC calls when scanning multiple vaults.

  • features (set[eth_defi.erc_4626.core.ERC4626Feature] | None) – Pass vault feature flags along, externally detected.

  • default_block_identifier (Optional[Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]]) –

    Override block identifier for on-chain metadata reads.

    When None, use get_safe_cached_latest_block_number() (the default, safe for broken RPCs). Set to "latest" for freshly deployed vaults whose contracts do not exist at the safe-cached block.

property address: eth_typing.evm.HexAddress

Get the vault smart contract address.

property chain_id: int

Chain this vault is on

property denomination_token: eth_defi.token.TokenDetails | None

Get the token which denominates the vault valuation

  • Used in deposits and redemptions

  • Used in NAV calculation

  • Used in profit benchmarks

  • Usually USDC

Returns

Token wrapper instance.

Maybe None for broken vaults like https://arbiscan.io/address/0x9d0fbc852deccb7dcdd6cb224fa7561efda74411#code

property deposit_manager: eth_defi.vault.deposit_redeem.VaultDepositManager

Deposit manager assocaited with this vault

property erc_7540: bool

Is this ERC-7540 vault with asynchronous deposits.

  • For example previewDeposit() function and other functions will revert

fetch_denomination_token()

Read denomination token from onchain.

Use denomination_token() for cached access.

Return type

eth_defi.token.TokenDetails | None

fetch_denomination_token_address()

Get the address for the denomination token.

Triggers RCP call

Return type

Optional[eth_typing.evm.HexAddress]

fetch_deposit_closed_reason()

Check ERC-4626 maxDeposit to determine if deposits are closed.

Return type

str | None

fetch_deposit_next_open()

Generic ERC-4626 - no timing information available.

Return type

datetime.datetime | None

fetch_info()

Use info() property for cached access.

Returns

See LagoonVaultInfo

Return type

eth_defi.erc_4626.vault.ERC4626VaultInfo

fetch_nav(block_identifier=None)

Fetch the most recent onchain NAV value.

  • In the case of Lagoon, this is the last value written in the contract with updateNewTotalAssets() and ` settleDeposit()`

  • TODO: updateNewTotalAssets() there is no way to read pending asset update on chain

Returns

Vault NAV, denominated in denomination_token()

Return type

decimal.Decimal

fetch_portfolio(universe, block_identifier=None, allow_fallback=True)

Read the current token balances of a vault.

  • SHould be supported by all implementations

Parameters
Return type

eth_defi.vault.base.VaultPortfolio

fetch_redemption_closed_reason()

Check ERC-4626 maxRedeem to determine if redemptions are closed.

Return type

str | None

fetch_redemption_next_open()

Generic ERC-4626 - no timing information available.

Return type

datetime.datetime | None

fetch_share_price(block_identifier)

Get the current share price.

Returns

The share price in underlying token.

If supply is zero return zero.

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –

Return type

decimal.Decimal

fetch_share_token()

Read share token details onchain.

Use share_token() for cached access.

Return type

eth_defi.token.TokenDetails

fetch_share_token_address(block_identifier='latest')

Get share token of this vault.

  • Vault itself (ERC-4626)

  • share() accessor (ERc-7575)

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –

Return type

eth_typing.evm.HexAddress

fetch_total_assets(block_identifier)

What is the total NAV of the vault.

Example:

assert vault.denomination_token.symbol == "USDC"
assert vault.share_token.symbol == "ipUSDCfusion"
assert vault.fetch_total_assets(block_identifier=test_block_number) == Decimal("1437072.77357")
assert vault.fetch_total_supply(block_identifier=test_block_number) == Decimal("1390401.22652875")
Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –

Block number to read.

Use web3.eth.block_number for the last block.

Returns

The vault value in underlyinh token

Return type

decimal.Decimal | None

fetch_total_supply(block_identifier)

What is the current outstanding shares.

Example:

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –

Block number to read.

Use web3.eth.block_number for the last block.

Returns

The vault value in underlyinh token

Return type

decimal.Decimal

fetch_vault_info()

Get all information we can extract from the vault smart contracts.

Return type

eth_defi.erc_4626.vault.ERC4626VaultInfo

property flow_manager: eth_defi.vault.base.VaultFlowManager

Flow manager associated with this vault

get_deposit_fee(block_identifier)

Deposit fee is set to zero by default as vaults usually do not have deposit fees.

Internal: Use get_fee_data().

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float | None

get_deposit_manager()

Get deposit manager to deposit/redeem from the vault.

Return type

eth_defi.erc_4626.deposit_redeem.ERC4626DepositManager

get_fee_data()

Get fee data structure for this vault.

Raises

ValueError – In the case of broken or unimplemented fee reading methods in the smart contract

Return type

eth_defi.vault.fee.FeeData

get_fee_mode()

Get how this vault accounts its fees.

Return type

eth_defi.vault.fee.VaultFeeMode | None

get_flags()

Get various vault state flags from the smart contract.

  • Override to add status flags

  • Also add flags from our manual flag list in eth_defi.vault.flag

Returns

Flag set.

Do not modify in place.

Return type

set[eth_defi.vault.flag.VaultFlag]

get_flow_manager()

Get flow manager to read indiviaul settle events.

Return type

eth_defi.vault.base.VaultFlowManager

get_historical_reader(stateful)

Get share price reader to fetch historical returns.

Parameters

stateful – If True, use a stateful reading strategy.

Returns

None if unsupported

Return type

eth_defi.vault.base.VaultHistoricalReader

get_link(referral=None)

Get a link to the vault dashboard on its native site.

  • By default, give RouteScan link

Parameters

referral (str | None) – Optional referral code to append to the URL.

Returns

URL string

Return type

str

get_notes()

Get a human readable message if we know somethign special is going on with this vault.

Return type

str | None

get_protocol_name()

Return the name of the vault protocol.

Return type

str

get_risk()

Get risk profile of this vault.

Return type

eth_defi.vault.risk.VaultTechnicalRisk | None

get_withdraw_fee(block_identifier)

Withdraw fee is set to zero by default as vaults usually do not have withdraw fees.

Internal: Use get_fee_data().

Parameters

block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, int]) –

Return type

float

has_block_range_event_support()

Does this vault support block range-based event queries for deposits and redemptions.

  • If not we use chain balance polling-based approach

has_deposit_distribution_to_all_positions()

Deposits go automatically to all open positions.

  • Deposits do not land into the vault as cash

  • Instead, smart contracts automatically increase all open positions

  • The behaviour of Velvet Capital

property info: eth_defi.vault.base.VaultInfo

Get info dictionary related to this vault deployment.

  • Get cached data on the various vault parameters

Returns

Vault protocol specific information dictionary

is_valid()

Check if this vault is valid.

  • Call a known smart contract function to verify the function exists

Return type

bool

property name: str

Vault name.

property share_token: eth_defi.token.TokenDetails

ERC-20 that presents vault shares.

  • User gets shares on deposit and burns them on redemption

property symbol: str

Vault share token symbol

property underlying_token: eth_defi.token.TokenDetails

Alias for denomination_token()