Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NFT Descriptor: decimal strings fuzz test #93

Merged
merged 8 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions contracts/libraries/NFTDescriptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,14 @@ library NFTDescriptor {
if (difference > 0 && difference <= 18) {
if (token0Decimals > token1Decimals) {
adjustedSqrtRatioX96 = sqrtRatioX96.mul(10**(difference.div(2)));
if (difference % 2 == 1) {
adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, sqrt10X128, 1 << 128);
}
} else {
adjustedSqrtRatioX96 = sqrtRatioX96.div(10**(difference.div(2)));
}
if (difference % 2 == 1) {
adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, sqrt10X128, 1 << 128);
if (difference % 2 == 1) {
adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, 1 << 128, sqrt10X128);
}
}
} else {
adjustedSqrtRatioX96 = uint256(sqrtRatioX96);
Expand Down
49 changes: 48 additions & 1 deletion test/NFTDescriptor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { Fixture } from 'ethereum-waffle'
import { FeeAmount, TICK_SPACINGS } from './shared/constants'
import snapshotGasCost from './shared/snapshotGasCost'
import { base64Encode } from './shared/base64encode'
import { formatSqrtRatioX96 } from './shared/formatSqrtRatioX96'
import { getMaxTick, getMinTick } from './shared/ticks'
import Decimal from 'decimal.js'
import { randomBytes } from 'crypto'

const TEN = BigNumber.from(10)
const LOWEST_SQRT_RATIO = 4310618292
const HIGHEST_SQRT_RATIO = BigNumber.from(33849).mul(TEN.pow(34))

describe('NFTDescriptor', () => {
const wallets = waffle.provider.getWallets()
Expand Down Expand Up @@ -374,7 +381,7 @@ describe('NFTDescriptor', () => {
})

it('returns the correct string when the decimal difference is odd', async () => {
expect(await nftDescriptor.fixedPointToDecimalString(ratio, 7, 18)).to.eq('0.0000000010000')
expect(await nftDescriptor.fixedPointToDecimalString(ratio, 7, 18)).to.eq('0.000000000010000')
moodysalem marked this conversation as resolved.
Show resolved Hide resolved
})

// TODO: provide compatibility token prices that breach minimum price due to token decimal differences
Expand All @@ -389,6 +396,46 @@ describe('NFTDescriptor', () => {
expect(await nftDescriptor.fixedPointToDecimalString(ratio, 24, 5)).to.eq('1.0000')
})
})

it('some fuzz', async () => {
const random3To21 = () => {
return Math.floor(3 + ((Math.random() * 100) % 18))
}
const random1To20 = () => {
moodysalem marked this conversation as resolved.
Show resolved Hide resolved
return Math.floor(1 + ((Math.random() * 100) % 19))
}

const inputs = []
let i = 0
while (i <= 10) {
const value = randomBytes(random1To20())
const decimals0 = random3To21()
const decimals1 = random3To21()
const decimalDiff = Math.abs(decimals0 - decimals1)

// TODO: Address edgecase out of bounds prices due to decimal differences
if (
BigNumber.from(`0x${value.toString('hex')}`)
.div(TEN.pow(decimalDiff))
.gt(LOWEST_SQRT_RATIO) &&
BigNumber.from(`0x${value.toString('hex')}`)
.mul(TEN.pow(decimalDiff))
.lt(HIGHEST_SQRT_RATIO)
) {
inputs.push([BigNumber.from(`0x${value.toString('hex')}`), decimals0, decimals1])
i++
}
}

for (let i in inputs) {
let input: any
moodysalem marked this conversation as resolved.
Show resolved Hide resolved
let decimals0: any
let decimals1: any
;[input, decimals0, decimals1] = inputs[i]
let result = await nftDescriptor.fixedPointToDecimalString(input, decimals0, decimals1)
expect(formatSqrtRatioX96(input, decimals0, decimals1)).to.eq(result)
}
}).timeout(300_000)
})
})

Expand Down
2 changes: 1 addition & 1 deletion test/shared/formatSqrtRatioX96.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { encodePriceSqrt } from './encodePriceSqrt'
import { expect } from './expect'
import formatSqrtRatioX96 from './formatSqrtRatioX96'
import { formatSqrtRatioX96 } from './formatSqrtRatioX96'

describe('#formatSqrtRatioX96', () => {
it('is correct for 9_999_999/10_000_000', () => {
Expand Down
24 changes: 18 additions & 6 deletions test/shared/formatSqrtRatioX96.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import { BigNumber } from 'ethers'
import Decimal from 'decimal.js'

const TWO = BigNumber.from(2)
const TEN = BigNumber.from(10)
const FIVE_SIG_FIGS_POW = new Decimal(10).pow(5)

export default function formatSqrtRatioX96(
export function formatSqrtRatioX96(
sqrtRatioX96: BigNumber,
decimalsToken0: number = 18,
decimalsToken1: number = 18
): string {
Decimal.set({ precision: 5, toExpPos: 9_999_999, toExpNeg: -9_999_999, rounding: Decimal.ROUND_HALF_CEIL })
Decimal.set({ precision: 5, toExpPos: 9_999_999, toExpNeg: -9_999_999, rounding: Decimal.ROUND_HALF_EVEN })

const decRatio = new Decimal(sqrtRatioX96.toString())

let ratio = decRatio.div(new Decimal(2).pow(96)).pow(2)
let ratio
let ratioNum
let ratioBN = sqrtRatioX96.mul(sqrtRatioX96).div(TWO.pow(64))
moodysalem marked this conversation as resolved.
Show resolved Hide resolved
if (sqrtRatioX96.lt(TWO.pow(96))) {
// accurate calculation in BigNumber
ratioBN = ratioBN.mul(TEN.pow(44)).div(TWO.pow(128))
// accurate precision api with vanilla js numbers
ratioNum = parseInt(ratioBN.toString()).toPrecision(5)
// then turn into decimal
ratio = new Decimal(ratioNum.toString()).div(new Decimal(10).pow(44))
} else {
ratioBN = ratioBN.mul(TEN.pow(5)).div(TWO.pow(128))
ratioNum = parseInt(ratioBN.toString()).toPrecision(5)
ratio = new Decimal(ratioNum.toString()).div(new Decimal(10).pow(5))
}

// adjust for decimals
if (decimalsToken1 < decimalsToken0) {
ratio = ratio.mul(TEN.pow(decimalsToken0 - decimalsToken1).toString())
} else if (decimalsToken0 < decimalsToken1) {
Expand Down