Skip to content

Feat: Workflow for allocating new funds #195

Description

@gowthamsundaresan
// Fetch all Operator prospects + Operator Prospect Strategy (from /lat/operator-insights)
// Fetch the AVS with tvl for each strategy
// Fetch TVL ETH deposits (dTvlEth)
// Fetch TVL ETH withdrawals (wTvlEth)
// Calc restakeable TVL ETH as rTvlEth = dTvlEth - wTvlEth
//
// Create strategyOperators <strategy, operator[]>[]
// Create currently possible allocations (cpsa)
//     - check for first ever run: if lat's stakedTvl = 0, genesis = true
//     - in each record of strategyOperators, filter
//          - operators: (delegated === true) && (warningSev === null/0 || bias === true)
//          - strategies: (genesis) || ((warningSev === null/0) && (daysInPreference >= MIN_DAYS_IN_PREF) && (operators.length > 0))
//     - for the set of (strategy, operator)[] find allocation TVLs aTvlEth[] = r(rTvlEth, strategyOperator[]) such that
//          - aggregate P (WP) is maximised where WP = w(P[], aTvlEth[]) ie, simple weighting algorithm, where P = p(purity, variableApy, constantApy, bias, fees)
//          - the main consideration here is that the variableApy value reduces when tvl gets allocated to the strategy because ΔAPY = APY₀ × (TVL₀/(TVL₀ + ΔTVL) - 1) ie, there is dilution of apy for every $1 more tvl allocated to the strategy
//     - now we have cpsa = (strategyAddress, operatorAddress, aTvlEth)[] ordered by aTvlEth desc
//
// Create best case scenario allocations (bcsa) so that we can signal warn/pref to the OperatorProspectStrategy table
//     - in each record, filter
//          - operators: (delegated === true) && (warningSev === null/0 || bias === true)
//          - strategies: operators.length > 0
//     - follow same logic as above
//     - now we have bcsa = (strategyAddress, operatorAddress, aTvlEth)[] ordered by aTvlEth desc
//
// Construct pref/warn action set from bcsa
//     - in each record, if the strategy
//          - has aTvlEth === 0 [reset the pref and increase warn]
//              - if (daysInWarning >= MAX_DAYS_IN_WARN_SEV2) then (daysInWarning++ && warningSev = 1 && daysInPreference = 0 && sev1Strategies.push(strategy))
//              - else if (daysInWarning >= MAX_DAYS_IN_WARN_SEV3) then (daysInWarning++ && warningSev = 2 && daysInPreference = 0 && sev2Strategies.push(strategy))
//              - else if (daysInWarning || 0 < MAX_DAYS_IN_WARN_SEV3)) then (daysInWarning++ && warningSev = 3 && daysInPreference = 0 && sev3Strategies.push(strategy))
//          - has aTvlEth > 0 [reduce warn until zero then begin pref]
//              - if (daysInWarning - 1 >= MAX_DAYS_IN_WARN_SEV2) then (--daysInWarning && warningSev = 1)
//              - else if (daysInWarning - 1 >= MAX_DAYS_IN_WARN_SEV3) then (--daysInWarning && warningSev = 2)
//              - else if (daysInWarning - 1 < MAX_DAYS_IN_WARN_SEV3) then (--daysInWarning && warningSev = 3)
//              - else if (daysInWarning - 1 === 0 && warningSev != null) then (--daysInWarning && warningSev = null)
//              - else if (genesis) then (daysInPreference = MIN_DAYS_IN_PREF)
//              - else if (daysInPreference < MIN_DAYS_IN_PREF) then (daysInPreference++)
//      - now we have dbTransactions: any[]
//
// Construct withdrawals if wTvlEth > 0
//     - re-order cpsa such that we first reverse the list then move all sev1Strategies on top and then sev2Strategies after that (sev3Strategies will naturally fall next as they have 0 aTvl). note that we do not add any new strategies that are not alrady in cpsa
//     - now we have wPref = (strategyAddress, operator.nodeId)[]
//     - create dAvl = (depositStrategyAddress, tvl)[]
//     - create sAvl = (strategyAddress, operator.nodeId, tvl)[]
//     - create wR = (withdrawStrategyAddress, tvl)[]
//     - calc swaps
//         - allocate swaps from dAvl, SD = sd(wPref, dAvl, min(wTvlEth, dTvlEth))
//         - if rTvlEth < 0, allocate swaps from sAvl, SS = sd(wPref, sAvl, rTvlEth)
//             - sd() and ss() allocates such that we first spread all tvl equally amongst sev1Strategies, if filled -> sev2Strategies, if filled -> sev3Strategies, if filled -> each single strategy in order
//     - now we have
//         - SD = (ltAssetsToSwap[], ltAmountsToSwap[], ltAssetsToWithdraw[])
//         - SS = (nodeIds[], elAssetsToSwap[][], elAmountsToSwap[][], elAssetsToWithdraw[][])
//     - create W = w(SS, SD) such that W = (requestIds[], ltAssetsToSwap[], ltAmountsToSwap[], ltAssetsToWithdraw[], nodeIds[], elAssetsToSwap[][], elAmountsToSwap[][], elAssetsToWithdraw[][])
//
// Construct deposits if rTvlEth > 0
//     - create D = d(cpsa) such that D = (nodeIds[], assetsToSwap[][], amountsToSwap[][], assetsToStake[][])
//     - note: if there are not enough staked funds for withdrawals, it is because some funds are still under withdrawal or node undelegation
//
// Construct node withdrawals
//     - for each strategy in sev1Strategies, check (strategy.daysInWarning >= MAX_DAYS_IN_WARN_SEV1)
//         - if yes, push <nodeId, (asset, strategyTvl)> to nwList
//     - create NW = nw(nwList) such that NW = (nodeIds[], assets[][], amounts[][])
//
// if (dbTransactions) send updates via API
// if (wTvlEth > 0) propose withdrawals via swapAndSettleUserWithdrawals(W)
// if (rTvlEth > 0) propose deposits via swapAndStakeAssetsToNode(D)
// if (NW) propose node withdrawals via withdrawNodeAssets(NW)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions