Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
swap-v2-sdk
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
LuckySwap
swap-v2-sdk
Commits
3249027e
Unverified
Commit
3249027e
authored
Jun 03, 2019
by
Noah Zinsmeister
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
address edge cases around decimals and rounding
parent
a56aa056
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
120 additions
and
68 deletions
+120
-68
computation.ts
src/__tests__/computation.ts
+8
-23
computation.ts
src/computation.ts
+44
-11
index.ts
src/constants/index.ts
+1
-2
data.ts
src/data.ts
+3
-3
format.ts
src/format.ts
+56
-27
index.ts
src/index.ts
+6
-2
index.ts
src/types/index.ts
+2
-0
No files found.
src/__tests__/computation.ts
View file @
3249027e
...
...
@@ -36,18 +36,13 @@ function testMarketRates(
})
describe
(
'
manually inverted
'
,
():
void
=>
{
const
tradeTypeInverted
=
tradeType
===
TRADE_TYPE
.
TOKEN_TO_TOKEN
?
TRADE_TYPE
.
TOKEN_TO_TOKEN
:
tradeType
===
TRADE_TYPE
.
ETH_TO_TOKEN
?
TRADE_TYPE
.
TOKEN_TO_ETH
:
TRADE_TYPE
.
ETH_TO_TOKEN
const
tradeTypeInverted
=
tradeType
===
TRADE_TYPE
.
ETH_TO_TOKEN
?
TRADE_TYPE
.
TOKEN_TO_ETH
:
TRADE_TYPE
.
ETH_TO_TOKEN
test
(
'
not inverted
'
,
():
void
=>
{
const
manuallyInvertedMarketRate
:
BigNumber
=
getMarketRate
(
outputTokenReserves
,
inputTokenReserves
,
tradeTypeInverted
,
tradeType
===
TRADE_TYPE
.
TOKEN_TO_TOKEN
?
TRADE_TYPE
.
TOKEN_TO_TOKEN
:
tradeType
Inverted
,
false
)
...
...
@@ -58,7 +53,7 @@ function testMarketRates(
const
manuallyInvertedInvertedMarketRate
:
BigNumber
=
getMarketRate
(
outputTokenReserves
,
inputTokenReserves
,
tradeTypeInverted
,
tradeType
===
TRADE_TYPE
.
TOKEN_TO_TOKEN
?
TRADE_TYPE
.
TOKEN_TO_TOKEN
:
tradeType
Inverted
,
true
)
...
...
@@ -74,11 +69,10 @@ describe('getMarketRate', (): void => {
'
4039700561005906883487
'
,
'
1094055210563660633471343
'
)
const
expectedMarketRate
=
'
0.003692410147130181
'
const
expectedMarketRateInverted
=
'
270.825818409480102284
'
const
expectedMarketRate
=
'
270.825818409480102284
'
const
expectedMarketRateInverted
=
'
0.003692410147130181
'
testMarketRates
(
null
,
tokenReserves
,
TRADE_TYPE
.
ETH_TO_TOKEN
,
expectedMarketRate
,
expectedMarketRateInverted
)
testMarketRates
(
tokenReserves
,
null
,
TRADE_TYPE
.
TOKEN_TO_ETH
,
expectedMarketRateInverted
,
expectedMarketRate
)
})
describe
(
'
dummy ETH/USDC and USDC/ETH
'
,
():
void
=>
{
...
...
@@ -86,8 +80,7 @@ describe('getMarketRate', (): void => {
const
expectedMarketRate
=
'
0.003678674143683891
'
const
expectedMarketRateInverted
=
'
271.837069808684359442
'
testMarketRates
(
null
,
tokenReserves
,
TRADE_TYPE
.
ETH_TO_TOKEN
,
expectedMarketRate
,
expectedMarketRateInverted
)
testMarketRates
(
tokenReserves
,
null
,
TRADE_TYPE
.
TOKEN_TO_ETH
,
expectedMarketRateInverted
,
expectedMarketRate
)
testMarketRates
(
tokenReserves
,
null
,
TRADE_TYPE
.
TOKEN_TO_ETH
,
expectedMarketRate
,
expectedMarketRateInverted
)
})
describe
(
'
dummy DAI/USDC and USDC/DAI
'
,
():
void
=>
{
...
...
@@ -97,8 +90,8 @@ describe('getMarketRate', (): void => {
'
1094055210563660633471343
'
)
const
USDCTokenReserves
:
TokenReserves
=
constructTokenReserves
(
6
,
'
1076592291503763426634
'
,
'
292657693901
'
)
const
expectedMarketRate
=
'
0.996279935624983178
'
const
expectedMarketRateInverted
=
'
1.003733954927721499
'
const
expectedMarketRate
=
'
1.003733954927721392
'
const
expectedMarketRateInverted
=
'
0.996279935624983143
'
testMarketRates
(
DAITokenReserves
,
...
...
@@ -107,13 +100,5 @@ describe('getMarketRate', (): void => {
expectedMarketRate
,
expectedMarketRateInverted
)
testMarketRates
(
USDCTokenReserves
,
DAITokenReserves
,
TRADE_TYPE
.
TOKEN_TO_TOKEN
,
expectedMarketRateInverted
,
expectedMarketRate
)
})
})
src/computation.ts
View file @
3249027e
...
...
@@ -37,14 +37,19 @@ export function getOutputPrice(outputAmount: BigNumber, inputReserve: BigNumber,
return
inputAmount
}
function
_getMarketRate
(
tokenReserves
:
TokenReservesOptional
,
tradeType
:
TRADE_TYPE
,
invert
:
boolean
):
BigNumber
{
// returns [numerator, decimal scalar, denominator]
function
getRawMarketRate
(
tokenReserves
:
TokenReservesOptional
,
tradeType
:
TRADE_TYPE
,
invert
:
boolean
):
[
BigNumber
,
BigNumber
,
BigNumber
]
{
if
(
tokenReserves
===
null
)
{
throw
Error
(
'
outputTokenReserves must be non-null.
'
)
}
else
{
const
numerator
:
TokenAmount
=
tradeType
===
TRADE_TYPE
.
ETH_TO_TOKEN
?
tokenReserves
.
ethReserve
:
tokenReserves
.
tokenReserve
const
denominator
:
TokenAmount
=
tradeType
===
TRADE_TYPE
.
ETH_TO_TOKEN
?
tokenReserves
.
tokenReserve
:
tokenReserves
.
ethReserve
const
denominator
:
TokenAmount
=
tradeType
===
TRADE_TYPE
.
ETH_TO_TOKEN
?
tokenReserves
.
ethReserve
:
tokenReserves
.
tokenReserve
const
numeratorAmount
:
BigNumber
=
normalizeBigNumberish
(
numerator
.
amount
)
const
denominatorAmount
:
BigNumber
=
normalizeBigNumberish
(
denominator
.
amount
)
...
...
@@ -55,15 +60,29 @@ function _getMarketRate(tokenReserves: TokenReservesOptional, tradeType: TRADE_T
ensureAllUInt8
([
numeratorDecimals
,
denominatorDecimals
])
if
(
!
invert
)
{
const
decimalScalar
=
_10
.
exponentiatedBy
(
denominatorDecimals
-
numeratorDecimals
)
return
numeratorAmount
.
multipliedBy
(
decimalScalar
).
div
(
denominatorAmount
)
const
decimalScalar
:
BigNumber
=
_10
.
exponentiatedBy
(
denominatorDecimals
-
numeratorDecimals
)
return
[
numeratorAmount
,
decimalScalar
,
denominatorAmount
]
}
else
{
const
decimalScalar
=
_10
.
exponentiatedBy
(
numeratorDecimals
-
denominatorDecimals
)
return
denominatorAmount
.
multipliedBy
(
decimalScalar
).
div
(
numeratorAmount
)
const
decimalScalar
:
BigNumber
=
_10
.
exponentiatedBy
(
numeratorDecimals
-
denominatorDecimals
)
return
[
denominatorAmount
,
decimalScalar
,
numeratorAmount
]
}
}
}
function
getRawMarketRateOneSided
(
tokenReserves
:
TokenReservesOptional
,
tradeType
:
TRADE_TYPE
,
invert
:
boolean
):
BigNumber
{
const
[
numerator
,
decimalScalar
,
denominator
]:
[
BigNumber
,
BigNumber
,
BigNumber
]
=
getRawMarketRate
(
tokenReserves
,
tradeType
,
invert
)
return
numerator
.
multipliedBy
(
decimalScalar
).
dividedBy
(
denominator
)
}
// rounds output rates to 18 decimal places
export
function
getMarketRate
(
inputTokenReserves
:
TokenReservesOptional
,
outputTokenReserves
:
TokenReservesOptional
,
...
...
@@ -74,12 +93,26 @@ export function getMarketRate(
if
(
inputTokenReserves
===
null
||
outputTokenReserves
===
null
)
{
throw
Error
(
'
Both inputTokenReserves and outputTokenReserves must be non-null.
'
)
}
else
{
const
inputMarketRate
:
BigNumber
=
_getMarketRate
(
inputTokenReserves
,
TRADE_TYPE
.
TOKEN_TO_ETH
,
invert
)
const
outputMarketRate
:
BigNumber
=
_getMarketRate
(
outputTokenReserves
,
TRADE_TYPE
.
ETH_TO_TOKEN
,
invert
)
return
inputMarketRate
.
multipliedBy
(
outputMarketRate
)
const
[
inputNumerator
,
inputDecimalScalar
,
inputDenominator
]:
[
BigNumber
,
BigNumber
,
BigNumber
]
=
getRawMarketRate
(
inputTokenReserves
,
TRADE_TYPE
.
TOKEN_TO_ETH
,
invert
)
const
[
outputNumerator
,
outputDecimalScalar
,
outputDenominator
]:
[
BigNumber
,
BigNumber
,
BigNumber
]
=
getRawMarketRate
(
outputTokenReserves
,
TRADE_TYPE
.
ETH_TO_TOKEN
,
invert
)
return
inputNumerator
.
multipliedBy
(
inputDecimalScalar
)
.
multipliedBy
(
outputNumerator
)
.
multipliedBy
(
outputDecimalScalar
)
.
dividedBy
(
inputDenominator
.
multipliedBy
(
outputDenominator
))
}
}
else
{
return
_getMarketRate
(
return
getRawMarketRateOneSided
(
tradeType
===
TRADE_TYPE
.
ETH_TO_TOKEN
?
outputTokenReserves
:
inputTokenReserves
,
tradeType
,
invert
...
...
src/constants/index.ts
View file @
3249027e
...
...
@@ -34,8 +34,7 @@ export enum TRADE_EXACT {
export
enum
FIXED_UNDERFLOW_BEHAVIOR
{
ZERO
=
'
ZERO
'
,
LESS_THAN
=
'
LESS_THAN
'
,
ONE_DIGIT
=
'
ONE_DIGIT
'
,
MAX_DECIMAL_PLACES
=
'
MAX_DECIMAL_PLACES
'
ONE_DIGIT
=
'
ONE_DIGIT
'
}
//// constants for internal use
...
...
src/data.ts
View file @
3249027e
...
...
@@ -2,7 +2,7 @@ import { ethers } from 'ethers'
import
{
ETH
,
CHAIN_ID_NAME
,
ERC20_ABI
,
FACTORY_ADDRESS
,
FACTORY_ABI
}
from
'
./constants
'
import
{
ChainIdOrProvider
,
ChainIdAndProvider
,
Token
,
TokenReserves
}
from
'
./types
'
import
{
normalizeAddress
}
from
'
./utils
'
import
{
normalizeAddress
,
normalizeBigNumberish
}
from
'
./utils
'
// abstraction to get contracts
function
_getContract
(
address
:
string
,
ABI
:
string
,
provider
:
ethers
.
providers
.
BaseProvider
):
ethers
.
Contract
{
...
...
@@ -80,8 +80,8 @@ export async function getTokenReserves(
ethers
.
utils
.
BigNumber
]
=
await
Promise
.
all
([
ethTokenPromise
,
tokenPromise
,
exchangeTokenPromise
,
ethBalancePromise
,
tokenBalancePromise
])
const
ethReserve
=
{
token
:
ethToken
,
amount
:
ethBalance
}
const
tokenReserve
=
{
token
,
amount
:
tokenBalance
}
const
ethReserve
=
{
token
:
ethToken
,
amount
:
normalizeBigNumberish
(
ethBalance
)
}
const
tokenReserve
=
{
token
,
amount
:
normalizeBigNumberish
(
tokenBalance
)
}
return
{
token
,
exchange
:
exchangeToken
,
ethReserve
,
tokenReserve
}
}
src/format.ts
View file @
3249027e
import
BigNumber
from
'
bignumber.js
'
import
{
MAX_DECIMAL_PLACES
,
ROUNDING_MODE
,
FIXED_UNDERFLOW_BEHAVIOR
,
_0
}
from
'
./constants
'
import
{
MAX_DECIMAL_PLACES
,
ROUNDING_MODE
,
FIXED_UNDERFLOW_BEHAVIOR
,
_0
,
_10
}
from
'
./constants
'
import
{
BigNumberish
,
FlexibleFormat
,
FormatSignificantOptions
,
FormatFixedOptions
}
from
'
./types
'
import
{
normalizeBigNumberish
,
ensureBoundedInteger
}
from
'
./utils
'
import
{
normalizeBigNumberish
,
ensureBoundedInteger
,
ensureAllUInt256
,
ensureAllUInt8
}
from
'
./utils
'
function
_format
(
bigNumber
:
BigNumber
,
format
:
FlexibleFormat
,
decimalPlaces
:
number
):
string
{
function
_format
(
bigNumber
:
BigNumber
,
decimalPlaces
:
number
,
roundingMode
:
BigNumber
.
RoundingMode
=
ROUNDING_MODE
,
format
:
FlexibleFormat
):
string
{
return
typeof
format
===
'
boolean
'
&&
format
===
false
?
bigNumber
.
toFixed
(
decimalPlaces
)
?
bigNumber
.
toFixed
(
decimalPlaces
,
roundingMode
)
:
bigNumber
.
toFormat
(
decimalPlaces
,
ROUNDING_MODE
,
roundingMode
,
typeof
format
===
'
boolean
'
&&
format
===
true
?
undefined
:
format
)
}
// bignumberish is converted to significantDigits, then cast back as a bignumber and formatted, dropping trailing 0s
export
function
formatSignificant
(
bigNumberish
:
BigNumberish
,
{
significantDigits
=
6
,
forceIntegerSignificance
=
false
,
format
=
false
}:
FormatSignificantOptions
):
string
{
export
function
formatSignificant
(
bigNumberish
:
BigNumberish
,
options
?:
FormatSignificantOptions
):
string
{
const
{
significantDigits
=
6
,
forceIntegerSignificance
=
true
,
roundingMode
=
ROUNDING_MODE
,
format
=
false
}
=
options
||
{}
const
bigNumber
:
BigNumber
=
normalizeBigNumberish
(
bigNumberish
)
ensureBoundedInteger
(
significantDigits
,
[
1
,
MAX_DECIMAL_PLACES
])
...
...
@@ -27,38 +32,33 @@ export function formatSignificant(
bigNumber
.
toPrecision
(
Math
.
max
(
minimumSignificantDigits
,
significantDigits
))
)
return
_format
(
preciseBigNumber
,
format
,
preciseBigNumber
.
decimalPlaces
()
)
return
_format
(
preciseBigNumber
,
preciseBigNumber
.
decimalPlaces
(),
roundingMode
,
format
)
}
export
function
formatFixed
(
bigNumberish
:
BigNumberish
,
{
export
function
formatFixed
(
bigNumberish
:
BigNumberish
,
options
?:
FormatFixedOptions
):
string
{
const
{
decimalPlaces
=
4
,
roundingMode
=
ROUNDING_MODE
,
dropTrailingZeros
=
true
,
format
=
false
,
underflowBehavior
=
FIXED_UNDERFLOW_BEHAVIOR
.
ONE_DIGIT
}
:
FormatFixedOptions
):
string
{
underflowBehavior
=
FIXED_UNDERFLOW_BEHAVIOR
.
ONE_DIGIT
,
format
=
false
}
=
options
||
{}
const
bigNumber
:
BigNumber
=
normalizeBigNumberish
(
bigNumberish
)
ensureBoundedInteger
(
decimalPlaces
,
MAX_DECIMAL_PLACES
)
// this works because we've specified the rounding mode
const
minimumNonZeroValue
:
BigNumber
=
new
BigNumber
(
decimalPlaces
===
0
?
'
0.5
'
:
`0.
${
'
0
'
.
repeat
(
decimalPlaces
)}
5`
)
if
(
bigNumber
.
isLessThan
(
minimumNonZeroValue
))
{
switch
(
underflowBehavior
)
{
case
FIXED_UNDERFLOW_BEHAVIOR
.
ZERO
:
{
return
_format
(
_0
,
format
,
dropTrailingZeros
?
0
:
decimalPlaces
)
return
_format
(
_0
,
dropTrailingZeros
?
0
:
decimalPlaces
,
undefined
,
format
)
}
case
FIXED_UNDERFLOW_BEHAVIOR
.
LESS_THAN
:
{
return
`<
${
_format
(
minimumNonZeroValue
,
format
,
minimumNonZeroValue
.
decimalPlaces
()
)}
`
return
`<
${
_format
(
minimumNonZeroValue
,
minimumNonZeroValue
.
decimalPlaces
(),
undefined
,
format
)}
`
}
case
FIXED_UNDERFLOW_BEHAVIOR
.
ONE_DIGIT
:
{
const
newBigNumber
=
new
BigNumber
(
bigNumber
.
toPrecision
(
1
))
return
_format
(
newBigNumber
,
format
,
newBigNumber
.
decimalPlaces
())
}
case
FIXED_UNDERFLOW_BEHAVIOR
.
MAX_DECIMAL_PLACES
:
{
const
newBigNumber
=
new
BigNumber
(
bigNumber
.
toFixed
(
MAX_DECIMAL_PLACES
))
return
_format
(
newBigNumber
,
format
,
dropTrailingZeros
?
newBigNumber
.
decimalPlaces
()
:
MAX_DECIMAL_PLACES
)
return
_format
(
newBigNumber
,
newBigNumber
.
decimalPlaces
(),
undefined
,
format
)
}
default
:
{
throw
Error
(
`Passed FIXED_UNDERFLOW_BEHAVIOR
${
underflowBehavior
}
is not valid.`
)
...
...
@@ -66,9 +66,38 @@ export function formatFixed(
}
}
else
{
const
newDecimalPlaces
=
dropTrailingZeros
?
new
BigNumber
(
bigNumber
.
toFixed
(
decimalPlaces
)).
decimalPlaces
()
?
new
BigNumber
(
bigNumber
.
toFixed
(
decimalPlaces
,
roundingMode
)).
decimalPlaces
()
:
decimalPlaces
return
_format
(
bigNumber
,
format
,
newDecimalPlaces
)
return
_format
(
bigNumber
,
newDecimalPlaces
,
roundingMode
,
format
)
}
}
function
decimalize
(
bigNumberish
:
BigNumberish
,
decimals
:
number
):
BigNumber
{
const
bigNumber
:
BigNumber
=
normalizeBigNumberish
(
bigNumberish
)
ensureAllUInt256
([
bigNumber
])
ensureAllUInt8
([
decimals
])
if
(
decimals
>
MAX_DECIMAL_PLACES
)
{
throw
Error
(
`This function does not support decimals greater than
${
MAX_DECIMAL_PLACES
}
.`
)
}
return
bigNumber
.
dividedBy
(
_10
.
exponentiatedBy
(
decimals
))
}
export
function
formatSignificantDecimals
(
bigNumberish
:
BigNumberish
,
decimals
:
number
,
options
?:
FormatSignificantOptions
):
string
{
return
formatSignificant
(
decimalize
(
bigNumberish
,
decimals
),
options
)
}
export
function
formatFixedDecimals
(
bigNumberish
:
BigNumberish
,
decimals
:
number
,
options
?:
FormatFixedOptions
):
string
{
return
formatFixed
(
decimalize
(
bigNumberish
,
decimals
),
options
)
}
src/index.ts
View file @
3249027e
export
*
from
'
./data
'
export
*
from
'
./computation
'
import
BigNumber
from
'
bignumber.js
'
export
{
BigNumber
}
export
{
ETH
,
...
...
@@ -9,3 +9,7 @@ export {
TRADE_EXACT
,
FIXED_UNDERFLOW_BEHAVIOR
}
from
'
./constants
'
export
*
from
'
./data
'
export
*
from
'
./computation
'
export
*
from
'
./format
'
src/types/index.ts
View file @
3249027e
...
...
@@ -46,11 +46,13 @@ export type FlexibleFormat = boolean | BigNumber.Format
export
interface
FormatSignificantOptions
{
significantDigits
:
number
roundingMode
:
BigNumber
.
RoundingMode
forceIntegerSignificance
:
boolean
format
:
FlexibleFormat
}
export
interface
FormatFixedOptions
{
decimalPlaces
:
number
roundingMode
:
BigNumber
.
RoundingMode
dropTrailingZeros
:
boolean
underflowBehavior
:
FIXED_UNDERFLOW_BEHAVIOR
format
:
FlexibleFormat
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment