@@ -3,116 +3,212 @@ package rates
3
3
import (
4
4
"bytes"
5
5
"io"
6
+ "reflect"
6
7
"testing"
7
8
8
- "github.com/stretchr/testify/assert "
9
+ "github.com/tonkeeper/opentonapi/pkg/references "
9
10
"github.com/tonkeeper/tongo/ton"
10
11
)
11
12
12
- func TestCalculateJettonPriceFromStonFiPool (t * testing.T ) {
13
+ func TestSortReserveAndAssetPairs (t * testing.T ) {
14
+ usdt := ton .MustParseAccountID ("EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs" )
15
+ pTon := ton .MustParseAccountID ("EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez" )
16
+ notcoin := ton .MustParseAccountID ("EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT" )
17
+ bolt := ton .MustParseAccountID ("EQD0vdSA_NedR9uvbgN9EikRX-suesDxGeFg69XQMavfLqIw" )
18
+
19
+ assetPairs := map [ton.AccountID ][]Assets {
20
+ notcoin : {
21
+ {Assets : []Asset {{Account : notcoin , Reserve : 40 }, {Account : usdt , Reserve : 50 }}},
22
+ {Assets : []Asset {{Account : pTon , Reserve : 90 }, {Account : notcoin , Reserve : 100 }}},
23
+ {Assets : []Asset {{Account : bolt , Reserve : 1000 }, {Account : notcoin , Reserve : 25 }}},
24
+ },
25
+ bolt : {
26
+ {Assets : []Asset {{Account : pTon , Reserve : 20 }, {Account : bolt , Reserve : 30 }}},
27
+ {Assets : []Asset {{Account : bolt , Reserve : 60 }, {Account : notcoin , Reserve : 50 }}},
28
+ {Assets : []Asset {{Account : usdt , Reserve : 100 }, {Account : bolt , Reserve : 100 }}},
29
+ },
30
+ }
31
+
32
+ expected := map [ton.AccountID ][]Assets {
33
+ notcoin : {
34
+ {Assets : []Asset {{Account : pTon , Reserve : 90 }, {Account : notcoin , Reserve : 100 }}},
35
+ {Assets : []Asset {{Account : notcoin , Reserve : 40 }, {Account : usdt , Reserve : 50 }}},
36
+ {Assets : []Asset {{Account : bolt , Reserve : 1000 }, {Account : notcoin , Reserve : 25 }}},
37
+ },
38
+ bolt : {
39
+ {Assets : []Asset {{Account : usdt , Reserve : 100 }, {Account : bolt , Reserve : 100 }}},
40
+ {Assets : []Asset {{Account : bolt , Reserve : 60 }, {Account : notcoin , Reserve : 50 }}},
41
+ {Assets : []Asset {{Account : pTon , Reserve : 20 }, {Account : bolt , Reserve : 30 }}},
42
+ },
43
+ }
44
+
45
+ assetPairs = sortAssetPairs (assetPairs )
46
+
47
+ for accountID , sortedAssets := range assetPairs {
48
+ expectedAssets := expected [accountID ]
49
+ for i , asset := range sortedAssets [0 ].Assets {
50
+ if asset != expectedAssets [0 ].Assets [i ] {
51
+ t .Errorf ("Mismatch for account %v: expected %v, got %v\n " , accountID , expectedAssets , sortedAssets )
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ func TestCalculatePoolPrice (t * testing.T ) {
58
+ pools := map [ton.AccountID ]float64 {
59
+ references .PTonV1 : 1 ,
60
+ references .PTonV2 : 1 ,
61
+ }
62
+
63
+ notcoin := ton .MustParseAccountID ("EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT" )
64
+ bolt := ton .MustParseAccountID ("EQD0vdSA_NedR9uvbgN9EikRX-suesDxGeFg69XQMavfLqIw" )
65
+ usdt := ton .MustParseAccountID ("EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs" )
66
+
67
+ assetPairs := map [ton.AccountID ][]Assets {
68
+ usdt : {
69
+ {Assets : []Asset {
70
+ {Account : usdt , Decimals : 6 , Reserve : 1.2723222601996e+13 , HoldersCount : 2649800 },
71
+ {Account : ton .MustParseAccountID ("0:671963027f7f85659ab55b821671688601cdcf1ee674fc7fbbb1a776a18d34a3" ), Decimals : 9 , Reserve : 3.379910335458364e+15 , HoldersCount : 39 }},
72
+ },
73
+ },
74
+ bolt : {
75
+ {Assets : []Asset {
76
+ {Account : ton .MustParseAccountID ("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c" ), Decimals : 9 , Reserve : 2.120750126788e+12 , HoldersCount : 0 },
77
+ {Account : bolt , Decimals : 9 , Reserve : 1.24636479861718e+14 , HoldersCount : 22384 }},
78
+ },
79
+ },
80
+ notcoin : {
81
+ {Assets : []Asset {
82
+ {Account : notcoin , Decimals : 9 , Reserve : 1.8417267910171e+17 , HoldersCount : 2865084 },
83
+ {Account : ton .MustParseAccountID ("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c" ), Decimals : 9 , Reserve : 1.25593435171918e+14 , HoldersCount : 82 }},
84
+ },
85
+ },
86
+ }
87
+
88
+ assetPairs = sortAssetPairs (assetPairs )
89
+ for attempt := 0 ; attempt < 3 ; attempt ++ {
90
+ for _ , assets := range assetPairs {
91
+ for _ , asset := range assets {
92
+ accountID , price := calculatePoolPrice (asset .Assets [0 ], asset .Assets [1 ], pools , asset .IsStable )
93
+ if price == 0 {
94
+ continue
95
+ }
96
+ if _ , ok := pools [accountID ]; ! ok {
97
+ pools [accountID ] = price
98
+ break
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ expectedPrices := map [ton.AccountID ]float64 {
105
+ notcoin : 0.0006819330412333231 ,
106
+ bolt : 0.017015484785360878 ,
107
+ usdt : 0.26564891939626434 ,
108
+ }
109
+
110
+ for accountID , expectedPrice := range expectedPrices {
111
+ if actualPrice , ok := pools [accountID ]; ok {
112
+ if actualPrice != expectedPrice {
113
+ t .Errorf ("Unexpected price for account %v: got %v, want %v\n " , accountID , actualPrice , expectedPrice )
114
+ }
115
+ } else {
116
+ t .Errorf ("Missing price for account %v\n " , accountID )
117
+ }
118
+ }
119
+ }
120
+
121
+ func TestParseStonFiJettonsAssets (t * testing.T ) {
13
122
tests := []struct {
14
123
name string
15
124
csv string
16
- expected map [ton. AccountID ] float64
125
+ expected [] Assets
17
126
}{
18
- // StonFi pools operate with tokens through their pTon token (0:8cdc...4e11f7c)
19
- // The price of pTon to TON is known and equals 1
20
- // Knowing the price of pTon, we can calculate the price of the Scaleton token (0:65aa...d6fedf920) using the XY pool formula
21
- // We can also calculate the price of USD₮ (0:b113...c3621dfe)
22
- {
23
- name : "Successful calculate Scaleton and USD₮ jettons" ,
24
- csv : `
25
- 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
26
- 0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,17245,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
27
- 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
28
- 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` ,
29
- expected : map [ton.AccountID ]float64 {
30
- ton .MustParseAccountID ("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c" ): 1 , // Default pTonV1 price
31
- ton .MustParseAccountID ("0:671963027f7f85659ab55b821671688601cdcf1ee674fc7fbbb1a776a18d34a3" ): 1 , // Default pTonV2 price
32
- ton .MustParseAccountID ("0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208" ): 0.6236390657633181 ,
33
- ton .MustParseAccountID ("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe" ): 0.17854661661707652 ,
34
- ton .MustParseAccountID ("0:afc49cb8786f21c87045b19ede78fc6b46c51048513f8e9a6d44060199c1bf0c" ): 0.00019291189444059384 ,
35
- },
36
- },
37
- // To display more accurate prices, the default minimum number of holders is set to 200
38
- // In this test, Scaleton only has 50 holders, so we do not calculate the token price
39
127
{
40
- name : "Failed calculate Scaleton (insufficient holders) " ,
128
+ name : "Successful parsing of Scaleton and USD₮ jettons " ,
41
129
csv : `
42
130
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
43
- 0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,10,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
131
+ 0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208,356773586306,572083446808,"{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}","{""name"":""Scaleton"",""symbol"":""SCALE""}",52,17245,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,1000000000000000,9
132
+ 0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe,0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c,54581198678395,9745288354931876,"{""decimals"":""6"",""name"":""Tether USD"",""symbol"":""USD₮""}","{""decimals"":""9"",""name"":""Proxy TON"",""symbol"":""pTON""}",1038000,52,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,2000000000000000,9
44
133
` ,
45
- expected : map [ton.AccountID ]float64 {
46
- ton .MustParseAccountID ("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c" ): 1 , // Default pTonV1 price
47
- ton .MustParseAccountID ("0:671963027f7f85659ab55b821671688601cdcf1ee674fc7fbbb1a776a18d34a3" ): 1 , // Default pTonV2 price
134
+ expected : []Assets {
135
+ {
136
+ Assets : []Asset {
137
+ {Account : ton .MustParseAccountID ("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c" ), Decimals : 9 , Reserve : 356773586306 , HoldersCount : 52 },
138
+ {Account : ton .MustParseAccountID ("0:65aac9b5e380eae928db3c8e238d9bc0d61a9320fdc2bc7a2f6c87d6fedf9208" ), Decimals : 9 , Reserve : 572083446808 , HoldersCount : 17245 },
139
+ },
140
+ },
141
+ {
142
+ Assets : []Asset {
143
+ {Account : ton .MustParseAccountID ("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe" ), Decimals : 6 , Reserve : 54581198678395 , HoldersCount : 1038000 },
144
+ {Account : ton .MustParseAccountID ("0:8cdc1d7640ad5ee326527fc1ad0514f468b30dc84b0173f0e155f451b4e11f7c" ), Decimals : 9 , Reserve : 9745288354931876 , HoldersCount : 52 },
145
+ },
146
+ },
48
147
},
49
148
},
50
149
}
51
- var err error
52
150
for _ , tt := range tests {
53
151
t .Run (tt .name , func (t * testing.T ) {
54
- pools := make (map [ton.AccountID ]float64 )
55
- for attempt := 0 ; attempt < 2 ; attempt ++ {
56
- respBody := io .NopCloser (bytes .NewReader ([]byte (tt .csv )))
57
- pools , err = convertedStonFiPoolResponse (pools , respBody )
58
- if err != nil {
59
- t .Fatalf ("[TestCalculateJettonPriceFromStonFiPool] failed to calculate jetton price: %v" , err )
60
- }
61
- }
152
+ respBody := io .NopCloser (bytes .NewReader ([]byte (tt .csv )))
153
+ assets , _ , err := convertedStonFiPoolResponse (respBody )
62
154
if err != nil {
63
- t .Fatalf ("[TestCalculateJettonPriceFromStonFiPool] failed to calculate jetton price : %v" , err )
155
+ t .Fatalf ("unexpected error : %v" , err )
64
156
}
65
- if ! assert . Equal ( t , tt .expected , pools ) {
66
- t .Errorf ("expected %v, got %v" , tt .expected , pools )
157
+ if ! reflect . DeepEqual ( assets , tt .expected ) {
158
+ t .Errorf ("expected %v, got %v" , tt .expected , assets )
67
159
}
68
160
})
69
161
}
70
162
}
71
163
72
- func TestCalculateJettonPriceFromDeDustPool (t * testing.T ) {
164
+ func TestParseDeDustJettonsAssets (t * testing.T ) {
73
165
tests := []struct {
74
166
name string
75
167
csv string
76
- expected map [ton. AccountID ] float64
168
+ expected [] Assets
77
169
}{
78
- // In the DeDust pools, if an asset has a NULL value, it means that this asset is TON. For simplicity,
79
- // we treat this asset as the zero address and assign it a price of 1 relative to TON
80
- //
81
- // In this test, we are calculating the prices of Spintria tokens (0:022d70...2aba257c5) and AI Coin (0:48cef1...33fa7e2d3) relative to TON
82
- // We are also calculating the price of USD₮ (0:b113a9...fecdc3621dfe) relative to AI Coin (0:48cef1...33fa7e2d3)
83
170
{
84
- name : "Successful calculate " ,
171
+ name : "Successful parsing of Spintria and AI Coin jettons " ,
85
172
csv : `
86
173
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
87
174
NULL,0:022d70f08add35b2d8aa2bd16f622268d7996e5737c3e7353cbb00d2aba257c5,true,false,100171974809,1787220634679,NULL,"{""decimals"":""8"",""name"":""Spintria"",""symbol"":""SP""}",false,0,3084,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
88
175
NULL,0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3,true,false,22004762576054,4171862045823,NULL,"{""decimals"":""9"",""name"":""AI Coin"",""symbol"":""AIC""}",false,0,1239,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9
89
- 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
90
- NULL,0:cf76af318c0872b58a9f1925fc29c156211782b9fb01f56760d292e56123bf87,true,false,5406255533839,3293533372962,NULL,"{""decimals"":""9"",""name"":""Hipo Staked TON"",""symbol"":""hTON""}",true,0,2181,0:4d70707f7f62d432157dd8f1a90ce7421b34bcb2ecc4390469181bc575e4739f,0,9` ,
91
- expected : map [ton.AccountID ]float64 {
92
- ton .MustParseAccountID ("0:0000000000000000000000000000000000000000000000000000000000000000" ): 1 , // Default TON price
93
- ton .MustParseAccountID ("0:022d70f08add35b2d8aa2bd16f622268d7996e5737c3e7353cbb00d2aba257c5" ): 0.005604902543383612 ,
94
- ton .MustParseAccountID ("0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3" ): 5.274566209130971 ,
95
- ton .MustParseAccountID ("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe" ): 0.1859514548223248 ,
96
- ton .MustParseAccountID ("0:cf76af318c0872b58a9f1925fc29c156211782b9fb01f56760d292e56123bf87" ): 1.0290600202253966 , // Stable pool
176
+ 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` ,
177
+ expected : []Assets {
178
+ {
179
+ Assets : []Asset {
180
+ {Account : references .PTonV1 , Decimals : 9 , Reserve : 100171974809 , HoldersCount : 0 },
181
+ {Account : ton .MustParseAccountID ("0:022d70f08add35b2d8aa2bd16f622268d7996e5737c3e7353cbb00d2aba257c5" ), Decimals : 8 , Reserve : 1787220634679 , HoldersCount : 3084 },
182
+ },
183
+ IsStable : false ,
184
+ },
185
+ {
186
+ Assets : []Asset {
187
+ {Account : references .PTonV1 , Decimals : 9 , Reserve : 22004762576054 , HoldersCount : 0 },
188
+ {Account : ton .MustParseAccountID ("0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3" ), Decimals : 9 , Reserve : 4171862045823 , HoldersCount : 1239 },
189
+ },
190
+ IsStable : false ,
191
+ },
192
+ {
193
+ Assets : []Asset {
194
+ {Account : ton .MustParseAccountID ("0:48cef1de34697508200b8026bf882f8e88aff894586cfd304ab513633fa7e2d3" ), Decimals : 9 , Reserve : 468457277157 , HoldersCount : 1239 },
195
+ {Account : ton .MustParseAccountID ("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe" ), Decimals : 6 , Reserve : 13287924673 , HoldersCount : 1039987 },
196
+ },
197
+ IsStable : false ,
198
+ },
97
199
},
98
200
},
99
201
}
100
- var err error
202
+
101
203
for _ , tt := range tests {
102
204
t .Run (tt .name , func (t * testing.T ) {
103
- pools := make (map [ton.AccountID ]float64 )
104
- for attempt := 0 ; attempt < 2 ; attempt ++ {
105
- respBody := io .NopCloser (bytes .NewReader ([]byte (tt .csv )))
106
- pools , err = convertedDeDustPoolResponse (pools , respBody )
107
- if err != nil {
108
- t .Fatalf ("[TestCalculateJettonPriceFromDeDustPool] failed to calculate jetton price: %v" , err )
109
- }
110
- }
205
+ respBody := io .NopCloser (bytes .NewReader ([]byte (tt .csv )))
206
+ assets , _ , err := convertedDeDustPoolResponse (respBody )
111
207
if err != nil {
112
- t .Fatalf ("[TestCalculateJettonPriceFromDeDustPool] failed to calculate jetton price : %v" , err )
208
+ t .Fatalf ("unexpected error : %v" , err )
113
209
}
114
- if ! assert . Equal ( t , tt .expected , pools ) {
115
- t .Errorf ("expected %v, got %v" , tt .expected , pools )
210
+ if ! reflect . DeepEqual ( assets , tt .expected ) {
211
+ t .Errorf ("expected %v, got %v" , tt .expected , assets )
116
212
}
117
213
})
118
214
}
0 commit comments