feat: add feeConfig() fallback and LockedYvUSD locker metadata#367
feat: add feeConfig() fallback and LockedYvUSD locker metadata#367matheus1lva merged 4 commits intomainfrom
Conversation
yvUSD's accountant (LockedYvUSD) exposes fees via feeConfig() instead of the standard getVaultConfig()/defaultConfig() methods. Add feeConfig() as a third fallback in extractFeesBps so fees are read correctly if they change from the current 0/0. Also introduce extractLockerMeta() which probes cooldownDuration(), withdrawalWindow(), and feeConfig()[2] (lockerBonus) on the accountant. Returns undefined for non-locker accountants. Exposes the locker object in the vault snapshot and GraphQL schema. Closes #360
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
murderteeth
left a comment
There was a problem hiding this comment.
Overall: Approve with suggestions
The fee fallback chain and locker metadata extraction are correct and well-guarded. Two suggestions to tighten up the RPC footprint.
1. Gate extractLockerMeta on whether the accountant is also a vault
packages/ingest/abis/yearn/3/vault/snapshot/hook.ts:106
Currently extractLockerMeta runs for every vault with a non-zero accountant, adding 3 RPC probes that revert for standard accountants. Since LockedYvUSD is itself a vault, you can gate on that:
const locker = snapshot.accountant && snapshot.accountant !== zeroAddress
&& await things.exist(chainId, snapshot.accountant, 'vault')
? await extractLockerMeta(chainId, snapshot.accountant)
: undefinedZero extra RPCs for non-locker vaults, no hardcoded addresses.
2. Use multicall in extractLockerMeta
packages/ingest/abis/yearn/3/vault/snapshot/hook.ts:525
The three separate readContract calls can be a single multicall:
async function extractLockerMeta(chainId: number, accountant: `0x${string}`) {
const results = await rpcs.next(chainId).multicall({
contracts: [
{ address: accountant, abi: parseAbi(['function cooldownDuration() view returns (uint256)']), functionName: 'cooldownDuration' },
{ address: accountant, abi: parseAbi(['function withdrawalWindow() view returns (uint256)']), functionName: 'withdrawalWindow' },
{ address: accountant, abi: parseAbi(['function feeConfig() view returns (uint16, uint16, uint16)']), functionName: 'feeConfig' },
],
allowFailure: true,
})
if (results[0].status === 'failure') return undefined
return {
cooldownDuration: Number(results[0].result),
withdrawalWindow: Number(results[1].result),
lockerBonus: results[2].status === 'success' ? results[2].result[2] : 0,
}
}One RPC call instead of three.
murderteeth
left a comment
There was a problem hiding this comment.
Updating previous review — these should be request for changes, not approve.
1. Gate extractLockerMeta on whether the accountant is also a vault
packages/ingest/abis/yearn/3/vault/snapshot/hook.ts:106
Currently extractLockerMeta runs for every vault with a non-zero accountant, adding 3 RPC probes that revert for standard accountants. Since LockedYvUSD is itself a vault, you can gate on that:
const locker = snapshot.accountant && snapshot.accountant !== zeroAddress
&& await things.exist(chainId, snapshot.accountant, 'vault')
? await extractLockerMeta(chainId, snapshot.accountant)
: undefinedZero extra RPCs for non-locker vaults, no hardcoded addresses.
2. Use multicall in extractLockerMeta
packages/ingest/abis/yearn/3/vault/snapshot/hook.ts:525
The three separate readContract calls can be a single multicall:
async function extractLockerMeta(chainId: number, accountant: `0x${string}`) {
const results = await rpcs.next(chainId).multicall({
contracts: [
{ address: accountant, abi: parseAbi(['function cooldownDuration() view returns (uint256)']), functionName: 'cooldownDuration' },
{ address: accountant, abi: parseAbi(['function withdrawalWindow() view returns (uint256)']), functionName: 'withdrawalWindow' },
{ address: accountant, abi: parseAbi(['function feeConfig() view returns (uint16, uint16, uint16)']), functionName: 'feeConfig' },
],
allowFailure: true,
})
if (results[0].status === 'failure') return undefined
return {
cooldownDuration: Number(results[0].result),
withdrawalWindow: Number(results[1].result),
lockerBonus: results[2].status === 'success' ? results[2].result[2] : 0,
}
}One RPC call instead of three.
murderteeth
left a comment
There was a problem hiding this comment.
Previous suggestions (vault gate + multicall) are addressed in 9837bc2.
Lint errors in extractLockerMeta
packages/ingest/abis/yearn/3/vault/snapshot/hook.ts:531-543
The contract objects in the multicall array use 8-space indentation but the linter expects 10. This causes 9 indent errors and fails bun --filter ingest lint. Fix the indentation inside each contract object in the contracts array:
const [cooldownDuration, withdrawalWindow, feeConfig] = await rpcs.next(chainId).multicall({
contracts: [
{
address: accountant,
abi: parseAbi(['function cooldownDuration() view returns (uint256)']),
functionName: 'cooldownDuration',
},
{
address: accountant,
abi: parseAbi(['function withdrawalWindow() view returns (uint256)']),
functionName: 'withdrawalWindow',
},
{
address: accountant,
abi: parseAbi(['function feeConfig() view returns (uint16, uint16, uint16)']),
functionName: 'feeConfig',
},
],
allowFailure: true,
})
Lint fixed! |
Summary
yvUSD's accountant (LockedYvUSD) exposes fees via
feeConfig()instead of the standardgetVaultConfig(vault)/defaultConfig()methods. Kong'sextractFeesBpssilently fell through to 0/0 when both standard calls reverted. This addsfeeConfig()as a third fallback so fees are read correctly if they ever change.Also indexes LockedYvUSD-specific metadata (
cooldownDuration,withdrawalWindow,lockerBonus) from the accountant and exposes it as alockerfield on the vault snapshot and GraphQL schema.Closes #360
How to review
packages/ingest/abis/yearn/3/vault/snapshot/hook.tsextractFeesBps()now triesdefaultConfig()and thenfeeConfig()before falling backextractLockerMeta()helper readscooldownDuration(),withdrawalWindow(), andfeeConfig()[2]process()now includeslockerin the returned snapshot hookpackages/web/app/api/gql/typeDefs/vault.tsLockerGraphQL typelocker: LockertoVaultManual validation
Expected:
accountant()returns0xAaaFEa48472f77563961Cdb53291DEDfB46F9040feeConfig()returns0 0 1000cooldownDuration()returns1209600withdrawalWindow()returns432000getVaultConfig(...)revertsdefaultConfig()revertsconfig/abis.local.yamlbefore starting Kong so the local process boots with the narrowed ABI set.config/abis.local.yamlis a full local override forconfig/abis.yaml, so keep it minimal and delete it when you are done validating.Wait until the app is serving successfully and
/api/mqresponds.In the terminal UI:
Ingestfanout abisThis fanout run will now use
config/abis.local.yaml, so it should only queue work for the yvUSD vault and the LockedYvUSD accountant.Expected:
{ "address": "0x696d02Db93291651ED510704c9b286841d506987", "name": "USD yVault", "symbol": "yvUSD", "accountant": "0xAaaFEa48472f77563961Cdb53291DEDfB46F9040", "fees": { "managementFee": 0, "performanceFee": 0 }, "locker": { "cooldownDuration": 1209600, "withdrawalWindow": 432000, "lockerBonus": 1000 } }The important part is that
feesare still resolved correctly even though the accountant does not implement the standardgetVaultConfig(vault)/defaultConfig()interface, and that the locker-specific fields are now exposed on the vault response.Note: issue
#360listedwithdrawalWindow = 604800, but the live contract currently returns432000. The implementation is intentionally following the on-chain value.If you want to go back to the full ABI set in the same local environment, restart Kong after removing the file.
Risk / impact
extractLockerMeta()is additive and returnsundefinedfor non-locker accountantsfeeConfig()fallback only runs when both standard accountant methods revert, so existing standard accountants keep their current behavior