Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-tron committed Oct 25, 2024
2 parents a9d79b2 + e18fb6a commit b1e407d
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 16 deletions.
151 changes: 146 additions & 5 deletions pkg/rates/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"math"
"math/big"
"net/http"
"sort"
"strconv"
Expand Down Expand Up @@ -47,6 +48,14 @@ type Asset struct {
HoldersCount int
}

// LpAsset represents a liquidity provider asset that holds a collection of assets in a pool
type LpAsset struct {
Account ton.AccountID
Decimals int
TotalSupply *big.Int // The total supply of the liquidity provider asset
Assets []Asset // A slice of Asset included in the liquidity pool
}

// DeDustAssets represents a collection of assets from the DeDust platform, including their stability status
type DeDustAssets struct {
Assets []Asset
Expand Down Expand Up @@ -429,7 +438,32 @@ func convertedStonFiPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
}
return firstAsset, secondAsset, nil
}

parseLpAsset := func(record []string, firstAsset, secondAsset Asset) (LpAsset, error) {
lpAsset, err := ton.ParseAccountID(record[8])
if err != nil {
return LpAsset{}, err
}
if record[9] == "0" {
return LpAsset{}, fmt.Errorf("unknown total supply")
}
totalSupply, ok := new(big.Int).SetString(record[9], 10)
if !ok {
return LpAsset{}, fmt.Errorf("failed to parse total supply")
}
if totalSupply.Cmp(big.NewInt(int64(defaultMinReserve))) < 0 {
return LpAsset{}, fmt.Errorf("the total supply is less than the minimum value")
}
decimals, err := strconv.Atoi(record[10])
if err != nil {
return LpAsset{}, err
}
return LpAsset{
Account: lpAsset,
Decimals: decimals,
TotalSupply: totalSupply,
Assets: []Asset{firstAsset, secondAsset},
}, nil
}
actualAssets := make(map[ton.AccountID][]Asset)
// Update the assets with the largest reserves
updateActualAssets := func(mainAsset Asset, firstAsset, secondAsset Asset) {
Expand All @@ -439,7 +473,14 @@ func convertedStonFiPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
return
}
for idx, asset := range assets {
if asset.Account == mainAsset.Account && asset.Reserve < mainAsset.Reserve {
assetReserveNormalised := asset.Reserve
mainAssetReserveNormalised := mainAsset.Reserve
// Adjust the reserves of assets considering their decimal places
if asset.Decimals != mainAsset.Decimals {
assetReserveNormalised = asset.Reserve / math.Pow10(asset.Decimals)
mainAssetReserveNormalised = mainAsset.Reserve / math.Pow10(mainAsset.Decimals)
}
if asset.Account == mainAsset.Account && assetReserveNormalised < mainAssetReserveNormalised {
if idx == 0 {
actualAssets[mainAsset.Account] = []Asset{firstAsset, secondAsset}
} else {
Expand All @@ -448,8 +489,9 @@ func convertedStonFiPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
}
}
}
actualLpAssets := make(map[ton.AccountID]LpAsset)
for idx, record := range records {
if idx == 0 || len(record) < 8 { // Skip headers
if idx == 0 || len(record) < 10 { // Skip headers
continue
}
firstAsset, secondAsset, err := parseAssets(record)
Expand All @@ -469,6 +511,10 @@ func convertedStonFiPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
(isNotPTon(secondAsset.Account) && secondAsset.HoldersCount < defaultMinHoldersCount) {
continue
}
lpAsset, err := parseLpAsset(record, firstAsset, secondAsset)
if err == nil {
actualLpAssets[lpAsset.Account] = lpAsset
}
updateActualAssets(firstAsset, firstAsset, secondAsset)
updateActualAssets(secondAsset, firstAsset, secondAsset)
}
Expand All @@ -481,6 +527,16 @@ func convertedStonFiPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
pools[accountID] = price
}
}
for _, asset := range actualLpAssets {
if _, ok := pools[asset.Account]; ok {
continue
}
price := calculateLpAssetPrice(asset, pools)
if price == 0 {
continue
}
pools[asset.Account] = price
}
return pools, nil
}

Expand Down Expand Up @@ -576,6 +632,32 @@ func convertedDeDustPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
}
return DeDustAssets{Assets: []Asset{firstAsset, secondAsset}, IsStable: isStable}, nil
}
parseLpAsset := func(record []string, firstAsset, secondAsset Asset) (LpAsset, error) {
lpAsset, err := ton.ParseAccountID(record[11])
if err != nil {
return LpAsset{}, err
}
if record[12] == "0" {
return LpAsset{}, fmt.Errorf("unknown total supply")
}
totalSupply, ok := new(big.Int).SetString(record[12], 10)
if !ok {
return LpAsset{}, fmt.Errorf("failed to parse total supply")
}
if totalSupply.Cmp(big.NewInt(int64(defaultMinReserve))) < 0 {
return LpAsset{}, fmt.Errorf("the total supply is less than the minimum value")
}
decimals, err := strconv.Atoi(record[13])
if err != nil {
return LpAsset{}, err
}
return LpAsset{
Account: lpAsset,
Decimals: decimals,
TotalSupply: totalSupply,
Assets: []Asset{firstAsset, secondAsset},
}, nil
}
actualAssets := make(map[ton.AccountID]DeDustAssets)
// Update the assets with the largest reserves
updateActualAssets := func(mainAsset Asset, deDustAssets DeDustAssets) {
Expand All @@ -586,7 +668,14 @@ func convertedDeDustPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
return
}
for idx, asset := range assets.Assets {
if asset.Account == mainAsset.Account && asset.Reserve < mainAsset.Reserve {
assetReserveNormalised := asset.Reserve
mainAssetReserveNormalised := mainAsset.Reserve
// Adjust the reserves of assets considering their decimal places
if asset.Decimals != mainAsset.Decimals {
assetReserveNormalised = asset.Reserve / math.Pow10(asset.Decimals)
mainAssetReserveNormalised = mainAsset.Reserve / math.Pow10(mainAsset.Decimals)
}
if asset.Account == mainAsset.Account && assetReserveNormalised < mainAssetReserveNormalised {
if idx == 0 {
actualAssets[mainAsset.Account] = DeDustAssets{Assets: []Asset{firstAsset, secondAsset}, IsStable: deDustAssets.IsStable}
} else {
Expand All @@ -595,8 +684,9 @@ func convertedDeDustPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
}
}
}
actualLpAssets := make(map[ton.AccountID]LpAsset)
for idx, record := range records {
if idx == 0 || len(record) < 10 { // Skip headers
if idx == 0 || len(record) < 14 { // Skip headers
continue
}
assets, err := parseAssets(record)
Expand All @@ -608,6 +698,10 @@ func convertedDeDustPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
if firstAsset.Reserve == 0 || secondAsset.Reserve == 0 {
continue
}
lpAsset, err := parseLpAsset(record, firstAsset, secondAsset)
if err == nil {
actualLpAssets[lpAsset.Account] = lpAsset
}
updateActualAssets(firstAsset, assets)
updateActualAssets(secondAsset, assets)
}
Expand All @@ -620,10 +714,57 @@ func convertedDeDustPoolResponse(pools map[ton.AccountID]float64, respBody io.Re
pools[accountID] = price
}
}
for _, asset := range actualLpAssets {
if _, ok := pools[asset.Account]; ok {
continue
}
price := calculateLpAssetPrice(asset, pools)
if price == 0 {
continue
}
pools[asset.Account] = price
}

return pools, nil
}

func calculateLpAssetPrice(asset LpAsset, pools map[ton.AccountID]float64) float64 {
firstAsset := asset.Assets[0]
secondAsset := asset.Assets[1]
firstAssetPrice, ok := pools[firstAsset.Account]
if !ok {
return 0
}
secondAssetPrice, ok := pools[secondAsset.Account]
if !ok {
return 0
}
// Adjust the reserves of assets considering their decimal places
firstAssetAdjustedReserve := new(big.Float).Quo(
big.NewFloat(firstAsset.Reserve),
new(big.Float).SetFloat64(math.Pow(10, float64(firstAsset.Decimals))),
)
secondAssetAdjustedReserve := new(big.Float).Quo(
big.NewFloat(secondAsset.Reserve),
new(big.Float).SetFloat64(math.Pow(10, float64(secondAsset.Decimals))),
)
// Calculate the total value of the jetton by summing the values of both assets
totalValue := new(big.Float).Add(
new(big.Float).Mul(firstAssetAdjustedReserve, big.NewFloat(firstAssetPrice)),
new(big.Float).Mul(secondAssetAdjustedReserve, big.NewFloat(secondAssetPrice)),
)
// Adjust the total supply for the asset decimals
totalSupplyAdjusted := new(big.Float).Quo(
new(big.Float).SetInt(asset.TotalSupply),
new(big.Float).SetFloat64(math.Pow(10, float64(asset.Decimals))),
)
// Calculate the price of the jetton by dividing the total value by the adjusted total supply of tokens
price := new(big.Float).Quo(totalValue, totalSupplyAdjusted)

convertedPrice, _ := price.Float64()
return convertedPrice
}

func calculatePoolPrice(firstAsset, secondAsset Asset, pools map[ton.AccountID]float64, isStable bool) (ton.AccountID, float64) {
priceFirst, okFirst := pools[firstAsset.Account]
priceSecond, okSecond := pools[secondAsset.Account]
Expand Down
22 changes: 11 additions & 11 deletions pkg/rates/rates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func TestCalculateJettonPriceFromStonFiPool(t *testing.T) {
{
name: "Successful calculate Scaleton and USD₮ jettons",
csv: `
asset_0_account_id,asset_1_account_id,asset_0_reserve,asset_1_reserve,asset_0_metadata,asset_1_metadata,asset_0_holders,asset_1_holders
0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,17245
0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,54581198678395,9745288354931876,"{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}","{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}",1038000,52
0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,0:afc49cb8786f21c87045b19ede78fc6b46c51048513f8e9a6d44060199c1bf0c,996119000168,921942515487299500,"{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}","{""decimals"":""9"",""name"":""Dogs"",""symbol"":""DOGS""}",1050066,881834`,
asset_0_account_id,asset_1_account_id,asset_0_reserve,asset_1_reserve,asset_0_metadata,asset_1_metadata,asset_0_holders,asset_1_holders,lp_jetton,total_supply,lp_jetton_decimals
0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,17245,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,54581198678395,9745288354931876,"{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}","{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}",1038000,52,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,0:afc49cb8786f21c87045b19ede78fc6b46c51048513f8e9a6d44060199c1bf0c,996119000168,921942515487299500,"{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}","{""decimals"":""9"",""name"":""Dogs"",""symbol"":""DOGS""}",1050066,881834,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9`,
expected: map[ton.AccountID]float64{
ton.MustParseAccountID("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c"): 1, // Default pTonV1 price
ton.MustParseAccountID("0:671963027f7f85659ab55b821671688601cdcf1ee674fc7fbbb1a776a18d34a3"): 1, // Default pTonV2 price
Expand All @@ -39,8 +39,8 @@ asset_0_account_id,asset_1_account_id,asset_0_reserve,asset_1_reserve,asset_0_me
{
name: "Failed calculate Scaleton (insufficient holders)",
csv: `
asset_0_account_id,asset_1_account_id,asset_0_reserve,asset_1_reserve,asset_0_metadata,asset_1_metadata,asset_0_holders,asset_1_holders
0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,10
asset_0_account_id,asset_1_account_id,asset_0_reserve,asset_1_reserve,asset_0_metadata,asset_1_metadata,asset_0_holders,asset_1_holders,lp_jetton,total_supply,lp_jetton_decimals
0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,10,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
`,
expected: map[ton.AccountID]float64{
ton.MustParseAccountID("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c"): 1, // Default pTonV1 price
Expand Down Expand Up @@ -83,11 +83,11 @@ func TestCalculateJettonPriceFromDeDustPool(t *testing.T) {
{
name: "Successful calculate",
csv: `
asset_0_account_id,asset_1_account_id,asset_0_native,asset_1_native,asset_0_reserve,asset_1_reserve,asset_0_metadata,asset_1_metadata,is_stable,asset_0_holders,asset_1_holders
NULL,0:022d70f08add35b2d8aa2bd16f622268d7996e5737c3e7353cbb00d2aba257c5,true,false,100171974809,1787220634679,NULL,"{""decimals"":""8"",""name"":""Spintria"",""symbol"":""SP""}",false,0,3084
NULL,0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3,true,false,22004762576054,4171862045823,NULL,"{""decimals"":""9"",""name"":""AI Coin"",""symbol"":""AIC""}",false,0,1239
0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3,0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,false,false,468457277157,13287924673,"{""decimals"":""9"",""name"":""AI Coin"",""symbol"":""AIC""}","{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}",false,1239,1039987
NULL,0:cf76af318c0872b58a9f1925fc29c156211782b9fb01f56760d292e56123bf87,true,false,5406255533839,3293533372962,NULL,"{""decimals"":""9"",""name"":""Hipo Staked TON"",""symbol"":""hTON""}",true,0,2181`,
asset_0_account_id,asset_1_account_id,asset_0_native,asset_1_native,asset_0_reserve,asset_1_reserve,asset_0_metadata,asset_1_metadata,is_stable,asset_0_holders,asset_1_holders,lp_jetton,total_supply,lp_jetton_decimals
NULL,0:022d70f08add35b2d8aa2bd16f622268d7996e5737c3e7353cbb00d2aba257c5,true,false,100171974809,1787220634679,NULL,"{""decimals"":""8"",""name"":""Spintria"",""symbol"":""SP""}",false,0,3084,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
NULL,0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3,true,false,22004762576054,4171862045823,NULL,"{""decimals"":""9"",""name"":""AI Coin"",""symbol"":""AIC""}",false,0,1239,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3,0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,false,false,468457277157,13287924673,"{""decimals"":""9"",""name"":""AI Coin"",""symbol"":""AIC""}","{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}",false,1239,1039987,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
NULL,0:cf76af318c0872b58a9f1925fc29c156211782b9fb01f56760d292e56123bf87,true,false,5406255533839,3293533372962,NULL,"{""decimals"":""9"",""name"":""Hipo Staked TON"",""symbol"":""hTON""}",true,0,2181,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9`,
expected: map[ton.AccountID]float64{
ton.MustParseAccountID("0:0000000000000000000000000000000000000000000000000000000000000000"): 1, // Default TON price
ton.MustParseAccountID("0:022d70f08add35b2d8aa2bd16f622268d7996e5737c3e7353cbb00d2aba257c5"): 0.005604902543383612,
Expand Down

0 comments on commit b1e407d

Please sign in to comment.