Skip to content

Commit

Permalink
fix: v3 pricing
Browse files Browse the repository at this point in the history
  • Loading branch information
Majorfi committed Oct 4, 2023
1 parent f29475c commit 5dda9d8
Show file tree
Hide file tree
Showing 9 changed files with 3,119 additions and 1 deletion.
2,947 changes: 2,947 additions & 0 deletions common/contracts/ERC4626.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions common/env/chain.ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ var ETHEREUM = TChain{
common.HexToAddress(`0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0`), // Wrapped liquid staked Ether 2.0
common.HexToAddress(`0xA35b1B31Ce002FBF2058D22F30f95D405200A15b`), // Stader ETHx
common.HexToAddress(`0xBe9895146f7AF43049ca1c1AE358B0541Ea49704`), // Coinbase Wrapped Staked ETH
common.HexToAddress(`0x583019fF0f430721aDa9cfb4fac8F06cA104d0B4`), // st-yETH
},
IgnoredTokens: []common.Address{
common.HexToAddress(`0x7AB4a7BE740131BdE216521B54ADddD672F44A05`), // nothing
Expand Down
7 changes: 7 additions & 0 deletions common/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,13 @@ func DecodeAddress(something []interface{}) common.Address {
return something[0].(common.Address)
}

func ToRawAmount(amount *bigNumber.Int, decimals uint64) *bigNumber.Int {
return bigNumber.NewInt(0).Mul(
amount,
bigNumber.NewInt(0).Exp(bigNumber.NewInt(10), bigNumber.NewInt(int64(decimals)), nil),
)
}

func ToNormalizedAmount(amount *bigNumber.Int, decimals uint64) *bigNumber.Float {
return bigNumber.NewFloat(0).Quo(
bigNumber.NewFloat(0).SetInt(amount),
Expand Down
1 change: 0 additions & 1 deletion internal/events/filterStakingPoolAdded.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func filterStakingPoolAdded(chainID uint64, start uint64, end *uint64, asyncMap
client := ethereum.GetRPC(chainID)
stackingReward := env.CHAINS[chainID].StackingRewardContract
if (stackingReward.Address == common.Address{}) {
logs.Error(`No stackingReward contract address for chain ` + strconv.FormatUint(chainID, 10))
return
}
contract, err := contracts.NewYOptimismStakingRewardRegistry(stackingReward.Address, client)
Expand Down
44 changes: 44 additions & 0 deletions internal/multicalls/calls.helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"math/big"

"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/logs"
Expand All @@ -25,6 +26,7 @@ var LensABI, _ = contracts.OracleMetaData.GetAbi()
var CurveGaugeABI, _ = contracts.CurveGaugeMetaData.GetAbi()
var CVXBoosterABI, _ = contracts.CVXBoosterMetaData.GetAbi()
var CrvUSDABI, _ = contracts.CrvUSDMetaData.GetAbi()
var ERC4626ABI, _ = contracts.ERC4626MetaData.GetAbi()

func GetPriceUsdcRecommendedCall(name string, contractAddress common.Address, tokenAddress common.Address) ethereum.Call {
parsedData, err := LensABI.Pack("getPriceUsdcRecommended", tokenAddress)
Expand Down Expand Up @@ -246,3 +248,45 @@ func GetCurveBalanceOf(name string, contractAddress common.Address, voter common
Name: name,
}
}

func GetConvertToShares(name string, contractAddress common.Address, oneUnit *bigNumber.Int) ethereum.Call {
parsedData, err := ERC4626ABI.Pack("convertToShares", &oneUnit.Int)
if err != nil {
logs.Error("Error packing ERC4626ABI convertToShares", err)
}
return ethereum.Call{
Target: contractAddress,
Abi: ERC4626ABI,
Method: `convertToShares`,
CallData: parsedData,
Name: name,
}
}

func GetConvertToAssets(name string, contractAddress common.Address, oneUnit *bigNumber.Int) ethereum.Call {
parsedData, err := ERC4626ABI.Pack("convertToAssets", &oneUnit.Int)
if err != nil {
logs.Error("Error packing ERC4626ABI convertToAssets", err)
}
return ethereum.Call{
Target: contractAddress,
Abi: ERC4626ABI,
Method: `convertToAssets`,
CallData: parsedData,
Name: name,
}
}

func GetAsset(name string, contractAddress common.Address) ethereum.Call {
parsedData, err := ERC4626ABI.Pack("asset")
if err != nil {
logs.Error("Error packing ERC4626ABI asset", err)
}
return ethereum.Call{
Target: contractAddress,
Abi: ERC4626ABI,
Method: `asset`,
CallData: parsedData,
Name: name,
}
}
11 changes: 11 additions & 0 deletions internal/tokens/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,17 @@ func RetrieveAllTokens(
}
}

/**********************************************************************************************
** We may have a list of extra tokens to add to the list. This is used for some tokens that
** are not in the registry but that we still want to track.
**********************************************************************************************/
for _, extraToken := range env.CHAINS[chainID].ExtraTokens {
updatedTokenMap[extraToken] = models.TERC20Token{
Address: extraToken,
ChainID: chainID,
}
}

// RESET ALL DB
for _, currentVault := range vaults {
if _, ok := tokenMap[currentVault.Address]; !ok {
Expand Down
77 changes: 77 additions & 0 deletions processes/prices/fetcher.ERC4626.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package prices

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/yearn/ydaemon/common/bigNumber"
"github.com/yearn/ydaemon/common/ethereum"
"github.com/yearn/ydaemon/common/helpers"
"github.com/yearn/ydaemon/common/store"
"github.com/yearn/ydaemon/internal/multicalls"
)

type TVaultToAsset struct {
VaultAddress common.Address
AssetAddress common.Address
Value *bigNumber.Int
}

/**************************************************************************************************
** fetchShareValueFromERC4626 will try to get the value of the assets for a ERC4626 vault type token
** It will return an array of struct with vault/asset/value
**************************************************************************************************/
func fetchShareValueFromERC4626(chainID uint64, blockNumber *uint64, tokenList []common.Address) []TVaultToAsset {
vaultToAsset := []TVaultToAsset{}

/**********************************************************************************************
** The first step is to prepare the multicall, connecting to the multicall instance and
** preparing the array of calls to send. All calls for all tokenList will be send in a single
** multicall and will later be accessible via a concatened string `tokenAddress + methodName`.
**********************************************************************************************/
calls := []ethereum.Call{}
for _, tokenAddress := range tokenList {
if token, ok := store.GetERC20(chainID, tokenAddress); ok {
oneUnitScaledToDecimals := helpers.ToRawAmount(bigNumber.NewInt(1), token.Decimals)
calls = append(calls, multicalls.GetConvertToAssets(tokenAddress.Hex(), tokenAddress, oneUnitScaledToDecimals))
calls = append(calls, multicalls.GetAsset(tokenAddress.Hex(), tokenAddress))
}
}

/**********************************************************************************************
** Then we can proceed the responses. We loop over the responses and check if the price is
** available. If it is, we add it to the map. If it's not, we try to fetch it from an external
** API.
**********************************************************************************************/
var response map[string][]interface{}
var blockNumberBigInt *big.Int

if blockNumber == nil {
currentBlockNumber, _ := ethereum.GetRPC(chainID).BlockNumber(context.Background())
blockNumber = &currentBlockNumber
response = multicalls.Perform(chainID, calls, nil)
} else {
blockNumberBigInt = big.NewInt(int64(*blockNumber))
response = multicalls.Perform(chainID, calls, blockNumberBigInt)
}

for _, token := range tokenList {
rawConvertedToAsset := response[token.Hex()+`convertToAssets`]
rawAsset := response[token.Hex()+`asset`]
if len(rawConvertedToAsset) == 0 || len(rawAsset) == 0 {
continue
}
tokenPrice := bigNumber.SetInt(rawConvertedToAsset[0].(*big.Int))
if tokenPrice.IsZero() {
continue
}

vaultToAsset = append(vaultToAsset, TVaultToAsset{
VaultAddress: token,
AssetAddress: helpers.DecodeAddress(rawAsset),
Value: helpers.DecodeBigInt(rawConvertedToAsset),
})
}
return vaultToAsset
}
32 changes: 32 additions & 0 deletions processes/prices/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,38 @@ func fetchPrices(
}
}

/**********************************************************************************************
** With the ERC-4626 standard, the `price per share` is no longer relevant. We can use the new
** `convertToAssets` function to get the value of the underlying asset for a given amount of
** shares: 1 share will give me 1.23 asset for example.
** Based on that, if we have the price of the underlying asset, we can calculate the price of
** the share.
**********************************************************************************************/
sharesValue := fetchShareValueFromERC4626(chainID, blockNumber, queryList)
for _, shareValue := range sharesValue {
if shareValue.Value == nil || shareValue.Value.IsZero() {
continue
}
if newPriceMap[shareValue.AssetAddress] == nil || newPriceMap[shareValue.AssetAddress].IsZero() {
continue
}

token, ok := store.GetERC20(chainID, shareValue.AssetAddress)
if !ok {
continue
}

tokenDecimals := helpers.ToRawAmount(bigNumber.NewInt(1), token.Decimals)
sharePrice := bigNumber.NewFloat(0).Quo(
bigNumber.NewFloat(0).Mul(
bigNumber.NewFloat(0).SetInt(shareValue.Value),
bigNumber.NewFloat(0).SetInt(newPriceMap[shareValue.AssetAddress]),
),
bigNumber.NewFloat(0).SetInt(tokenDecimals),
)
newPriceMap[shareValue.VaultAddress] = sharePrice.Int()
}

/**********************************************************************************************
** If the price is missing, check if it's a vault and try to compute the price from the
** underlying tokens.
Expand Down
File renamed without changes.

0 comments on commit 5dda9d8

Please sign in to comment.