infrastructure-upgrade/lib/v3-core/test/FullMath.spec.ts

183 lines
6.1 KiB
TypeScript
Raw Normal View History

2023-04-08 21:46:18 +03:00
import { ethers } from 'hardhat'
import { FullMathTest } from '../typechain/FullMathTest'
import { expect } from './shared/expect'
import { Decimal } from 'decimal.js'
const {
BigNumber,
constants: { MaxUint256 },
} = ethers
const Q128 = BigNumber.from(2).pow(128)
Decimal.config({ toExpNeg: -500, toExpPos: 500 })
describe('FullMath', () => {
let fullMath: FullMathTest
before('deploy FullMathTest', async () => {
const factory = await ethers.getContractFactory('FullMathTest')
fullMath = (await factory.deploy()) as FullMathTest
})
describe('#mulDiv', () => {
it('reverts if denominator is 0', async () => {
await expect(fullMath.mulDiv(Q128, 5, 0)).to.be.reverted
})
it('reverts if denominator is 0 and numerator overflows', async () => {
await expect(fullMath.mulDiv(Q128, Q128, 0)).to.be.reverted
})
it('reverts if output overflows uint256', async () => {
await expect(fullMath.mulDiv(Q128, Q128, 1)).to.be.reverted
})
it('reverts if output overflows uint256', async () => {
await expect(fullMath.mulDiv(Q128, Q128, 1)).to.be.reverted
})
it('reverts on overflow with all max inputs', async () => {
await expect(fullMath.mulDiv(MaxUint256, MaxUint256, MaxUint256.sub(1))).to.be.reverted
})
it('all max inputs', async () => {
expect(await fullMath.mulDiv(MaxUint256, MaxUint256, MaxUint256)).to.eq(MaxUint256)
})
it('accurate without phantom overflow', async () => {
const result = Q128.div(3)
expect(
await fullMath.mulDiv(
Q128,
/*0.5=*/ BigNumber.from(50).mul(Q128).div(100),
/*1.5=*/ BigNumber.from(150).mul(Q128).div(100)
)
).to.eq(result)
})
it('accurate with phantom overflow', async () => {
const result = BigNumber.from(4375).mul(Q128).div(1000)
expect(await fullMath.mulDiv(Q128, BigNumber.from(35).mul(Q128), BigNumber.from(8).mul(Q128))).to.eq(result)
})
it('accurate with phantom overflow and repeating decimal', async () => {
const result = BigNumber.from(1).mul(Q128).div(3)
expect(await fullMath.mulDiv(Q128, BigNumber.from(1000).mul(Q128), BigNumber.from(3000).mul(Q128))).to.eq(result)
})
})
describe('#mulDivRoundingUp', () => {
it('reverts if denominator is 0', async () => {
await expect(fullMath.mulDivRoundingUp(Q128, 5, 0)).to.be.reverted
})
it('reverts if denominator is 0 and numerator overflows', async () => {
await expect(fullMath.mulDivRoundingUp(Q128, Q128, 0)).to.be.reverted
})
it('reverts if output overflows uint256', async () => {
await expect(fullMath.mulDivRoundingUp(Q128, Q128, 1)).to.be.reverted
})
it('reverts on overflow with all max inputs', async () => {
await expect(fullMath.mulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256.sub(1))).to.be.reverted
})
it('reverts if mulDiv overflows 256 bits after rounding up', async () => {
await expect(
fullMath.mulDivRoundingUp(
'535006138814359',
'432862656469423142931042426214547535783388063929571229938474969',
'2'
)
).to.be.reverted
})
it('reverts if mulDiv overflows 256 bits after rounding up case 2', async () => {
await expect(
fullMath.mulDivRoundingUp(
'115792089237316195423570985008687907853269984659341747863450311749907997002549',
'115792089237316195423570985008687907853269984659341747863450311749907997002550',
'115792089237316195423570985008687907853269984653042931687443039491902864365164'
)
).to.be.reverted
})
it('all max inputs', async () => {
expect(await fullMath.mulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256)).to.eq(MaxUint256)
})
it('accurate without phantom overflow', async () => {
const result = Q128.div(3).add(1)
expect(
await fullMath.mulDivRoundingUp(
Q128,
/*0.5=*/ BigNumber.from(50).mul(Q128).div(100),
/*1.5=*/ BigNumber.from(150).mul(Q128).div(100)
)
).to.eq(result)
})
it('accurate with phantom overflow', async () => {
const result = BigNumber.from(4375).mul(Q128).div(1000)
expect(await fullMath.mulDivRoundingUp(Q128, BigNumber.from(35).mul(Q128), BigNumber.from(8).mul(Q128))).to.eq(
result
)
})
it('accurate with phantom overflow and repeating decimal', async () => {
const result = BigNumber.from(1).mul(Q128).div(3).add(1)
expect(
await fullMath.mulDivRoundingUp(Q128, BigNumber.from(1000).mul(Q128), BigNumber.from(3000).mul(Q128))
).to.eq(result)
})
})
function pseudoRandomBigNumber() {
return BigNumber.from(new Decimal(MaxUint256.toString()).mul(Math.random().toString()).round().toString())
}
// tiny fuzzer. unskip to run
it.skip('check a bunch of random inputs against JS implementation', async () => {
// generates random inputs
const tests = Array(1_000)
.fill(null)
.map(() => {
return {
x: pseudoRandomBigNumber(),
y: pseudoRandomBigNumber(),
d: pseudoRandomBigNumber(),
}
})
.map(({ x, y, d }) => {
return {
input: {
x,
y,
d,
},
floored: fullMath.mulDiv(x, y, d),
ceiled: fullMath.mulDivRoundingUp(x, y, d),
}
})
await Promise.all(
tests.map(async ({ input: { x, y, d }, floored, ceiled }) => {
if (d.eq(0)) {
await expect(floored).to.be.reverted
await expect(ceiled).to.be.reverted
return
}
if (x.eq(0) || y.eq(0)) {
await expect(floored).to.eq(0)
await expect(ceiled).to.eq(0)
} else if (x.mul(y).div(d).gt(MaxUint256)) {
await expect(floored).to.be.reverted
await expect(ceiled).to.be.reverted
} else {
expect(await floored).to.eq(x.mul(y).div(d))
expect(await ceiled).to.eq(
x
.mul(y)
.div(d)
.add(x.mul(y).mod(d).gt(0) ? 1 : 0)
)
}
})
)
})
})