From 28e7a118b08987f60b16509f0ef4dfd5ead351c7 Mon Sep 17 00:00:00 2001 From: Ross Date: Wed, 14 Jan 2026 22:45:11 -0500 Subject: [PATCH 1/3] add apy trace debugging tool --- processes/apr/apy_trace.go | 178 +++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 processes/apr/apy_trace.go diff --git a/processes/apr/apy_trace.go b/processes/apr/apy_trace.go new file mode 100644 index 00000000..9dcd1c89 --- /dev/null +++ b/processes/apr/apy_trace.go @@ -0,0 +1,178 @@ +package apr + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +// APY trace logging helper (opt-in via env vars): +// +// - Enable tracing: +// APY_TRACE=1 +// +// - Write to a dedicated file (optional): +// APY_TRACE_PATH=/tmp/apy_trace.log +// +// - If this is a relative path, it is relative to the process working directory. +// +// - If not set, output goes to stderr. +// +// - Filter by chain, vault, and/or strategy (comma-separated): +// APY_TRACE_CHAIN=1,10 +// APY_TRACE_VAULT=0xabc...,0xdef... +// APY_TRACE_STRATEGY=0x123...,0x456... +// +// Notes: +// - Filters are case-insensitive and address strings should be hex with 0x prefix. +// - If a filter is set, only matching entries are emitted. +type apyTraceConfig struct { + enabled bool + path string + chains map[string]struct{} + vaults map[string]struct{} + strategies map[string]struct{} +} + +var ( + apyTraceConfigOnce sync.Once + apyTraceCfg apyTraceConfig + + apyTraceFileOnce sync.Once + apyTraceFile *os.File + apyTraceFileErr error + apyTraceFileWarn sync.Once + + apyTraceWriteMu sync.Mutex +) + +func apyTrace(scope string, chainID uint64, vaultAddr common.Address, strategyAddr common.Address, step string, value interface{}) { + cfg := apyTraceLoadConfig() + if !apyTraceMatches(cfg, chainID, vaultAddr, strategyAddr) { + return + } + + line := fmt.Sprintf( + "%s APY_TRACE scope=%s chain=%d vault=%s strategy=%s step=%s value=%v", + time.Now().UTC().Format(time.RFC3339Nano), + scope, + chainID, + vaultAddr.Hex(), + strategyAddr.Hex(), + step, + value, + ) + + if cfg.path != "" { + if file, err := apyTraceOpenFile(cfg.path); err == nil && file != nil { + apyTraceWriteMu.Lock() + fmt.Fprintln(file, line) + apyTraceWriteMu.Unlock() + return + } + } + + fmt.Fprintln(os.Stderr, line) +} + +func apyTraceLoadConfig() apyTraceConfig { + apyTraceConfigOnce.Do(func() { + enabled := false + if value, ok := os.LookupEnv("APY_TRACE"); ok { + enabled = apyTraceParseBool(value) + } else if _, ok := os.LookupEnv("APY_TRACE_PATH"); ok { + enabled = true + } + + apyTraceCfg = apyTraceConfig{ + enabled: enabled, + path: strings.TrimSpace(os.Getenv("APY_TRACE_PATH")), + chains: apyTraceParseList("APY_TRACE_CHAIN"), + vaults: apyTraceParseList("APY_TRACE_VAULT"), + strategies: apyTraceParseList("APY_TRACE_STRATEGY"), + } + }) + + return apyTraceCfg +} + +func apyTraceParseBool(value string) bool { + if strings.TrimSpace(value) == "" { + return true + } + switch strings.ToLower(strings.TrimSpace(value)) { + case "1", "true", "t", "yes", "y", "on": + return true + default: + return false + } +} + +func apyTraceParseList(envKey string) map[string]struct{} { + raw := strings.TrimSpace(os.Getenv(envKey)) + if raw == "" { + return nil + } + + items := strings.Split(raw, ",") + values := make(map[string]struct{}, len(items)) + for _, item := range items { + value := strings.ToLower(strings.TrimSpace(item)) + if value == "" { + continue + } + values[value] = struct{}{} + } + + return values +} + +func apyTraceMatches(cfg apyTraceConfig, chainID uint64, vaultAddr common.Address, strategyAddr common.Address) bool { + if !cfg.enabled { + return false + } + + if len(cfg.chains) > 0 { + chainKey := strings.ToLower(strconv.FormatUint(chainID, 10)) + if _, ok := cfg.chains[chainKey]; !ok { + return false + } + } + + if len(cfg.vaults) > 0 { + vaultKey := strings.ToLower(vaultAddr.Hex()) + if _, ok := cfg.vaults[vaultKey]; !ok { + return false + } + } + + if len(cfg.strategies) > 0 { + strategyKey := strings.ToLower(strategyAddr.Hex()) + if _, ok := cfg.strategies[strategyKey]; !ok { + return false + } + } + + return true +} + +func apyTraceOpenFile(path string) (*os.File, error) { + apyTraceFileOnce.Do(func() { + if path == "" { + return + } + apyTraceFile, apyTraceFileErr = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if apyTraceFileErr != nil { + apyTraceFileWarn.Do(func() { + fmt.Fprintf(os.Stderr, "%s APY_TRACE error opening %s: %v\n", time.Now().UTC().Format(time.RFC3339Nano), path, apyTraceFileErr) + }) + } + }) + + return apyTraceFile, apyTraceFileErr +} From b72792882fe0ec739d9bc3e485edf4169f158c76 Mon Sep 17 00:00:00 2001 From: Ross Date: Wed, 14 Jan 2026 22:45:31 -0500 Subject: [PATCH 2/3] update frax and convex logic --- common/contracts/convexStakingToken.go | 39 +++++++++++++++++++ common/contracts/convexUserVault.go | 38 +++++++++++++++++++ common/contracts/fraxBaseStrategy.go | 38 +++++++++++++++++++ processes/apr/forward.convex.go | 2 +- processes/apr/forward.convex.helpers.go | 50 ++++++++++++++++++++++++- 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 common/contracts/convexStakingToken.go create mode 100644 common/contracts/convexUserVault.go create mode 100644 common/contracts/fraxBaseStrategy.go diff --git a/common/contracts/convexStakingToken.go b/common/contracts/convexStakingToken.go new file mode 100644 index 00000000..3003bc9b --- /dev/null +++ b/common/contracts/convexStakingToken.go @@ -0,0 +1,39 @@ +package contracts + +import ( + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +// ConvexStakingTokenABI is the ABI used to query convexPoolId() on a Convex staking token. +const ConvexStakingTokenABI = "[{\"inputs\":[],\"name\":\"convexPoolId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// ConvexStakingToken is a minimal binding for convexPoolId() on a Convex staking token. +type ConvexStakingToken struct { + contract *bind.BoundContract +} + +// NewConvexStakingToken creates a minimal caller for stakingToken.convexPoolId(). +func NewConvexStakingToken(address common.Address, backend bind.ContractBackend) (*ConvexStakingToken, error) { + parsed, err := abi.JSON(strings.NewReader(ConvexStakingTokenABI)) + if err != nil { + return nil, err + } + contract := bind.NewBoundContract(address, parsed, backend, backend, backend) + return &ConvexStakingToken{contract: contract}, nil +} + +// ConvexPoolId calls convexPoolId() on a staking token. +func (c *ConvexStakingToken) ConvexPoolId(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := c.contract.Call(opts, &out, "convexPoolId") + if err != nil { + return *new(*big.Int), err + } + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + return out0, err +} diff --git a/common/contracts/convexUserVault.go b/common/contracts/convexUserVault.go new file mode 100644 index 00000000..d3bf7fa9 --- /dev/null +++ b/common/contracts/convexUserVault.go @@ -0,0 +1,38 @@ +package contracts + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +// ConvexUserVaultABI is the ABI used to query stakingToken() on a Convex user vault. +const ConvexUserVaultABI = "[{\"inputs\":[],\"name\":\"stakingToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// ConvexUserVault is a minimal binding for stakingToken() on a Convex user vault. +type ConvexUserVault struct { + contract *bind.BoundContract +} + +// NewConvexUserVault creates a minimal caller for userVault.stakingToken(). +func NewConvexUserVault(address common.Address, backend bind.ContractBackend) (*ConvexUserVault, error) { + parsed, err := abi.JSON(strings.NewReader(ConvexUserVaultABI)) + if err != nil { + return nil, err + } + contract := bind.NewBoundContract(address, parsed, backend, backend, backend) + return &ConvexUserVault{contract: contract}, nil +} + +// StakingToken calls stakingToken() on a user vault. +func (c *ConvexUserVault) StakingToken(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := c.contract.Call(opts, &out, "stakingToken") + if err != nil { + return *new(common.Address), err + } + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + return out0, err +} diff --git a/common/contracts/fraxBaseStrategy.go b/common/contracts/fraxBaseStrategy.go new file mode 100644 index 00000000..607eb07c --- /dev/null +++ b/common/contracts/fraxBaseStrategy.go @@ -0,0 +1,38 @@ +package contracts + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +// FraxBaseStrategyABI is the ABI used to query userVault() on Frax base strategies. +const FraxBaseStrategyABI = "[{\"inputs\":[],\"name\":\"userVault\",\"outputs\":[{\"internalType\":\"contract IConvexFrax\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// FraxBaseStrategy is a minimal binding for userVault() on Frax base strategies. +type FraxBaseStrategy struct { + contract *bind.BoundContract +} + +// NewFraxBaseStrategy creates a minimal caller for fraxBaseStrategy.userVault(). +func NewFraxBaseStrategy(address common.Address, backend bind.ContractBackend) (*FraxBaseStrategy, error) { + parsed, err := abi.JSON(strings.NewReader(FraxBaseStrategyABI)) + if err != nil { + return nil, err + } + contract := bind.NewBoundContract(address, parsed, backend, backend, backend) + return &FraxBaseStrategy{contract: contract}, nil +} + +// UserVault calls userVault() on a Frax base strategy. +func (f *FraxBaseStrategy) UserVault(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := f.contract.Call(opts, &out, "userVault") + if err != nil { + return *new(common.Address), err + } + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + return out0, err +} diff --git a/processes/apr/forward.convex.go b/processes/apr/forward.convex.go index 92ac5f5c..8b971a98 100644 --- a/processes/apr/forward.convex.go +++ b/processes/apr/forward.convex.go @@ -44,7 +44,7 @@ func calculateConvexForwardAPY(args TCalculateConvexAPYDataStruct) TStrategyAPY ** of CVX printed, based on the CRV rate for the given gauge. Tldr X crv = Y cvx and we do ** something to gt an APR. **********************************************************************************************/ - crvAPR, cvxAPR, crvAPY, cvxAPY := getCVXPoolAPY(chainID, args.strategy.Address, args.baseAssetPrice) + crvAPR, cvxAPR, crvAPY, cvxAPY := getCVXPoolAPY(chainID, args.vault.Address, args.strategy.Address, args.baseAssetPrice) /********************************************************************************************** ** Just like curve, Convex can have extra rewards which are incentives/bribes on top of the diff --git a/processes/apr/forward.convex.helpers.go b/processes/apr/forward.convex.helpers.go index 24eaed94..52d0e75e 100644 --- a/processes/apr/forward.convex.helpers.go +++ b/processes/apr/forward.convex.helpers.go @@ -130,9 +130,11 @@ func getCVXForCRV(chainID uint64, crvEarned *bigNumber.Float) *bigNumber.Float { **************************************************************************************************/ func getCVXPoolAPY( chainID uint64, + vaultAddress common.Address, strategyAddress common.Address, virtualPoolPrice *bigNumber.Float, ) (*bigNumber.Float, *bigNumber.Float, *bigNumber.Float, *bigNumber.Float) { + crvAPR := bigNumber.NewFloat(0) cvxAPR := bigNumber.NewFloat(0) crvAPY := bigNumber.NewFloat(0) @@ -157,11 +159,55 @@ func getCVXPoolAPY( if err != nil { rewardPID, err = convexStrategyContract.ID(nil) if err != nil { - rewardPID, err = convexStrategyContract.FraxPid(nil) + warnMissingPID := func(pidErr error) { + logs.Warning("Convex PID not found for vault "+vaultAddress.Hex()+" strategy "+strategyAddress.Hex(), pidErr) + } + fraxBaseStrategy, err := contracts.NewFraxBaseStrategy(strategyAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init fraxBaseStrategy for convex strategy `+strategyAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + userVaultAddress, err := fraxBaseStrategy.UserVault(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get userVault for fraxBaseStrategy `+strategyAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + userVaultContract, err := contracts.NewConvexUserVault(userVaultAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init userVault contract `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + stakingTokenAddress, err := userVaultContract.StakingToken(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get stakingToken for userVault `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + stakingTokenContract, err := contracts.NewConvexStakingToken(stakingTokenAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init stakingToken contract `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + rewardPID, err = stakingTokenContract.ConvexPoolId(nil) if err != nil { if os.Getenv("ENVIRONMENT") == "dev" { - logs.Error(`Unable to get reward PID for convex strategy ` + strategyAddress.Hex()) + logs.Error(`Unable to get cvxPoolId for stakingToken `+stakingTokenAddress.Hex(), err) } + warnMissingPID(err) return crvAPR, cvxAPR, crvAPY, cvxAPY } } From 5747abc83f0c17f6f1e8e88f1bb51957c99bb458 Mon Sep 17 00:00:00 2001 From: Ross Date: Fri, 16 Jan 2026 13:03:16 -0500 Subject: [PATCH 3/3] add PID fix to additional function and add debugging --- apy_trace.log | 128 +++++ apy_trace_human_commented.md | 704 ++++++++++++++++++++++++ processes/apr/forward.convex.go | 58 +- processes/apr/forward.convex.helpers.go | 77 ++- 4 files changed, 958 insertions(+), 9 deletions(-) create mode 100644 apy_trace.log create mode 100644 apy_trace_human_commented.md diff --git a/apy_trace.log b/apy_trace.log new file mode 100644 index 00000000..0d9e8a73 --- /dev/null +++ b/apy_trace.log @@ -0,0 +1,128 @@ +0xc52d44abA4B7739173821afF175aEb53367E629E = Curve ZUSDFBP3CRV-f Factory + +2026-01-16T02:25:16.179592683Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.gaugeAddress value=0x218E4678318ab5527e41135713193E5EAd73337f +2026-01-16T02:25:16.179774065Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.baseAssetPrice value=0.018355142896898657 +2026-01-16T02:25:16.179814591Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.poolPrice value=1.0110440176923634 +2026-01-16T02:25:16.179830221Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.baseAPY value=3.851825336994172 +2026-01-16T02:25:16.17984055Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.rewardAPY value=0 +2026-01-16T02:25:16.179852763Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.poolWeeklyAPY value=0.0001 +2026-01-16T02:25:16.23280686Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxBoost value=2.063936749763785 +2026-01-16T02:25:16.232904374Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=keepCrv value=0 +2026-01-16T02:25:16.232923109Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=debtRatio value=0.8 +2026-01-16T02:25:16.232937406Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=vaultPerformanceFee value=0.1 +2026-01-16T02:25:16.232950491Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=vaultManagementFee value=0 +2026-01-16T02:25:16.232958766Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=oneMinusPerfFee value=0.9 +2026-01-16T02:25:16.23297156Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=input.virtualPoolPrice value=0.018355142896898657 +2026-01-16T02:25:16.309182083Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=fraxBaseStrategy.userVault value=0xAB919492C39263e97D4190b9bb0d2437D15EAcC9 +2026-01-16T02:25:16.361947274Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=userVault.stakingToken value=0xFD2d7847E0f450d8B00d3D697D720C687E622a7B +2026-01-16T02:25:16.403605338Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=stakingToken.cvxPoolId value=196 +2026-01-16T02:25:16.403746113Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=rewardPID value=196 +2026-01-16T02:25:16.460728901Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=poolInfo.crvRewards value=0xFd3A7636694259b32B3896f59436997AD25380cA +2026-01-16T02:25:16.542193862Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=rewardRateRaw value=45048708159937 +2026-01-16T02:25:16.542301134Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=totalSupplyRaw value=317120356635973553634 +2026-01-16T02:25:16.54233596Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=rewardRate value=4.5048708159937e-05 +2026-01-16T02:25:16.542351369Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=totalSupply value=317.12035663597356 +2026-01-16T02:25:16.542364133Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=virtualSupply value=5.820789461568759 +2026-01-16T02:25:16.542377368Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvPerUnderlying value=7.739278057962252e-06 +2026-01-16T02:25:16.54238902Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvPerUnderlyingPerYear value=244.06587283589755 +2026-01-16T02:25:16.579662594Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxPerYear value=0.1322365676305354 +2026-01-16T02:25:16.579759267Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvPrice value=0.433265 +2026-01-16T02:25:16.579785967Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxPrice value=2.019843 +2026-01-16T02:25:16.579808569Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvAPR value=105.74520039424516 +2026-01-16T02:25:16.579823618Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxAPR value=0.2670971054725635 +2026-01-16T02:25:16.579838366Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvAPRFloat64 value=105.74520039424516 +2026-01-16T02:25:16.579852081Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxAPRFloat64 value=0.2670971054725635 +2026-01-16T02:25:16.579866779Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvAPY value=105.74520039424516 +2026-01-16T02:25:16.579880725Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxAPY value=0.2670971054725635 +2026-01-16T02:25:16.579893359Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.crvAPR value=105.74520039424516 +2026-01-16T02:25:16.579915621Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.cvxAPR value=0.2670971054725635 +2026-01-16T02:25:16.57993082Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.crvAPY value=105.74520039424516 +2026-01-16T02:25:16.579947812Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.cvxAPY value=0.2670971054725635 +2026-01-16T02:25:16.579982637Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvAPR value=105.74520039424516 +2026-01-16T02:25:16.579995522Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxAPR value=0.2670971054725635 +2026-01-16T02:25:16.580006863Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=crvAPY value=105.74520039424516 +2026-01-16T02:25:16.580016661Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=cvxAPY value=0.2670971054725635 +2026-01-16T02:25:16.896372183Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=rewardsAPY value=0 +2026-01-16T02:25:16.896471009Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=keepCRVRatio value=1 +2026-01-16T02:25:16.896500294Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=grossAPY.afterKeepCRV value=105.74520039424516 +2026-01-16T02:25:16.896538296Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=grossAPY.afterRewards value=105.74520039424516 +2026-01-16T02:25:16.896548675Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=grossAPY.afterCvx value=106.01229749971772 +2026-01-16T02:25:16.896560518Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=netAPY.preMgmt value=95.41106774974595 +2026-01-16T02:25:16.896573011Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=netAPY.postMgmt value=95.41106774974595 +2026-01-16T02:25:16.896581788Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=netAPY.netAPRFloat64 value=95.41106774974595 +2026-01-16T02:25:16.896593239Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=netAPY.compounded value=3.3993111000835647e+23 +2026-01-16T02:25:16.896602928Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=netAPY.withPool value=3.3993111000835647e+23 +2026-01-16T02:25:16.896615091Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.netAPY value=3.3993111000835647e+23 +2026-01-16T02:25:16.89662534Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.netAPYWithDebtRatio value=2.7194488800668517e+23 +2026-01-16T02:25:16.896635188Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.boostWithDebtRatio value=1.651149399811028 +2026-01-16T02:25:16.896647371Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.poolAPYWithDebtRatio value=8e-05 +2026-01-16T02:25:16.896661809Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.boostedAPRWithDebtRatio value=84.59616031539613 +2026-01-16T02:25:16.896709378Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.baseAPRWithDebtRatio value=3.081460269595338 +2026-01-16T02:25:16.89672634Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.cvxAPRWithDebtRatio value=0.2136776843780508 +2026-01-16T02:25:16.8967367Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.rewardsAPYWithDebtRatio value=0 +2026-01-16T02:25:16.896746719Z APY_TRACE scope=forward.convex chain=1 vault=0xc52d44abA4B7739173821afF175aEb53367E629E strategy=0x1161B0bA29a88F014f1c8C1aC94498926283aAE0 step=result.apyStruct value={convex 0.8 2.7194488800668517e+23 {1.651149399811028 8e-05 84.59616031539613 3.081460269595338 0.2136776843780508 0 0 }} + +------------------------- +0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac = Curve XAI-FRAXBP Factory + +2026-01-16T02:25:23.077895672Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.gaugeAddress value=0xa8Ea11465A1375BF42463C3B613dFC54248b9C7B +2026-01-16T02:25:23.077990611Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.baseAssetPrice value=0.06417867250402262 +2026-01-16T02:25:23.07801657Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.poolPrice value=1.0080959363340094 +2026-01-16T02:25:23.07803256Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.baseAPY value=0.2975187046483139 +2026-01-16T02:25:23.078046937Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.rewardAPY value=0 +2026-01-16T02:25:23.078058428Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.poolWeeklyAPY value=0.0002 +2026-01-16T02:25:23.133872385Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxBoost value=2.5 +2026-01-16T02:25:23.133955291Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=keepCrv value=0 +2026-01-16T02:25:23.133985658Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=debtRatio value=0.8 +2026-01-16T02:25:23.134001077Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=vaultPerformanceFee value=0.1 +2026-01-16T02:25:23.134017308Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=vaultManagementFee value=0 +2026-01-16T02:25:23.134032877Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=oneMinusPerfFee value=0.9 +2026-01-16T02:25:23.134045591Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=input.virtualPoolPrice value=0.06417867250402262 +2026-01-16T02:25:23.210495494Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=fraxBaseStrategy.userVault value=0x9628d5f254B010C0122440F282E1fB6b0f25239c +2026-01-16T02:25:23.264065383Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=userVault.stakingToken value=0x19f0a60f4635d3E2c48647822Eda5332BA094fd3 +2026-01-16T02:25:23.303950768Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=stakingToken.cvxPoolId value=129 +2026-01-16T02:25:23.304167456Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=rewardPID value=129 +2026-01-16T02:25:23.34654532Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=poolInfo.crvRewards value=0x4a866fE20A442Dff55FAA010684A5C1379151458 +2026-01-16T02:25:23.400397714Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=rewardRateRaw value=36068329259793 +2026-01-16T02:25:23.400500387Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=totalSupplyRaw value=822828886434854500906 +2026-01-16T02:25:23.400534722Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=rewardRate value=3.6068329259793e-05 +2026-01-16T02:25:23.400559899Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=totalSupply value=822.8288864348544 +2026-01-16T02:25:23.400577222Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=virtualSupply value=52.80806562935214 +2026-01-16T02:25:23.400590818Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvPerUnderlying value=6.830079615668645e-07 +2026-01-16T02:25:23.400607289Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvPerUnderlyingPerYear value=21.539339075972638 +2026-01-16T02:25:23.444294752Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxPerYear value=0.011670161974476423 +2026-01-16T02:25:23.44442653Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvPrice value=0.433265 +2026-01-16T02:25:23.444456065Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxPrice value=2.019843 +2026-01-16T02:25:23.444473959Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvAPR value=9.332241744751284 +2026-01-16T02:25:23.444487825Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxAPR value=0.02357189497301238 +2026-01-16T02:25:23.444502864Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvAPRFloat64 value=9.332241744751284 +2026-01-16T02:25:23.444515477Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxAPRFloat64 value=0.02357189497301238 +2026-01-16T02:25:23.444525817Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvAPY value=9.332241744751284 +2026-01-16T02:25:23.444562326Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxAPY value=0.02357189497301238 +2026-01-16T02:25:23.444572565Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.crvAPR value=9.332241744751284 +2026-01-16T02:25:23.444583465Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.cvxAPR value=0.02357189497301238 +2026-01-16T02:25:23.444598604Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.crvAPY value=9.332241744751284 +2026-01-16T02:25:23.444611548Z APY_TRACE scope=forward.convex.cvxPool chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.cvxAPY value=0.02357189497301238 +2026-01-16T02:25:23.444624362Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvAPR value=9.332241744751284 +2026-01-16T02:25:23.444636125Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxAPR value=0.02357189497301238 +2026-01-16T02:25:23.444647005Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=crvAPY value=9.332241744751284 +2026-01-16T02:25:23.444658637Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=cvxAPY value=0.02357189497301238 +2026-01-16T02:25:23.700515339Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=rewardsAPY value=0 +2026-01-16T02:25:23.700671944Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=keepCRVRatio value=1 +2026-01-16T02:25:23.70071208Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=grossAPY.afterKeepCRV value=9.332241744751284 +2026-01-16T02:25:23.700769448Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=grossAPY.afterRewards value=9.332241744751284 +2026-01-16T02:25:23.700793964Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=grossAPY.afterCvx value=9.355813639724296 +2026-01-16T02:25:23.700817408Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=netAPY.preMgmt value=8.420232275751866 +2026-01-16T02:25:23.700842045Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=netAPY.postMgmt value=8.420232275751866 +2026-01-16T02:25:23.700880708Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=netAPY.netAPRFloat64 value=8.420232275751866 +2026-01-16T02:25:23.700932595Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=netAPY.compounded value=2449.814481037776 +2026-01-16T02:25:23.700965367Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=netAPY.withPool value=2449.814681037776 +2026-01-16T02:25:23.701058132Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.netAPY value=2449.814681037776 +2026-01-16T02:25:23.701077839Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.netAPYWithDebtRatio value=1959.851744830221 +2026-01-16T02:25:23.701096394Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.boostWithDebtRatio value=2 +2026-01-16T02:25:23.701109989Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.poolAPYWithDebtRatio value=0.00016 +2026-01-16T02:25:23.701128254Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.boostedAPRWithDebtRatio value=7.465793395801028 +2026-01-16T02:25:23.701141389Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.baseAPRWithDebtRatio value=0.23801496371865116 +2026-01-16T02:25:23.701155295Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.cvxAPRWithDebtRatio value=0.018857515978409903 +2026-01-16T02:25:23.701172918Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.rewardsAPYWithDebtRatio value=0 +2026-01-16T02:25:23.701183748Z APY_TRACE scope=forward.convex chain=1 vault=0x00e8Eb340f8AF587EEA6200D2081E31dC87285ac strategy=0x9C924EE29070964CD7Afde32CD6FDcA620511747 step=result.apyStruct value={convex 0.8 1959.851744830221 {2 0.00016 7.465793395801028 0.23801496371865116 0.018857515978409903 0 0 }} diff --git a/apy_trace_human_commented.md b/apy_trace_human_commented.md new file mode 100644 index 00000000..741e4725 --- /dev/null +++ b/apy_trace_human_commented.md @@ -0,0 +1,704 @@ +APY Trace Review (Commented Code) + +Generated: 2026-01-16T14:29:41.180657+00:00 + +Source log: `apy_trace.log` + +## `processes/apr/forward.curve.go` + +```go +package apr + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/yearn/ydaemon/common/bigNumber" + "github.com/yearn/ydaemon/common/helpers" + "github.com/yearn/ydaemon/internal/models" + "github.com/yearn/ydaemon/internal/storage" +) + +type TCalculateCurveAPYDataStruct struct { + vault models.TVault + gaugeAddress common.Address + strategy models.TStrategy + baseAPY *bigNumber.Float + rewardAPY *bigNumber.Float + poolAPY *bigNumber.Float +} + +func calculateCurveForwardAPY(args TCalculateCurveAPYDataStruct) TStrategyAPY { + chainID := args.vault.ChainID + + /********************************************************************************************** + ** We first need to retrieve a bunch to be able to proceed: + ** - the yBoost, aka how much boost the Yearn voter has for this gauge + ** - the performanceFee and the managementFee for that vault + ** - the debtRatio for the strategy (aka the % of fund allocated to the strategy by the vault) + **********************************************************************************************/ + yBoost := getCurveBoost(chainID, storage.YEARN_VOTER_ADDRESS[chainID], args.gaugeAddress) + keepCrv := determineCurveKeepCRV(args.strategy) + debtRatio := helpers.ToNormalizedAmount(args.strategy.LastDebtRatio, 4) + vaultPerformanceFee := helpers.ToNormalizedAmount(bigNumber.NewInt(int64(args.vault.PerformanceFee)), 4) + vaultManagementFee := helpers.ToNormalizedAmount(bigNumber.NewInt(int64(args.vault.ManagementFee)), 4) + oneMinusPerfFee := bigNumber.NewFloat(0).Sub(bigNumber.NewFloat(1), vaultPerformanceFee) + + /********************************************************************************************** + ** The CRV APR is simply the baseAPR (aka how much CRV we get from the gauge) scaled by the + ** yBoost. We then add the extraRewards which are incentives/bribes on top of the base rewards. + **********************************************************************************************/ + crvAPY := bigNumber.NewFloat(0).Mul(args.baseAPY, yBoost) // baseAPR * yBoost + crvAPY = bigNumber.NewFloat(0).Add(crvAPY, args.rewardAPY) // (baseAPR * yBoost) + rewardAPY + + /********************************************************************************************** + ** Calculate the CRV Gross APR: + ** 1. Taking the base APR, scaling it with the boost and removing the percentage of CRV we want + ** to keep + ** 2. Adding the rewards APR + ** 3. Adding the pool APY + **********************************************************************************************/ + keepCRVRatio := bigNumber.NewFloat(0).Sub(storage.ONE, keepCrv) // 1 - keepCRV + grossAPY := bigNumber.NewFloat(0).Mul(args.baseAPY, yBoost) // 1 - baseAPR * yBoost + grossAPY = bigNumber.NewFloat(0).Mul(grossAPY, keepCRVRatio) // 1 - baseAPR * yBoost * keepCRV + grossAPY = bigNumber.NewFloat(0).Add(grossAPY, args.rewardAPY) // 2 - (baseAPR * yBoost * keepCRV) + rewardAPY + + /********************************************************************************************** + ** Calculate the CRV Net APR: + ** Take the gross APR and remove the performance fee and the management fee + **********************************************************************************************/ + netAPY := bigNumber.NewFloat(0).Mul(grossAPY, oneMinusPerfFee) // grossAPY * (1 - perfFee) + if netAPY.Gt(vaultManagementFee) { + netAPY = bigNumber.NewFloat(0).Sub(netAPY, vaultManagementFee) // (grossAPY * (1 - perfFee)) - managementFee + netAPRFloat64, _ := netAPY.Float64() + netAPY = bigNumber.NewFloat(0).SetFloat64(convertFloatAPRToAPY(netAPRFloat64, 52)) + netAPY = bigNumber.NewFloat(0).Add(netAPY, args.poolAPY) + } else { + netAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), args.poolAPY) + } + + apyStruct := TStrategyAPY{ + Type: "crv", + DebtRatio: debtRatio, + NetAPY: bigNumber.NewFloat(0).Mul(netAPY, debtRatio), + Composite: TCompositeData{ + Boost: bigNumber.NewFloat(0).Mul(yBoost, debtRatio), + PoolAPY: bigNumber.NewFloat(0).Mul(args.poolAPY, debtRatio), + BoostedAPR: bigNumber.NewFloat(0).Mul(crvAPY, debtRatio), + BaseAPR: bigNumber.NewFloat(0).Mul(args.baseAPY, debtRatio), + RewardsAPY: bigNumber.NewFloat(0).Mul(args.rewardAPY, debtRatio), + KeepCRV: keepCrv, + }, + } + return apyStruct +} +``` + +## `processes/apr/forward.convex.go` + +```go +package apr + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/yearn/ydaemon/common/bigNumber" + "github.com/yearn/ydaemon/common/helpers" + "github.com/yearn/ydaemon/internal/models" + "github.com/yearn/ydaemon/internal/storage" +) + +type TCalculateConvexAPYDataStruct struct { + vault models.TVault + gaugeAddress common.Address + strategy models.TStrategy + baseAssetPrice *bigNumber.Float + poolPrice *bigNumber.Float + baseAPY *bigNumber.Float + rewardAPY *bigNumber.Float + poolWeeklyAPY *bigNumber.Float +} + +func calculateConvexForwardAPY(args TCalculateConvexAPYDataStruct) TStrategyAPY { + chainID := args.vault.ChainID + // Curve ZUSD-FRAXBP Factory | args.gaugeAddress.Hex() | 0x218E4678318ab5527e41135713193E5EAd73337f + // Curve XAI-FRAXBP Factory | args.gaugeAddress.Hex() | 0xa8Ea11465A1375BF42463C3B613dFC54248b9C7B + // Curve ZUSD-FRAXBP Factory | args.baseAssetPrice | 0.018355142896898657 + // Curve XAI-FRAXBP Factory | args.baseAssetPrice | 0.06417867250402262 + // Curve ZUSD-FRAXBP Factory | args.poolPrice | 1.0110440176923634 + // Curve XAI-FRAXBP Factory | args.poolPrice | 1.0080959363340094 + // Curve ZUSD-FRAXBP Factory | args.baseAPY | 3.851825336994172 + // Curve XAI-FRAXBP Factory | args.baseAPY | 0.2975187046483139 + // Curve ZUSD-FRAXBP Factory | args.rewardAPY | 0 + // Curve XAI-FRAXBP Factory | args.rewardAPY | 0 + // Curve ZUSD-FRAXBP Factory | args.poolWeeklyAPY | 0.0001 + // Curve XAI-FRAXBP Factory | args.poolWeeklyAPY | 0.0002 + + /********************************************************************************************** + ** We first need to retrieve a bunch to be able to proceed: + ** - the cvxBoost, aka how much boost the convex voter has for this gauge + ** - the performanceFee and the managementFee for that vault + ** - the debtRatio for the strategy (aka the % of fund allocated to the strategy by the vault) + **********************************************************************************************/ + cvxBoost := getCurveBoost(chainID, storage.CONVEX_VOTER_ADDRESS[chainID], args.gaugeAddress) + // Curve ZUSD-FRAXBP Factory | cvxBoost | 2.063936749763785 + // Curve XAI-FRAXBP Factory | cvxBoost | 2.5 + keepCrv := determineConvexKeepCRV(args.strategy) + // Curve ZUSD-FRAXBP Factory | keepCrv | 0 + // Curve XAI-FRAXBP Factory | keepCrv | 0 + debtRatio := helpers.ToNormalizedAmount(args.strategy.LastDebtRatio, 4) + // Curve ZUSD-FRAXBP Factory | debtRatio | 0.8 + // Curve XAI-FRAXBP Factory | debtRatio | 0.8 + vaultPerformanceFee := helpers.ToNormalizedAmount(bigNumber.NewInt(int64(args.vault.PerformanceFee)), 4) + // Curve ZUSD-FRAXBP Factory | vaultPerformanceFee | 0.1 + // Curve XAI-FRAXBP Factory | vaultPerformanceFee | 0.1 + vaultManagementFee := helpers.ToNormalizedAmount(bigNumber.NewInt(int64(args.vault.ManagementFee)), 4) + // Curve ZUSD-FRAXBP Factory | vaultManagementFee | 0 + // Curve XAI-FRAXBP Factory | vaultManagementFee | 0 + oneMinusPerfFee := bigNumber.NewFloat(0).Sub(bigNumber.NewFloat(1), vaultPerformanceFee) + // Curve ZUSD-FRAXBP Factory | oneMinusPerfFee | 0.9 + // Curve XAI-FRAXBP Factory | oneMinusPerfFee | 0.9 + + /********************************************************************************************** + ** The CRV APR is simply the baseAPY (aka how much CRV we get from the gauge) not based on + ** curve API but directly from the formula used by convex subgraph (link below). We are using + ** this for th APR to match convex APR and avoid confusion. This should be very close to the + ** one we could compute. + ** The CVX APR is the APR we get from the CVX rewards, based on the CVX price and the amount + ** of CVX printed, based on the CRV rate for the given gauge. Tldr X crv = Y cvx and we do + ** something to gt an APR. + **********************************************************************************************/ + crvAPR, cvxAPR, crvAPY, cvxAPY := getCVXPoolAPY(chainID, args.vault.Address, args.strategy.Address, args.baseAssetPrice) + // Curve ZUSD-FRAXBP Factory | crvAPR | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPR | 9.332241744751284 + // Curve ZUSD-FRAXBP Factory | cvxAPR | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPR | 0.02357189497301238 + // Curve ZUSD-FRAXBP Factory | crvAPY | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPY | 9.332241744751284 + // Curve ZUSD-FRAXBP Factory | cvxAPY | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPY | 0.02357189497301238 + + /********************************************************************************************** + ** Just like curve, Convex can have extra rewards which are incentives/bribes on top of the + ** base rewards. We need to retrieve them. + **********************************************************************************************/ + _, rewardsAPY := getConvexRewardAPY(chainID, args.strategy, args.baseAssetPrice, args.poolPrice) + // Curve ZUSD-FRAXBP Factory | rewardsAPY | 0 + // Curve XAI-FRAXBP Factory | rewardsAPY | 0 + + /********************************************************************************************** + ** Calculate the CRV Gross APR: + ** 1. Taking the base APR and removing the percentage of CRV we want to keep + ** 2. Adding the rewards APR + ** 3. Adding the pool APY + ** 4. Adding the CVX APR + **********************************************************************************************/ + keepCRVRatio := bigNumber.NewFloat(0).Sub(storage.ONE, keepCrv) // 1 - keepCRV + // Curve ZUSD-FRAXBP Factory | keepCRVRatio | 1 + // Curve XAI-FRAXBP Factory | keepCRVRatio | 1 + grossAPY := bigNumber.NewFloat(0).Mul(crvAPY, keepCRVRatio) // 1 - baseAPY * keepCRV + // Curve ZUSD-FRAXBP Factory | grossAPY | 105.74520039424516 + // Curve XAI-FRAXBP Factory | grossAPY | 9.332241744751284 + grossAPY = bigNumber.NewFloat(0).Add(grossAPY, rewardsAPY) // 2 - (baseAPY * keepCRV) + rewardAPR + // Curve ZUSD-FRAXBP Factory | grossAPY | 105.74520039424516 + // Curve XAI-FRAXBP Factory | grossAPY | 9.332241744751284 + grossAPY = bigNumber.NewFloat(0).Add(grossAPY, cvxAPY) // 4 - (baseAPY * keepCRV) + rewardAPR + poolAPY + cvxAPR + // Curve ZUSD-FRAXBP Factory | grossAPY | 106.01229749971772 + // Curve XAI-FRAXBP Factory | grossAPY | 9.355813639724296 + + /********************************************************************************************** + ** Calculate the CRV Net APR: + ** Take the gross APR and remove the performance fee and the management fee + **********************************************************************************************/ + netAPY := bigNumber.NewFloat(0).Mul(grossAPY, oneMinusPerfFee) // grossAPR * (1 - perfFee) + // Curve ZUSD-FRAXBP Factory | netAPY | 95.41106774974595 + // Curve XAI-FRAXBP Factory | netAPY | 8.420232275751866 + if netAPY.Gt(vaultManagementFee) { + netAPY = bigNumber.NewFloat(0).Sub(netAPY, vaultManagementFee) // (grossAPR * (1 - perfFee)) - managementFee + // Curve ZUSD-FRAXBP Factory | netAPY | 95.41106774974595 + // Curve XAI-FRAXBP Factory | netAPY | 8.420232275751866 + netAPRFloat64, _ := netAPY.Float64() + // Curve ZUSD-FRAXBP Factory | netAPRFloat64 | 95.41106774974595 + // Curve XAI-FRAXBP Factory | netAPRFloat64 | 8.420232275751866 + netAPY = bigNumber.NewFloat(0).SetFloat64(convertFloatAPRToAPY(netAPRFloat64, 52)) + // Curve ZUSD-FRAXBP Factory | netAPY | 3.3993111000835647e+23 + // Curve XAI-FRAXBP Factory | netAPY | 2449.814481037776 + netAPY = bigNumber.NewFloat(0).Add(netAPY, args.poolWeeklyAPY) + // Curve ZUSD-FRAXBP Factory | netAPY | 3.3993111000835647e+23 + // Curve XAI-FRAXBP Factory | netAPY | 2449.814681037776 + } else { + netAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), args.poolWeeklyAPY) + } + + netAPYWithDebtRatio := bigNumber.NewFloat(0).Mul(netAPY, debtRatio) + // Curve ZUSD-FRAXBP Factory | netAPY | 3.3993111000835647e+23 + // Curve XAI-FRAXBP Factory | netAPY | 2449.814681037776 + // Curve ZUSD-FRAXBP Factory | netAPYWithDebtRatio | 2.7194488800668517e+23 + // Curve XAI-FRAXBP Factory | netAPYWithDebtRatio | 1959.851744830221 + boostWithDebtRatio := bigNumber.NewFloat(0).Mul(cvxBoost, debtRatio) + // Curve ZUSD-FRAXBP Factory | boostWithDebtRatio | 1.651149399811028 + // Curve XAI-FRAXBP Factory | boostWithDebtRatio | 2 + poolAPYWithDebtRatio := bigNumber.NewFloat(0).Mul(args.poolWeeklyAPY, debtRatio) + // Curve ZUSD-FRAXBP Factory | poolAPYWithDebtRatio | 8e-05 + // Curve XAI-FRAXBP Factory | poolAPYWithDebtRatio | 0.00016 + boostedAPRWithDebtRatio := bigNumber.NewFloat(0).Mul(crvAPR, debtRatio) + // Curve ZUSD-FRAXBP Factory | boostedAPRWithDebtRatio | 84.59616031539613 + // Curve XAI-FRAXBP Factory | boostedAPRWithDebtRatio | 7.465793395801028 + baseAPRWithDebtRatio := bigNumber.NewFloat(0).Mul(args.baseAPY, debtRatio) + // Curve ZUSD-FRAXBP Factory | baseAPRWithDebtRatio | 3.081460269595338 + // Curve XAI-FRAXBP Factory | baseAPRWithDebtRatio | 0.23801496371865116 + cvxAPRWithDebtRatio := bigNumber.NewFloat(0).Mul(cvxAPR, debtRatio) + // Curve ZUSD-FRAXBP Factory | cvxAPRWithDebtRatio | 0.2136776843780508 + // Curve XAI-FRAXBP Factory | cvxAPRWithDebtRatio | 0.018857515978409903 + rewardsAPYWithDebtRatio := bigNumber.NewFloat(0).Mul(args.rewardAPY, debtRatio) + // Curve ZUSD-FRAXBP Factory | rewardsAPYWithDebtRatio | 0 + // Curve XAI-FRAXBP Factory | rewardsAPYWithDebtRatio | 0 + + apyStruct := TStrategyAPY{ + Type: "convex", + DebtRatio: debtRatio, + NetAPY: netAPYWithDebtRatio, + Composite: TCompositeData{ + Boost: boostWithDebtRatio, + PoolAPY: poolAPYWithDebtRatio, + BoostedAPR: boostedAPRWithDebtRatio, + BaseAPR: baseAPRWithDebtRatio, + CvxAPR: cvxAPRWithDebtRatio, + RewardsAPY: rewardsAPYWithDebtRatio, + KeepCRV: keepCrv, + }, + } + // Curve ZUSD-FRAXBP Factory | apyStruct | {convex 0.8 2.7194488800668517e+23 {1.651149399811028 8e-05 84.59616031539613 3.081460269595338 0.2136776843780508 0 0 }} + // Curve XAI-FRAXBP Factory | apyStruct | {convex 0.8 1959.851744830221 {2 0.00016 7.465793395801028 0.23801496371865116 0.018857515978409903 0 0 }} + return apyStruct +} +``` + +## `processes/apr/forward.convex.helpers.go` + +```go +package apr + +import ( + "math/big" + "os" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/yearn/ydaemon/common/bigNumber" + "github.com/yearn/ydaemon/common/contracts" + "github.com/yearn/ydaemon/common/ethereum" + "github.com/yearn/ydaemon/common/helpers" + "github.com/yearn/ydaemon/common/logs" + "github.com/yearn/ydaemon/internal/models" + "github.com/yearn/ydaemon/internal/storage" +) + +/************************************************************************************************** +** The cumulative apr of all extra tokens that are emitted by depositing to Convex, assuming that +** they will be sold for profit. +** We need to pull data from convex's virtual rewards contracts to get bonus rewards +**************************************************************************************************/ +func getConvexRewardAPY( + chainID uint64, + strategy models.TStrategy, + baseAssetPrice *bigNumber.Float, + poolPrice *bigNumber.Float, +) (*bigNumber.Float, *bigNumber.Float) { + client := ethereum.GetRPC(chainID) + convexStrategyContract, _ := contracts.NewConvexBaseStrategy(strategy.Address, client) + cvxBoosterContract, _ := contracts.NewCVXBooster(storage.CVX_BOOSTER_ADDRESS[chainID], client) + rewardPID, err := convexStrategyContract.Pid(nil) + if err != nil { + rewardPID, err = convexStrategyContract.ID(nil) + if err != nil { + warnMissingPID := func(pidErr error) { + logs.Warning("Convex PID not found for strategy "+strategy.Address.Hex(), pidErr) + } + fraxBaseStrategy, err := contracts.NewFraxBaseStrategy(strategy.Address, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init fraxBaseStrategy for convex strategy `+strategy.Address.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + userVaultAddress, err := fraxBaseStrategy.UserVault(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get userVault for fraxBaseStrategy `+strategy.Address.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + userVaultContract, err := contracts.NewConvexUserVault(userVaultAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init userVault contract `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + stakingTokenAddress, err := userVaultContract.StakingToken(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get stakingToken for userVault `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + stakingTokenContract, err := contracts.NewConvexStakingToken(stakingTokenAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init stakingToken contract `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + rewardPID, err = stakingTokenContract.ConvexPoolId(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get cvxPoolId for stakingToken `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + } + } + rewardContract, err := cvxBoosterContract.PoolInfo(nil, rewardPID) + if err != nil { + logs.Error(err) + return storage.ZERO, storage.ZERO + } + crvRewardContract, _ := contracts.NewCrvRewards(rewardContract.CrvRewards, client) + rewardsLength, _ := crvRewardContract.ExtraRewardsLength(nil) + + now := time.Now().Unix() + totalRewardsAPR := bigNumber.NewFloat(0) + if rewardsLength != nil { + for i := 0; i < int(rewardsLength.Int64()); i++ { + virtualRewardsPool, err := crvRewardContract.ExtraRewards(nil, big.NewInt(int64(i))) + if err != nil { + logs.Error(err) + continue + } + virtualRewardsPoolContract, _ := contracts.NewCrvRewards(virtualRewardsPool, client) + periodFinish, err := virtualRewardsPoolContract.PeriodFinish(nil) + if err != nil { + logs.Error(err) + continue + } + if periodFinish.Int64() < now { + continue + } + rewardToken, _ := virtualRewardsPoolContract.RewardToken(nil) + rewardTokenPrice, ok := storage.GetPrice(chainID, rewardToken) + if !ok { + continue + } + rewardRateInt, _ := virtualRewardsPoolContract.RewardRate(nil) + totalSupplyInt, _ := virtualRewardsPoolContract.TotalSupply(nil) + + tokenPrice := rewardTokenPrice.HumanizedPrice + rewardRate := helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(rewardRateInt), 18) + totalSupply := helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(totalSupplyInt), 18) + secondPerYear := bigNumber.NewFloat(0).SetFloat64(31556952) + + rewardAPRTop := bigNumber.NewFloat(0).Mul(rewardRate, secondPerYear) + rewardAPRTop = bigNumber.NewFloat(0).Mul(rewardAPRTop, tokenPrice) + rewardAPRBottom := bigNumber.NewFloat(0).Div(poolPrice, storage.ONE) //wei? + rewardAPRBottom = bigNumber.NewFloat(0).Mul(rewardAPRBottom, baseAssetPrice) + rewardAPRBottom = bigNumber.NewFloat(0).Mul(rewardAPRBottom, totalSupply) + rewardAPR := bigNumber.NewFloat(0).Div(rewardAPRTop, rewardAPRBottom) + totalRewardsAPR = bigNumber.NewFloat(0).Add(totalRewardsAPR, rewardAPR) + } + } + totalRewardsAPY := bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), totalRewardsAPR) + return totalRewardsAPR, totalRewardsAPY +} + +/************************************************************************************************** +** Calculate the number of CVX minted for a given amount of CRV earned. +** See formula here: https://docs.convexfinance.com/convexfinanceintegration/cvx-minting +**************************************************************************************************/ +func getCVXForCRV(chainID uint64, crvEarned *bigNumber.Float) *bigNumber.Float { + cliffSize := bigNumber.NewFloat(0).SetString(`100000000000000000000000`) //1e23 + cliffCount := bigNumber.NewFloat(0).SetString(`1000`) //1e3 + maxSupply := bigNumber.NewFloat(0).SetString(`100000000000000000000000000`) //1e26 + + cvxContract, _ := contracts.NewERC20(storage.CVX_TOKEN_ADDRESS[chainID], ethereum.GetRPC(chainID)) + cvxTotalSupplyInt, _ := cvxContract.TotalSupply(nil) + cvxTotalSupply := bigNumber.NewFloat(0).SetInt(bigNumber.NewInt(0).Set(cvxTotalSupplyInt)) + currentCliff := bigNumber.NewFloat(0).Div(cvxTotalSupply, cliffSize) + if currentCliff.Gte(cliffCount) { + return storage.ZERO + } + remaining := bigNumber.NewFloat(0).Sub(cliffCount, currentCliff) + cvxEarned := bigNumber.NewFloat(0).Mul(crvEarned, remaining) + cvxEarned = bigNumber.NewFloat(0).Div(cvxEarned, cliffCount) + + amountTillMax := bigNumber.NewFloat(0).Sub(maxSupply, cvxTotalSupply) + if cvxEarned.Gt(amountTillMax) { + cvxEarned = amountTillMax + } + return cvxEarned +} + +/************************************************************************************************** +** Calculate the cvxAPR for a given CVX pool. +** See formula here: +** https://github.com/convex-community/convex-subgraph/blob/ +** 13dbb4e3f3f69c6762fecb1ebc46f477162e2093/subgraphs/convex/src/services/pools.ts#L269-L289 +**************************************************************************************************/ +func getCVXPoolAPY( + chainID uint64, + vaultAddress common.Address, + strategyAddress common.Address, + virtualPoolPrice *bigNumber.Float, +) (*bigNumber.Float, *bigNumber.Float, *bigNumber.Float, *bigNumber.Float) { + // Curve ZUSD-FRAXBP Factory | virtualPoolPrice | 0.018355142896898657 + // Curve XAI-FRAXBP Factory | virtualPoolPrice | 0.06417867250402262 + + crvAPR := bigNumber.NewFloat(0) + cvxAPR := bigNumber.NewFloat(0) + crvAPY := bigNumber.NewFloat(0) + cvxAPY := bigNumber.NewFloat(0) + + /********************************************************************************************** + ** First thing to do to be able to calculate the APR is to retrieve the `crvRewards` contract + ** for this given convex strategy. It's a multiple step with way too many contracts involved, + ** but in the end we should be able to query the `rewardRate` for it. + ***********************************************************************************************/ + client := ethereum.GetRPC(chainID) + convexStrategyContract, err := contracts.NewConvexBaseStrategy(strategyAddress, client) + if err != nil { + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + + /********************************************************************************************** + ** We need to know the PID of the pool. Based on the contract, it can be PID or ID, so we need + ** to try both, and one call can revert. + ***********************************************************************************************/ + rewardPID, err := convexStrategyContract.Pid(nil) + if err != nil { + rewardPID, err = convexStrategyContract.ID(nil) + if err != nil { + warnMissingPID := func(pidErr error) { + logs.Warning("Convex PID not found for vault "+vaultAddress.Hex()+" strategy "+strategyAddress.Hex(), pidErr) + } + fraxBaseStrategy, err := contracts.NewFraxBaseStrategy(strategyAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init fraxBaseStrategy for convex strategy `+strategyAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + userVaultAddress, err := fraxBaseStrategy.UserVault(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get userVault for fraxBaseStrategy `+strategyAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + // Curve ZUSD-FRAXBP Factory | userVaultAddress.Hex() | 0xAB919492C39263e97D4190b9bb0d2437D15EAcC9 + // Curve XAI-FRAXBP Factory | userVaultAddress.Hex() | 0x9628d5f254B010C0122440F282E1fB6b0f25239c + userVaultContract, err := contracts.NewConvexUserVault(userVaultAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init userVault contract `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + stakingTokenAddress, err := userVaultContract.StakingToken(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get stakingToken for userVault `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + // Curve ZUSD-FRAXBP Factory | stakingTokenAddress.Hex() | 0xFD2d7847E0f450d8B00d3D697D720C687E622a7B + // Curve XAI-FRAXBP Factory | stakingTokenAddress.Hex() | 0x19f0a60f4635d3E2c48647822Eda5332BA094fd3 + stakingTokenContract, err := contracts.NewConvexStakingToken(stakingTokenAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init stakingToken contract `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + rewardPID, err = stakingTokenContract.ConvexPoolId(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get cvxPoolId for stakingToken `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + // Curve ZUSD-FRAXBP Factory | rewardPID | 196 + // Curve XAI-FRAXBP Factory | rewardPID | 129 + } + } + // Curve ZUSD-FRAXBP Factory | rewardPID | 196 + // Curve XAI-FRAXBP Factory | rewardPID | 129 + + /********************************************************************************************** + ** Once we got the PID, we can query the convexBooster contract to get the `poolInfo` for this + ** and retrieve the `crvRewards` contract + ***********************************************************************************************/ + cvxBoosterContract, err := contracts.NewCVXBooster(storage.CVX_BOOSTER_ADDRESS[chainID], client) + if err != nil { + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + poolInfo, err := cvxBoosterContract.PoolInfo(nil, rewardPID) + if err != nil { + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + // Curve ZUSD-FRAXBP Factory | poolInfo.CrvRewards.Hex() | 0xFd3A7636694259b32B3896f59436997AD25380cA + // Curve XAI-FRAXBP Factory | poolInfo.CrvRewards.Hex() | 0x4a866fE20A442Dff55FAA010684A5C1379151458 + + /********************************************************************************************** + ** Once we got the poolInfo, we can init a new contract connector, which would be a + ** `BaseRewardPool`, and we should be able to query the rewardRate for it. + ***********************************************************************************************/ + rewardContract, err := contracts.NewCrvRewards(poolInfo.CrvRewards, client) + if err != nil { + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + rateResult, err1 := rewardContract.RewardRate(nil) + supplyResult, err2 := rewardContract.TotalSupply(nil) + if err1 != nil || err2 != nil { + return crvAPR, cvxAPR, crvAPY, cvxAPY + } + // Curve ZUSD-FRAXBP Factory | rateResult | 45048708159937 + // Curve XAI-FRAXBP Factory | rateResult | 36068329259793 + // Curve ZUSD-FRAXBP Factory | supplyResult | 317120356635973553634 + // Curve XAI-FRAXBP Factory | supplyResult | 822828886434854500906 + + /********************************************************************************************** + ** Then we should be able to calculate the cvxAPR just like it's done on the CVX subgraph + ***********************************************************************************************/ + rate := helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(rateResult), 18) + supply := helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(supplyResult), 18) + // Curve ZUSD-FRAXBP Factory | rate | 4.5048708159937e-05 + // Curve XAI-FRAXBP Factory | rate | 3.6068329259793e-05 + // Curve ZUSD-FRAXBP Factory | supply | 317.12035663597356 + // Curve XAI-FRAXBP Factory | supply | 822.8288864348544 + crvPerUnderlying := bigNumber.NewFloat(0) + virtualSupply := bigNumber.NewFloat(0).Mul(supply, virtualPoolPrice) + // Curve ZUSD-FRAXBP Factory | virtualSupply | 5.820789461568759 + // Curve XAI-FRAXBP Factory | virtualSupply | 52.80806562935214 + + if virtualSupply.Gt(storage.ZERO) { + crvPerUnderlying = bigNumber.NewFloat(0).Div(rate, virtualSupply) + } + // Curve ZUSD-FRAXBP Factory | crvPerUnderlying | 7.739278057962252e-06 + // Curve XAI-FRAXBP Factory | crvPerUnderlying | 6.830079615668645e-07 + crvPerUnderlyingPerYear := bigNumber.NewFloat(0).Mul(crvPerUnderlying, bigNumber.NewFloat(31536000)) + // Curve ZUSD-FRAXBP Factory | crvPerUnderlyingPerYear | 244.06587283589755 + // Curve XAI-FRAXBP Factory | crvPerUnderlyingPerYear | 21.539339075972638 + cvxPerYear := getCVXForCRV(chainID, crvPerUnderlyingPerYear) + // Curve ZUSD-FRAXBP Factory | cvxPerYear | 0.1322365676305354 + // Curve XAI-FRAXBP Factory | cvxPerYear | 0.011670161974476423 + + crvPrice := bigNumber.NewFloat(0) + if tokenPrice, ok := storage.GetPrice(chainID, storage.CRV_TOKEN_ADDRESS[chainID]); ok { + crvPrice = tokenPrice.HumanizedPrice + } + // Curve ZUSD-FRAXBP Factory | crvPrice | 0.433265 + // Curve XAI-FRAXBP Factory | crvPrice | 0.433265 + cvxPrice := bigNumber.NewFloat(0) + if tokenPrice, ok := storage.GetPrice(chainID, storage.CVX_TOKEN_ADDRESS[chainID]); ok { + cvxPrice = tokenPrice.HumanizedPrice + } + // Curve ZUSD-FRAXBP Factory | cvxPrice | 2.019843 + // Curve XAI-FRAXBP Factory | cvxPrice | 2.019843 + crvAPR = bigNumber.NewFloat(0).Mul(crvPerUnderlyingPerYear, crvPrice) + // Curve ZUSD-FRAXBP Factory | crvAPR | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPR | 9.332241744751284 + cvxAPR = bigNumber.NewFloat(0).Mul(cvxPerYear, cvxPrice) + // Curve ZUSD-FRAXBP Factory | cvxAPR | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPR | 0.02357189497301238 + + crvAPRFloat64, _ := crvAPR.Float64() + cvxAPRFloat64, _ := cvxAPR.Float64() + // Curve ZUSD-FRAXBP Factory | crvAPRFloat64 | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPRFloat64 | 9.332241744751284 + // Curve ZUSD-FRAXBP Factory | cvxAPRFloat64 | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPRFloat64 | 0.02357189497301238 + crvAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), crvAPR) + // Curve ZUSD-FRAXBP Factory | crvAPY | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPY | 9.332241744751284 + cvxAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), cvxAPR) + // Curve ZUSD-FRAXBP Factory | cvxAPY | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPY | 0.02357189497301238 + + // Curve ZUSD-FRAXBP Factory | crvAPR | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPR | 9.332241744751284 + // Curve ZUSD-FRAXBP Factory | cvxAPR | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPR | 0.02357189497301238 + // Curve ZUSD-FRAXBP Factory | crvAPY | 105.74520039424516 + // Curve XAI-FRAXBP Factory | crvAPY | 9.332241744751284 + // Curve ZUSD-FRAXBP Factory | cvxAPY | 0.2670971054725635 + // Curve XAI-FRAXBP Factory | cvxAPY | 0.02357189497301238 + + return crvAPR, cvxAPR, crvAPY, cvxAPY +} + +/************************************************************************************************** +** Determine the keepCRV value for the vault. This indicates the amount of CRV this strategy should +** keep as rewards instead of insta dump. +** Because of the contract upgrade, we have multiple stuff to checks: +** - If the strategy does not have a `UselLocalCRV` (with a typo) function, we return the already +** retrieved KeepCRV value (retrieved for normal yDaemon execution) +** - If the strategy has a `UselLocalCRV` function, we check if it's true or false +** - If it's true, we retrieve the localCRV value and use it if it exists +** - If it does not, we can check the LocalKeepCRV value and use it if it exists, or ZERO +** - If it's false, we can query the KeepCRV value from the curveGlobal contract and use it +**************************************************************************************************/ +func determineConvexKeepCRV(strategy models.TStrategy) *bigNumber.Float { + if strategy.KeepCRV == nil { + return storage.ZERO + } + client := ethereum.GetRPC(strategy.ChainID) + convexStrategyContract, _ := contracts.NewConvexBaseStrategy(strategy.Address, client) + useLocalCRV, err := convexStrategyContract.UselLocalCRV(nil) + if err != nil { + return helpers.ToNormalizedAmount(strategy.KeepCRV, 4) + } + if useLocalCRV { + cvxKeepCRV, err := convexStrategyContract.LocalCRV(nil) + if err != nil { + localKeepCRV, err := convexStrategyContract.LocalKeepCRV(nil) + if err != nil { + return storage.ZERO + } + return helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(localKeepCRV), 4) + } + return helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(cvxKeepCRV), 4) + } + curveGlobal, err := convexStrategyContract.CurveGlobal(nil) + if err != nil { + return storage.ZERO + } + curveGlobalContract, err := contracts.NewStrategyBase(curveGlobal, client) + if err != nil { + return storage.ZERO + } + keepCRV, err := curveGlobalContract.KeepCRV(nil) + if err != nil { + return storage.ZERO + } + return helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(keepCRV), 4) +} + +/************************************************************************************************** +** Check if the strategy is a convex strategy. This is a check based on the strategy name. What +** could go wrong. +**************************************************************************************************/ +func isConvexStrategy(strategy models.TStrategy) bool { + name := strings.ToLower(strategy.Name) + return strings.Contains(name, `convex`) && !strings.Contains(name, `convexfrax`) && !strings.Contains(name, `ethconvex`) +} +``` diff --git a/processes/apr/forward.convex.go b/processes/apr/forward.convex.go index 8b971a98..09517c21 100644 --- a/processes/apr/forward.convex.go +++ b/processes/apr/forward.convex.go @@ -21,6 +21,12 @@ type TCalculateConvexAPYDataStruct struct { func calculateConvexForwardAPY(args TCalculateConvexAPYDataStruct) TStrategyAPY { chainID := args.vault.ChainID + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "input.gaugeAddress", args.gaugeAddress.Hex()) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "input.baseAssetPrice", args.baseAssetPrice) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "input.poolPrice", args.poolPrice) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "input.baseAPY", args.baseAPY) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "input.rewardAPY", args.rewardAPY) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "input.poolWeeklyAPY", args.poolWeeklyAPY) /********************************************************************************************** ** We first need to retrieve a bunch to be able to proceed: @@ -29,11 +35,17 @@ func calculateConvexForwardAPY(args TCalculateConvexAPYDataStruct) TStrategyAPY ** - the debtRatio for the strategy (aka the % of fund allocated to the strategy by the vault) **********************************************************************************************/ cvxBoost := getCurveBoost(chainID, storage.CONVEX_VOTER_ADDRESS[chainID], args.gaugeAddress) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "cvxBoost", cvxBoost) keepCrv := determineConvexKeepCRV(args.strategy) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "keepCrv", keepCrv) debtRatio := helpers.ToNormalizedAmount(args.strategy.LastDebtRatio, 4) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "debtRatio", debtRatio) vaultPerformanceFee := helpers.ToNormalizedAmount(bigNumber.NewInt(int64(args.vault.PerformanceFee)), 4) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "vaultPerformanceFee", vaultPerformanceFee) vaultManagementFee := helpers.ToNormalizedAmount(bigNumber.NewInt(int64(args.vault.ManagementFee)), 4) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "vaultManagementFee", vaultManagementFee) oneMinusPerfFee := bigNumber.NewFloat(0).Sub(bigNumber.NewFloat(1), vaultPerformanceFee) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "oneMinusPerfFee", oneMinusPerfFee) /********************************************************************************************** ** The CRV APR is simply the baseAPY (aka how much CRV we get from the gauge) not based on @@ -45,12 +57,17 @@ func calculateConvexForwardAPY(args TCalculateConvexAPYDataStruct) TStrategyAPY ** something to gt an APR. **********************************************************************************************/ crvAPR, cvxAPR, crvAPY, cvxAPY := getCVXPoolAPY(chainID, args.vault.Address, args.strategy.Address, args.baseAssetPrice) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "crvAPR", crvAPR) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "cvxAPR", cvxAPR) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "crvAPY", crvAPY) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "cvxAPY", cvxAPY) /********************************************************************************************** ** Just like curve, Convex can have extra rewards which are incentives/bribes on top of the ** base rewards. We need to retrieve them. **********************************************************************************************/ _, rewardsAPY := getConvexRewardAPY(chainID, args.strategy, args.baseAssetPrice, args.poolPrice) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "rewardsAPY", rewardsAPY) /********************************************************************************************** ** Calculate the CRV Gross APR: @@ -60,37 +77,64 @@ func calculateConvexForwardAPY(args TCalculateConvexAPYDataStruct) TStrategyAPY ** 4. Adding the CVX APR **********************************************************************************************/ keepCRVRatio := bigNumber.NewFloat(0).Sub(storage.ONE, keepCrv) // 1 - keepCRV + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "keepCRVRatio", keepCRVRatio) grossAPY := bigNumber.NewFloat(0).Mul(crvAPY, keepCRVRatio) // 1 - baseAPY * keepCRV + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "grossAPY.afterKeepCRV", grossAPY) grossAPY = bigNumber.NewFloat(0).Add(grossAPY, rewardsAPY) // 2 - (baseAPY * keepCRV) + rewardAPR + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "grossAPY.afterRewards", grossAPY) grossAPY = bigNumber.NewFloat(0).Add(grossAPY, cvxAPY) // 4 - (baseAPY * keepCRV) + rewardAPR + poolAPY + cvxAPR + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "grossAPY.afterCvx", grossAPY) /********************************************************************************************** ** Calculate the CRV Net APR: ** Take the gross APR and remove the performance fee and the management fee **********************************************************************************************/ netAPY := bigNumber.NewFloat(0).Mul(grossAPY, oneMinusPerfFee) // grossAPR * (1 - perfFee) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "netAPY.preMgmt", netAPY) if netAPY.Gt(vaultManagementFee) { netAPY = bigNumber.NewFloat(0).Sub(netAPY, vaultManagementFee) // (grossAPR * (1 - perfFee)) - managementFee + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "netAPY.postMgmt", netAPY) netAPRFloat64, _ := netAPY.Float64() + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "netAPY.netAPRFloat64", netAPRFloat64) netAPY = bigNumber.NewFloat(0).SetFloat64(convertFloatAPRToAPY(netAPRFloat64, 52)) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "netAPY.compounded", netAPY) netAPY = bigNumber.NewFloat(0).Add(netAPY, args.poolWeeklyAPY) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "netAPY.withPool", netAPY) } else { netAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), args.poolWeeklyAPY) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "netAPY.poolOnly", netAPY) } + netAPYWithDebtRatio := bigNumber.NewFloat(0).Mul(netAPY, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.netAPY", netAPY) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.netAPYWithDebtRatio", netAPYWithDebtRatio) + boostWithDebtRatio := bigNumber.NewFloat(0).Mul(cvxBoost, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.boostWithDebtRatio", boostWithDebtRatio) + poolAPYWithDebtRatio := bigNumber.NewFloat(0).Mul(args.poolWeeklyAPY, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.poolAPYWithDebtRatio", poolAPYWithDebtRatio) + boostedAPRWithDebtRatio := bigNumber.NewFloat(0).Mul(crvAPR, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.boostedAPRWithDebtRatio", boostedAPRWithDebtRatio) + baseAPRWithDebtRatio := bigNumber.NewFloat(0).Mul(args.baseAPY, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.baseAPRWithDebtRatio", baseAPRWithDebtRatio) + cvxAPRWithDebtRatio := bigNumber.NewFloat(0).Mul(cvxAPR, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.cvxAPRWithDebtRatio", cvxAPRWithDebtRatio) + rewardsAPYWithDebtRatio := bigNumber.NewFloat(0).Mul(args.rewardAPY, debtRatio) + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.rewardsAPYWithDebtRatio", rewardsAPYWithDebtRatio) + apyStruct := TStrategyAPY{ Type: "convex", DebtRatio: debtRatio, - NetAPY: bigNumber.NewFloat(0).Mul(netAPY, debtRatio), + NetAPY: netAPYWithDebtRatio, Composite: TCompositeData{ - Boost: bigNumber.NewFloat(0).Mul(cvxBoost, debtRatio), - PoolAPY: bigNumber.NewFloat(0).Mul(args.poolWeeklyAPY, debtRatio), - BoostedAPR: bigNumber.NewFloat(0).Mul(crvAPR, debtRatio), - BaseAPR: bigNumber.NewFloat(0).Mul(args.baseAPY, debtRatio), - CvxAPR: bigNumber.NewFloat(0).Mul(cvxAPR, debtRatio), - RewardsAPY: bigNumber.NewFloat(0).Mul(args.rewardAPY, debtRatio), + Boost: boostWithDebtRatio, + PoolAPY: poolAPYWithDebtRatio, + BoostedAPR: boostedAPRWithDebtRatio, + BaseAPR: baseAPRWithDebtRatio, + CvxAPR: cvxAPRWithDebtRatio, + RewardsAPY: rewardsAPYWithDebtRatio, KeepCRV: keepCrv, }, } + apyTrace("forward.convex", chainID, args.vault.Address, args.strategy.Address, "result.apyStruct", apyStruct) return apyStruct } diff --git a/processes/apr/forward.convex.helpers.go b/processes/apr/forward.convex.helpers.go index 52d0e75e..30254501 100644 --- a/processes/apr/forward.convex.helpers.go +++ b/processes/apr/forward.convex.helpers.go @@ -34,11 +34,55 @@ func getConvexRewardAPY( if err != nil { rewardPID, err = convexStrategyContract.ID(nil) if err != nil { - rewardPID, err = convexStrategyContract.FraxPid(nil) + warnMissingPID := func(pidErr error) { + logs.Warning("Convex PID not found for strategy "+strategy.Address.Hex(), pidErr) + } + fraxBaseStrategy, err := contracts.NewFraxBaseStrategy(strategy.Address, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init fraxBaseStrategy for convex strategy `+strategy.Address.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + userVaultAddress, err := fraxBaseStrategy.UserVault(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get userVault for fraxBaseStrategy `+strategy.Address.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + userVaultContract, err := contracts.NewConvexUserVault(userVaultAddress, client) if err != nil { if os.Getenv("ENVIRONMENT") == "dev" { - logs.Error(`Unable to get reward PID for convex strategy ` + strategy.Address.Hex()) + logs.Error(`Unable to init userVault contract `+userVaultAddress.Hex(), err) } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + stakingTokenAddress, err := userVaultContract.StakingToken(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get stakingToken for userVault `+userVaultAddress.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + stakingTokenContract, err := contracts.NewConvexStakingToken(stakingTokenAddress, client) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to init stakingToken contract `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) + return storage.ZERO, storage.ZERO + } + rewardPID, err = stakingTokenContract.ConvexPoolId(nil) + if err != nil { + if os.Getenv("ENVIRONMENT") == "dev" { + logs.Error(`Unable to get cvxPoolId for stakingToken `+stakingTokenAddress.Hex(), err) + } + warnMissingPID(err) return storage.ZERO, storage.ZERO } } @@ -134,6 +178,7 @@ func getCVXPoolAPY( strategyAddress common.Address, virtualPoolPrice *bigNumber.Float, ) (*bigNumber.Float, *bigNumber.Float, *bigNumber.Float, *bigNumber.Float) { + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "input.virtualPoolPrice", virtualPoolPrice) crvAPR := bigNumber.NewFloat(0) cvxAPR := bigNumber.NewFloat(0) @@ -178,6 +223,7 @@ func getCVXPoolAPY( warnMissingPID(err) return crvAPR, cvxAPR, crvAPY, cvxAPY } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "fraxBaseStrategy.userVault", userVaultAddress.Hex()) userVaultContract, err := contracts.NewConvexUserVault(userVaultAddress, client) if err != nil { if os.Getenv("ENVIRONMENT") == "dev" { @@ -194,6 +240,7 @@ func getCVXPoolAPY( warnMissingPID(err) return crvAPR, cvxAPR, crvAPY, cvxAPY } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "userVault.stakingToken", stakingTokenAddress.Hex()) stakingTokenContract, err := contracts.NewConvexStakingToken(stakingTokenAddress, client) if err != nil { if os.Getenv("ENVIRONMENT") == "dev" { @@ -210,8 +257,10 @@ func getCVXPoolAPY( warnMissingPID(err) return crvAPR, cvxAPR, crvAPY, cvxAPY } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "stakingToken.cvxPoolId", rewardPID) } } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "rewardPID", rewardPID) /********************************************************************************************** ** Once we got the PID, we can query the convexBooster contract to get the `poolInfo` for this @@ -225,6 +274,7 @@ func getCVXPoolAPY( if err != nil { return crvAPR, cvxAPR, crvAPY, cvxAPY } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "poolInfo.crvRewards", poolInfo.CrvRewards.Hex()) /********************************************************************************************** ** Once we got the poolInfo, we can init a new contract connector, which would be a @@ -239,34 +289,57 @@ func getCVXPoolAPY( if err1 != nil || err2 != nil { return crvAPR, cvxAPR, crvAPY, cvxAPY } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "rewardRateRaw", rateResult) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "totalSupplyRaw", supplyResult) /********************************************************************************************** ** Then we should be able to calculate the cvxAPR just like it's done on the CVX subgraph ***********************************************************************************************/ rate := helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(rateResult), 18) supply := helpers.ToNormalizedAmount(bigNumber.NewInt(0).Set(supplyResult), 18) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "rewardRate", rate) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "totalSupply", supply) crvPerUnderlying := bigNumber.NewFloat(0) virtualSupply := bigNumber.NewFloat(0).Mul(supply, virtualPoolPrice) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "virtualSupply", virtualSupply) if virtualSupply.Gt(storage.ZERO) { crvPerUnderlying = bigNumber.NewFloat(0).Div(rate, virtualSupply) } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "crvPerUnderlying", crvPerUnderlying) crvPerUnderlyingPerYear := bigNumber.NewFloat(0).Mul(crvPerUnderlying, bigNumber.NewFloat(31536000)) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "crvPerUnderlyingPerYear", crvPerUnderlyingPerYear) cvxPerYear := getCVXForCRV(chainID, crvPerUnderlyingPerYear) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "cvxPerYear", cvxPerYear) crvPrice := bigNumber.NewFloat(0) if tokenPrice, ok := storage.GetPrice(chainID, storage.CRV_TOKEN_ADDRESS[chainID]); ok { crvPrice = tokenPrice.HumanizedPrice } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "crvPrice", crvPrice) cvxPrice := bigNumber.NewFloat(0) if tokenPrice, ok := storage.GetPrice(chainID, storage.CVX_TOKEN_ADDRESS[chainID]); ok { cvxPrice = tokenPrice.HumanizedPrice } + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "cvxPrice", cvxPrice) crvAPR = bigNumber.NewFloat(0).Mul(crvPerUnderlyingPerYear, crvPrice) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "crvAPR", crvAPR) cvxAPR = bigNumber.NewFloat(0).Mul(cvxPerYear, cvxPrice) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "cvxAPR", cvxAPR) + crvAPRFloat64, _ := crvAPR.Float64() + cvxAPRFloat64, _ := cvxAPR.Float64() + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "crvAPRFloat64", crvAPRFloat64) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "cvxAPRFloat64", cvxAPRFloat64) crvAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), crvAPR) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "crvAPY", crvAPY) cvxAPY = bigNumber.NewFloat(0).Add(bigNumber.NewFloat(0), cvxAPR) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "cvxAPY", cvxAPY) + + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "result.crvAPR", crvAPR) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "result.cvxAPR", cvxAPR) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "result.crvAPY", crvAPY) + apyTrace("forward.convex.cvxPool", chainID, vaultAddress, strategyAddress, "result.cvxAPY", cvxAPY) return crvAPR, cvxAPR, crvAPY, cvxAPY }