Newer
Older
Roman
committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package twap_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v10/x/gamm/twap/types"
)
var ThreePlusOneThird sdk.Dec = sdk.MustNewDecFromStr("3.333333333333333333")
func (s *TestSuite) TestGetBeginBlockAccumulatorRecord() {
poolId, denomA, denomB := s.setupDefaultPool()
initStartRecord := newRecord(s.Ctx.BlockTime(), sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec())
initStartRecord.PoolId, initStartRecord.Height = poolId, s.Ctx.BlockHeight()
initStartRecord.Asset0Denom, initStartRecord.Asset1Denom = denomA, denomB
zeroAccumTenPoint1Record := recordWithUpdatedSpotPrice(initStartRecord, sdk.NewDec(10), sdk.NewDecWithPrec(1, 1))
tests := map[string]struct {
// if start record is blank, don't do any sets
startRecord types.TwapRecord
// We set it to have the updated time
expRecord types.TwapRecord
time time.Time
poolId uint64
quoteDenom string
baseDenom string
expError bool
}{
"no record (wrong pool ID)": {initStartRecord, initStartRecord, baseTime, 4, denomA, denomB, true},
"default record": {initStartRecord, initStartRecord, baseTime, 1, denomA, denomB, false},
"one second later record": {initStartRecord, recordWithUpdatedAccum(initStartRecord, OneSec, OneSec), tPlusOne, 1, denomA, denomB, false},
"idempotent overwrite": {initStartRecord, initStartRecord, baseTime, 1, denomA, denomB, false},
"idempotent overwrite2": {initStartRecord, recordWithUpdatedAccum(initStartRecord, OneSec, OneSec), tPlusOne, 1, denomA, denomB, false},
"diff spot price": {zeroAccumTenPoint1Record,
recordWithUpdatedAccum(zeroAccumTenPoint1Record, OneSec.MulInt64(10), OneSec.QuoInt64(10)),
tPlusOne, 1, denomA, denomB, false},
// TODO: Overflow
}
for name, tc := range tests {
s.Run(name, func() {
// setup time
s.Ctx = s.Ctx.WithBlockTime(tc.time)
tc.expRecord.Time = tc.time
s.twapkeeper.StoreNewRecord(s.Ctx, tc.startRecord)
actualRecord, err := s.twapkeeper.GetBeginBlockAccumulatorRecord(s.Ctx, tc.poolId, tc.baseDenom, tc.quoteDenom)
if tc.expError {
s.Require().Error(err)
return
}
s.Require().NoError(err)
s.Require().Equal(tc.expRecord, actualRecord)
})
}
}
func (s *TestSuite) TestGetArithmeticTwap() {
type getTwapInput struct {
poolId uint64
quoteAssetDenom string
baseAssetDenom string
startTime time.Time
endTime time.Time
}
makeSimpleTwapInput := func(startTime time.Time, endTime time.Time, isQuoteTokenA bool) getTwapInput {
quoteAssetDenom, baseAssetDenom := denom0, denom1
if isQuoteTokenA {
baseAssetDenom, quoteAssetDenom = quoteAssetDenom, baseAssetDenom
}
return getTwapInput{1, quoteAssetDenom, baseAssetDenom, startTime, endTime}
}
quoteAssetA := true
quoteAssetB := false
// base record is a record with t=baseTime, sp0=10, sp1=.1, accumulators set to 0
baseRecord := newTwapRecordWithDefaults(baseTime, sdk.NewDec(10), sdk.ZeroDec(), sdk.ZeroDec())
// record with t=baseTime+10, sp0=5, sp1=.2, accumulators updated from baseRecord
// accum0 = 10 seconds * (spot price = 10), accum1 = 10 seconds * (spot price = .1)
accum0, accum1 := OneSec.MulInt64(10*10), OneSec
tPlus10sp5Record := newTwapRecordWithDefaults(
baseTime.Add(10*time.Second), sdk.NewDec(5), accum0, accum1)
// TODO: Make use of the below for test cases:
// record with t=baseTime+20, sp0=2, sp1=.5, accumulators updated from tPlus10sp5Record
// tPlus20sp2Record := newTwapRecordWithDefaults(
// baseTime.Add(20*time.Second), sdk.NewDec(2), OneSec.MulInt64(10*10+5*10), OneSec.MulInt64(3))
tests := map[string]struct {
recordsToSet []types.TwapRecord
ctxTime time.Time
input getTwapInput
expTwap sdk.Dec
expErrorStr string
}{
"(1 record) start and end point to same record": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(1 record) start and end point to same record, use sp1": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetB),
expTwap: sdk.NewDecWithPrec(1, 1),
},
"(1 record) start and end point to same record, end time = now": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(time.Minute), quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(2 record) start and end point to same record": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(2 record) start and end exact, different records": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(10*time.Second), quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(2 record) start exact, end after second record": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(20*time.Second), quoteAssetA),
expTwap: sdk.NewDecWithPrec(75, 1), // 10 for 10s, 5 for 10s
},
"(2 record) start exact, end after second record, sp1": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(20*time.Second), quoteAssetB),
expTwap: sdk.NewDecWithPrec(15, 2), // .1 for 10s, .2 for 10s
},
"(2 record) start and end interpolated": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime.Add(5*time.Second), baseTime.Add(20*time.Second), quoteAssetA),
// 10 for 5s, 5 for 10s = 100/15 = 6 + 2/3 = 6.66666666
expTwap: ThreePlusOneThird.MulInt64(2),
},
// error catching
"end time in future": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime,
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetA),
expErrorStr: "future",
},
"start time after end time": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime,
input: makeSimpleTwapInput(tPlusOne, baseTime, quoteAssetA),
expErrorStr: "after",
},
"start time too old (end time = now)": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime,
input: makeSimpleTwapInput(baseTime.Add(-time.Hour), baseTime, quoteAssetA),
expErrorStr: "too old",
},
"start time too old": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Second),
input: makeSimpleTwapInput(baseTime.Add(-time.Hour), baseTime, quoteAssetA),
expErrorStr: "too old",
},
// TODO: overflow tests, multi-asset pool handling, make more record interpolation cases
}
for name, test := range tests {
s.Run(name, func() {
s.SetupTest()
for _, record := range test.recordsToSet {
s.twapkeeper.StoreNewRecord(s.Ctx, record)
}
s.Ctx = s.Ctx.WithBlockTime(test.ctxTime)
twap, err := s.twapkeeper.GetArithmeticTwap(s.Ctx, test.input.poolId,
test.input.quoteAssetDenom, test.input.baseAssetDenom,
test.input.startTime, test.input.endTime)
if test.expErrorStr != "" {
s.Require().Error(err)
s.Require().Contains(err.Error(), test.expErrorStr)
return
}
s.Require().NoError(err)
s.Require().Equal(test.expTwap, twap)
})
}
}