UNPKG

18.9 kBPlain TextView Raw
1import chai, {expect} from 'chai'
2import {Contract, BigNumber} from 'ethers'
3import {solidity, MockProvider, deployContract} from 'ethereum-waffle'
4
5import FixedPointTest from '../build/FixedPointTest.json'
6
7chai.use(solidity)
8
9const overrides = {
10 gasLimit: 9999999,
11}
12
13const Q112 = BigNumber.from(2).pow(112)
14
15describe('FixedPoint', () => {
16 const provider = new MockProvider({
17 ganacheOptions: {
18 hardfork: 'istanbul',
19 mnemonic: 'horn horn horn horn horn horn horn horn horn horn horn horn',
20 gasLimit: 9999999,
21 },
22 })
23 const [wallet] = provider.getWallets()
24
25 let fixedPoint: Contract
26 before('deploy FixedPointTest', async () => {
27 fixedPoint = await deployContract(wallet, FixedPointTest, [], overrides)
28 })
29
30 describe('#encode', () => {
31 it('shifts left by 112', async () => {
32 expect((await fixedPoint.encode('0x01'))[0]).to.eq(Q112.toHexString())
33 })
34 it('will not take >uint112(-1)', async () => {
35 expect(() => fixedPoint.encode(BigNumber.from(2).pow(113).sub(1))).to.throw
36 })
37 })
38
39 describe('#encode144', () => {
40 it('shifts left by 112', async () => {
41 expect((await fixedPoint.encode144('0x01'))[0]).to.eq(Q112.toHexString())
42 })
43 it('will not take >uint144(-1)', async () => {
44 expect(() => fixedPoint.encode144(BigNumber.from(2).pow(145).sub(1))).to.throw
45 })
46 })
47
48 describe('#decode', () => {
49 it('shifts right by 112', async () => {
50 expect(await fixedPoint.decode([BigNumber.from(3).mul(Q112)])).to.eq(BigNumber.from(3))
51 })
52 it('will not take >uint224(-1)', async () => {
53 expect(() => fixedPoint.decode([BigNumber.from(2).pow(225).sub(1)])).to.throw
54 })
55 })
56
57 describe('#decode144', () => {
58 it('shifts right by 112', async () => {
59 expect(await fixedPoint.decode([BigNumber.from(3).mul(Q112)])).to.eq(BigNumber.from(3))
60 })
61
62 it('will not take >uint256(-1)', async () => {
63 expect(() => fixedPoint.decode([BigNumber.from(2).pow(257).sub(1)])).to.throw
64 })
65 })
66
67 describe('#mul', () => {
68 it('works for 0', async () => {
69 expect((await fixedPoint.mul([0], 1))[0]).to.eq(0)
70 expect((await fixedPoint.mul([1], 0))[0]).to.eq(0)
71 })
72
73 it('correct multiplication', async () => {
74 expect((await fixedPoint.mul([BigNumber.from(3).mul(Q112)], BigNumber.from(2)))[0]).to.eq(
75 BigNumber.from(3).mul(2).mul(Q112)
76 )
77 })
78
79 it('overflow', async () => {
80 await expect(fixedPoint.mul([BigNumber.from(1).mul(Q112)], BigNumber.from(2).pow(144))).to.be.revertedWith(
81 'FixedPoint::mul: overflow'
82 )
83 })
84
85 it('max of q112x112', async () => {
86 expect((await fixedPoint.mul([BigNumber.from(2).pow(112)], BigNumber.from(2).pow(112)))[0]).to.eq(
87 BigNumber.from(2).pow(224)
88 )
89 })
90
91 it('max without overflow, largest fixed point', async () => {
92 const maxMultiplier = BigNumber.from(2).pow(32)
93 expect((await fixedPoint.mul([BigNumber.from(2).pow(224).sub(1)], maxMultiplier))[0]).to.eq(
94 BigNumber.from('115792089237316195423570985008687907853269984665640564039457584007908834672640')
95 )
96 await expect(fixedPoint.mul([BigNumber.from(2).pow(224).sub(1)], maxMultiplier.add(1))).to.be.revertedWith(
97 'FixedPoint::mul: overflow'
98 )
99 })
100
101 it('max without overflow, smallest fixed point', async () => {
102 const maxUint = BigNumber.from(2).pow(256).sub(1)
103 expect((await fixedPoint.mul([BigNumber.from(1)], maxUint))[0]).to.eq(maxUint)
104 await expect(fixedPoint.mul([BigNumber.from(2)], maxUint)).to.be.revertedWith('FixedPoint::mul: overflow')
105 })
106 })
107
108 describe('#muli', () => {
109 it('works for 0', async () => {
110 expect(await fixedPoint.muli([BigNumber.from(0).mul(Q112)], BigNumber.from(1))).to.eq(BigNumber.from(0))
111 expect(await fixedPoint.muli([BigNumber.from(1).mul(Q112)], BigNumber.from(0))).to.eq(BigNumber.from(0))
112 })
113
114 it('works for 3*2', async () => {
115 expect(await fixedPoint.muli([BigNumber.from(3).mul(Q112)], BigNumber.from(2))).to.eq(BigNumber.from(6))
116 })
117
118 it('works for 3*-2', async () => {
119 expect(await fixedPoint.muli([BigNumber.from(3).mul(Q112)], BigNumber.from(-2))).to.eq(BigNumber.from(-6))
120 })
121
122 it('max without overflow, largest int', async () => {
123 const maxInt = BigNumber.from(2).pow(255).sub(1)
124 expect(await fixedPoint.muli([BigNumber.from(1).mul(Q112)], maxInt)).to.be.eq(maxInt)
125
126 const minInt = BigNumber.from(2).pow(255).mul(-1)
127 await expect(fixedPoint.muli([BigNumber.from(1).mul(Q112)], minInt)).to.be.revertedWith(
128 'FixedPoint::muli: overflow'
129 )
130
131 expect(await fixedPoint.muli([BigNumber.from(1).mul(Q112).sub(1)], minInt)).to.be.eq(
132 '-57896044618658097711785492504343942776262393067508711251869655679775811829760'
133 )
134 expect(await fixedPoint.muli([BigNumber.from(1).mul(Q112)], minInt.add(1))).to.be.eq(minInt.add(1))
135 })
136 it('max without overflow, largest fixed point', async () => {
137 const maxMultiplier = BigNumber.from(2)
138 .pow(255 + 112)
139 .div(BigNumber.from(2).pow(224).sub(1))
140 expect(await fixedPoint.muli([BigNumber.from(2).pow(224).sub(1)], maxMultiplier)).to.eq(
141 BigNumber.from('57896044618658097711785492504343953926634992332820282019728792003954417336320')
142 )
143 await expect(fixedPoint.muli([BigNumber.from(2).pow(224).sub(1)], maxMultiplier.add(1))).to.be.revertedWith(
144 'FixedPoint::muli: overflow'
145 )
146
147 // negative versions
148 expect(await fixedPoint.muli([BigNumber.from(2).pow(224).sub(1)], maxMultiplier.mul(-1))).to.eq(
149 BigNumber.from('57896044618658097711785492504343953926634992332820282019728792003954417336320').mul(-1)
150 )
151 await expect(
152 fixedPoint.muli([BigNumber.from(2).pow(224).sub(1)], maxMultiplier.add(1).mul(-1))
153 ).to.be.revertedWith('FixedPoint::muli: overflow')
154 })
155 })
156
157 describe('#muluq', () => {
158 it('works for 0', async () => {
159 expect((await fixedPoint.muluq([BigNumber.from(0)], [Q112]))[0]).to.eq(BigNumber.from(0))
160 expect((await fixedPoint.muluq([Q112], [BigNumber.from(0)]))[0]).to.eq(BigNumber.from(0))
161 })
162
163 it('multiplies 3*2', async () => {
164 expect((await fixedPoint.muluq([BigNumber.from(3).mul(Q112)], [BigNumber.from(2).mul(Q112)]))[0]).to.eq(
165 BigNumber.from(3).mul(2).mul(Q112)
166 )
167 })
168 function multiplyExpanded(self: BigNumber, other: BigNumber): BigNumber {
169 const upper = self.shr(112).mul(other.shr(112))
170 const lower = self.mask(112).mul(other.mask(112))
171 const uppersLowero = self.shr(112).mul(other.mask(112))
172 const upperoLowers = self.mask(112).mul(other.shr(112))
173 return upper.mul(Q112).add(uppersLowero).add(upperoLowers).add(lower.div(Q112))
174 }
175 it('multiplies 4/3*4/3', async () => {
176 const multiplier = BigNumber.from(4).mul(Q112).div(3)
177 const expectedResult = multiplyExpanded(multiplier, multiplier)
178 expect((await fixedPoint.muluq([multiplier], [multiplier]))[0]).to.eq(expectedResult)
179 expect(expectedResult.add(1)).to.eq(BigNumber.from(16).mul(Q112).div(9)) // close to 16/9
180 })
181
182 it('overflow upper', async () => {
183 const multiplier1 = Q112.mul(2)
184 const multiplier2 = Q112.mul(Q112).div(2)
185 await expect(fixedPoint.muluq([multiplier1], [multiplier2])).to.be.revertedWith(
186 'FixedPoint::muluq: upper overflow'
187 )
188 expect((await fixedPoint.muluq([multiplier1.sub(1)], [multiplier2]))[0]).to.eq(
189 multiplyExpanded(multiplier1.sub(1), multiplier2)
190 )
191 expect((await fixedPoint.muluq([multiplier1], [multiplier2.sub(1)]))[0]).to.eq(
192 multiplyExpanded(multiplier1, multiplier2.sub(1))
193 )
194 })
195
196 it('gas for short circuit where one multiplicand is 0', async () => {
197 expect(await fixedPoint.getGasCostOfMuluq([BigNumber.from(0)], [BigNumber.from(30).mul(Q112)])).to.eq(671)
198 expect(await fixedPoint.getGasCostOfMuluq([BigNumber.from(50).mul(Q112)], [BigNumber.from(0)])).to.eq(688)
199 })
200
201 it('gas', async () => {
202 expect(await fixedPoint.getGasCostOfMuluq([BigNumber.from(30).mul(Q112)], [BigNumber.from(30).mul(Q112)])).to.eq(
203 992
204 )
205 })
206 })
207
208 describe('#divuq', () => {
209 it('works for 0 numerator', async () => {
210 expect((await fixedPoint.divuq([BigNumber.from(0)], [Q112]))[0]).to.eq(BigNumber.from(0))
211 })
212
213 it('throws for 0 denominator', async () => {
214 await expect(fixedPoint.divuq([Q112], [BigNumber.from(0)])).to.be.revertedWith(
215 'FixedPoint::divuq: division by zero'
216 )
217 })
218
219 it('equality 30/30', async () => {
220 expect((await fixedPoint.divuq([BigNumber.from(30).mul(Q112)], [BigNumber.from(30).mul(Q112)]))[0]).to.eq(Q112)
221 })
222
223 it('divides 30/10', async () => {
224 expect((await fixedPoint.divuq([BigNumber.from(30).mul(Q112)], [BigNumber.from(10).mul(Q112)]))[0]).to.eq(
225 BigNumber.from(3).mul(Q112)
226 )
227 })
228
229 it('divides 35/8', async () => {
230 expect((await fixedPoint.divuq([BigNumber.from(35).mul(Q112)], [BigNumber.from(8).mul(Q112)]))[0]).to.eq(
231 BigNumber.from(4375).mul(Q112).div(1000)
232 )
233 })
234
235 it('divides 1/3', async () => {
236 expect((await fixedPoint.divuq([BigNumber.from(1).mul(Q112)], [BigNumber.from(3).mul(Q112)]))[0]).to.eq(
237 // this is max precision 0.3333 repeating
238 '1730765619511609209510165443073365'
239 )
240 })
241
242 it('divides 1e15/3e15 (long division, repeating)', async () => {
243 expect(
244 (
245 await fixedPoint.divuq(
246 [BigNumber.from(10).pow(15).mul(Q112)],
247 [BigNumber.from(3).mul(BigNumber.from(10).pow(15)).mul(Q112)]
248 )
249 )[0]
250 ).to.eq('1730765619511609209510165443073365')
251 })
252
253 it('boundary of full precision', async () => {
254 const maxNumeratorFullPrecision = BigNumber.from(2).pow(144).sub(1)
255 const minDenominatorFullPrecision = BigNumber.from('4294967296') // ceiling(uint144(-1) * Q112 / uint224(-1))
256
257 expect((await fixedPoint.divuq([maxNumeratorFullPrecision], [minDenominatorFullPrecision]))[0]).to.eq(
258 BigNumber.from('26959946667150639794667015087019630673637143213614752866474435543040')
259 )
260
261 await expect(
262 fixedPoint.divuq([maxNumeratorFullPrecision.add(1)], [minDenominatorFullPrecision])
263 ).to.be.revertedWith('FixedPoint::divuq: overflow')
264
265 await expect(
266 fixedPoint.divuq([maxNumeratorFullPrecision], [minDenominatorFullPrecision.sub(1)])
267 ).to.be.revertedWith('FixedPoint::divuq: overflow')
268 })
269
270 it('precision', async () => {
271 const numerator = BigNumber.from(2).pow(144)
272
273 expect((await fixedPoint.divuq([numerator], [numerator.sub(1)]))[0]).to.eq(
274 BigNumber.from('5192296858534827628530496329220096')
275 )
276
277 expect((await fixedPoint.divuq([numerator], [numerator.add(1)]))[0]).to.eq(
278 BigNumber.from('5192296858534827628530496329220095')
279 )
280 })
281
282 it('gas cost of dividend = divisor short circuit', async () => {
283 expect(await fixedPoint.getGasCostOfDivuq([BigNumber.from(30).mul(Q112)], [BigNumber.from(30).mul(Q112)])).to.eq(
284 698
285 )
286 })
287
288 it('divuq overflow with smaller numbers', async () => {
289 const numerator = BigNumber.from(2).pow(143)
290 const denominator = BigNumber.from(2).pow(29)
291 await expect(fixedPoint.divuq([numerator], [denominator])).to.be.revertedWith('FixedPoint::divuq: overflow')
292 })
293
294 it('divuq overflow with large numbers', async () => {
295 const numerator = BigNumber.from(2).pow(145)
296 const denominator = BigNumber.from(2).pow(32)
297 await expect(fixedPoint.divuq([numerator], [denominator])).to.be.revertedWith('FixedPoint::divuq: overflow')
298 })
299
300 it('gas cost of full precision small dividend short circuit', async () => {
301 expect(await fixedPoint.getGasCostOfDivuq([BigNumber.from(125).mul(Q112)], [BigNumber.from(30).mul(Q112)])).to.eq(
302 838
303 )
304 expect(await fixedPoint.getGasCostOfDivuq([BigNumber.from(28).mul(Q112)], [BigNumber.from(280).mul(Q112)])).to.eq(
305 838
306 )
307 expect(await fixedPoint.getGasCostOfDivuq([BigNumber.from(1).mul(Q112)], [BigNumber.from(3).mul(Q112)])).to.eq(
308 838
309 )
310 })
311
312 it('gas cost of long division with less than 112 iterations', async () => {
313 // long division but makes fewer iterations
314 expect(
315 await fixedPoint.getGasCostOfDivuq([BigNumber.from(10).pow(10).mul(Q112)], [BigNumber.from(25).mul(Q112)])
316 ).to.eq(1502)
317 })
318
319 it('gas cost of long division with all iterations', async () => {
320 // 1/3rd, should make all iterations
321 expect(
322 await fixedPoint.getGasCostOfDivuq(
323 [BigNumber.from(10).pow(10).mul(Q112)],
324 [BigNumber.from(3).mul(BigNumber.from(10).pow(10)).mul(Q112)]
325 )
326 ).to.eq(1502)
327 })
328 })
329
330 describe('#fraction', () => {
331 it('correct computation less than 1', async () => {
332 expect((await fixedPoint.fraction(4, 100))[0]).to.eq(BigNumber.from(4).mul(Q112).div(100))
333 })
334
335 it('correct computation greater than 1', async () => {
336 expect((await fixedPoint.fraction(100, 4))[0]).to.eq(BigNumber.from(100).mul(Q112).div(4))
337 })
338
339 it('fails with 0 denominator', async () => {
340 await expect(fixedPoint.fraction(BigNumber.from(1), BigNumber.from(0))).to.be.revertedWith(
341 'FixedPoint::fraction: division by zero'
342 )
343 })
344 it('can be called with numerator exceeding uint112 max', async () => {
345 expect((await fixedPoint.fraction(Q112.mul(2359), 6950))[0]).to.eq(Q112.mul(Q112).mul(2359).div(6950))
346 })
347 it('can be called with denominator exceeding uint112 max', async () => {
348 expect((await fixedPoint.fraction(2359, Q112.mul(2359)))[0]).to.eq(1)
349 })
350 it('can be called with numerator exceeding uint144 max', async () => {
351 expect((await fixedPoint.fraction(Q112.mul(2359).mul(BigNumber.from(2).pow(32)), Q112.mul(50)))[0]).to.eq(
352 BigNumber.from(2359).mul(Q112).mul(BigNumber.from(2).pow(32)).div(50)
353 )
354 })
355 it('can be called with numerator and denominator exceeding uint112 max', async () => {
356 expect((await fixedPoint.fraction(Q112.mul(2359), Q112.mul(50)))[0]).to.eq(BigNumber.from(2359).mul(Q112).div(50))
357 })
358 it('short circuits for 0', async () => {
359 expect((await fixedPoint.fraction(0, Q112.mul(Q112).mul(2360)))[0]).to.eq(0)
360 })
361 it('can overflow if result of division does not fit', async () => {
362 await expect(fixedPoint.fraction(Q112.mul(2359), 50)).to.be.revertedWith('FixedPoint::fraction: overflow')
363 })
364 it('gas cost of 0', async () => {
365 expect(await fixedPoint.getGasCostOfFraction(BigNumber.from(0), BigNumber.from(569))).to.eq(210)
366 })
367 it('gas cost of smaller numbers', async () => {
368 expect(await fixedPoint.getGasCostOfFraction(BigNumber.from(239), BigNumber.from(569))).to.eq(314)
369 })
370 it('gas cost of number greater than Q112 numbers', async () => {
371 expect(await fixedPoint.getGasCostOfFraction(Q112.mul(2359), Q112.mul(2360))).to.eq(314)
372 })
373 it('gas cost of number greater than Q112 numbers', async () => {
374 expect(
375 await fixedPoint.getGasCostOfFraction(Q112.mul(BigNumber.from(2).pow(32).mul(2359)), Q112.mul(2360))
376 ).to.eq(996)
377 })
378 })
379
380 describe('#reciprocal', () => {
381 it('fails for 0', async () => {
382 await expect(fixedPoint.reciprocal([BigNumber.from(0)])).to.be.revertedWith(
383 'FixedPoint::reciprocal: reciprocal of zero'
384 )
385 })
386 it('fails for 1', async () => {
387 await expect(fixedPoint.reciprocal([BigNumber.from(1)])).to.be.revertedWith('FixedPoint::reciprocal: overflow')
388 })
389 it('works for 0.25', async () => {
390 expect((await fixedPoint.reciprocal([Q112.mul(BigNumber.from(25)).div(100)]))[0]).to.eq(Q112.mul(4))
391 })
392 it('works for 5', async () => {
393 expect((await fixedPoint.reciprocal([Q112.mul(BigNumber.from(5))]))[0]).to.eq(Q112.mul(BigNumber.from(1)).div(5))
394 })
395 })
396
397 describe('#sqrt', () => {
398 it('works with 0', async () => {
399 expect((await fixedPoint.sqrt([BigNumber.from(0)]))[0]).to.eq(BigNumber.from(0))
400 })
401
402 it('works with numbers less than 1', async () => {
403 expect((await fixedPoint.sqrt([BigNumber.from(1225).mul(Q112).div(100)]))[0]).to.eq(
404 BigNumber.from(35).mul(Q112).div(10)
405 )
406 })
407
408 it('gas cost of less than 1', async () => {
409 const input = BigNumber.from(1225).mul(Q112).div(100)
410 expect(await fixedPoint.getGasCostOfSqrt([input])).to.eq(1173)
411 })
412
413 it('works for 25', async () => {
414 expect((await fixedPoint.sqrt([BigNumber.from(25).mul(Q112)]))[0]).to.eq(BigNumber.from(5).mul(Q112))
415 })
416
417 it('gas cost of 25', async () => {
418 const input = BigNumber.from(25).mul(Q112)
419 expect(await fixedPoint.getGasCostOfSqrt([input])).to.eq(1191)
420 })
421
422 it('works for max uint144', async () => {
423 const input = BigNumber.from(2).pow(144).sub(1)
424 const result = (await fixedPoint.sqrt([input]))[0]
425 const expected = BigNumber.from('340282366920938463463374607431768211455')
426 expect(result).to.eq(expected)
427 })
428
429 it('gas cost of max uint144', async () => {
430 const input = BigNumber.from(2).pow(144).sub(1)
431 expect(await fixedPoint.getGasCostOfSqrt([input])).to.eq(1235)
432 })
433
434 it('works for 2**144', async () => {
435 const input = BigNumber.from(2).pow(144)
436 const result = (await fixedPoint.sqrt([input]))[0]
437 const expected = BigNumber.from('340282366920938463463374607431768211456')
438 expect(result).to.eq(expected.shr(2).shl(2))
439 })
440
441 it('gas cost of 2**144', async () => {
442 const input = BigNumber.from(2).pow(144)
443 expect(await fixedPoint.getGasCostOfSqrt([input])).to.eq(1640)
444 })
445
446 it('works for encoded max uint112', async () => {
447 const input = BigNumber.from(2).pow(112).sub(1).mul(Q112)
448 const result = (await fixedPoint.sqrt([input]))[0]
449 const expected = BigNumber.from('374144419156711147060143317175368417003121712037887')
450 expect(result).to.eq(expected.shr(40).shl(40))
451 })
452
453 it('gas cost of encoded max uint112', async () => {
454 const input = BigNumber.from(2).pow(112).sub(1).mul(Q112)
455 expect(await fixedPoint.getGasCostOfSqrt([input])).to.eq(1723)
456 })
457
458 it('works for max uint224', async () => {
459 const input = BigNumber.from(2).pow(224).sub(1)
460 const result = (await fixedPoint.sqrt([input]))[0]
461 const expected = BigNumber.from('374144419156711147060143317175368453031918731001855')
462 expect(result).to.eq(expected.shr(40).shl(40))
463 })
464
465 it('gas cost of max uint224', async () => {
466 const input = BigNumber.from(2).pow(224).sub(1)
467 expect(await fixedPoint.getGasCostOfSqrt([input])).to.eq(1723)
468 })
469 })
470})