Skip to content

Commit

Permalink
SDK: all quotes, Query modifications (#1716)
Browse files Browse the repository at this point in the history
* Add `modifyDeadline()` for Query

* Add `applySlippage()` for Query

* Add `allBridgeQuotes`

* Add tests for `allBridgeQuotes`

* Sort `allBridgeQuotes` by amountOut

* Simplify `bridgeQuote()`

* Add `applySlippageInBips`

* Expose "apply slippage" funcs

* Add tests for "apply slippage" not modifying the passed object

* Fix: don't modify `query` in test setups
  • Loading branch information
ChiTimesChi authored Dec 27, 2023
1 parent 225e70a commit b5bf2b5
Show file tree
Hide file tree
Showing 6 changed files with 499 additions and 37 deletions.
308 changes: 308 additions & 0 deletions packages/sdk-router/src/module/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
reduceToQuery,
narrowToRouterQuery,
narrowToCCTPRouterQuery,
modifyDeadline,
applySlippage,
applySlippageInBips,
} from './query'

describe('#query', () => {
Expand Down Expand Up @@ -72,4 +75,309 @@ describe('#query', () => {
)
})
})

describe('modifyDeadline', () => {
describe('RouterQuery', () => {
it('modifies the deadline', () => {
const query = modifyDeadline(routerQuery, BigNumber.from(42))
expect(query).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(3),
deadline: BigNumber.from(42),
rawParams: '5',
})
})

it('does not modify the original query', () => {
modifyDeadline(routerQuery, BigNumber.from(42))
expect(routerQuery).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(3),
deadline: BigNumber.from(4),
rawParams: '5',
})
})
})

describe('CCTPRouterQuery', () => {
it('modifies the deadline', () => {
const query = modifyDeadline(cctpRouterQuery, BigNumber.from(42))
expect(query).toEqual({
routerAdapter: '6',
tokenOut: '7',
minAmountOut: BigNumber.from(8),
deadline: BigNumber.from(42),
rawParams: '10',
})
})

it('does not modify the original query', () => {
modifyDeadline(cctpRouterQuery, BigNumber.from(42))
expect(cctpRouterQuery).toEqual({
routerAdapter: '6',
tokenOut: '7',
minAmountOut: BigNumber.from(8),
deadline: BigNumber.from(9),
rawParams: '10',
})
})
})
})

describe('applySlippage', () => {
describe('RouterQuery', () => {
// 1M in 18 decimals
const query: RouterQuery = {
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
}

it('applies 0% slippage', () => {
const newQuery = applySlippage(query, 0, 10000)
expect(newQuery).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('applies 0.5% slippage', () => {
// 50 bips
const newQuery = applySlippage(query, 50, 10000)
expect(newQuery).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(995_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('applies 10% slippage', () => {
const newQuery = applySlippage(query, 10, 100)
expect(newQuery).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(900_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('applies 100% slippage', () => {
const newQuery = applySlippage(query, 1, 1)
expect(newQuery).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(0),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('rounds down', () => {
const queryPlusOne = {
...query,
minAmountOut: query.minAmountOut.add(1),
}
const newQuery = applySlippage(queryPlusOne, 50, 10000)
expect(newQuery).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(995_000).add(1),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('does not modify the original query', () => {
applySlippage(query, 50, 10000)
expect(query).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})
})

describe('CCTPRouterQuery', () => {
// 1M in 6 decimals
const query: CCTPRouterQuery = {
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(6).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
}

it('applies 0% slippage', () => {
const newQuery = applySlippage(query, 0, 10000)
expect(newQuery).toEqual({
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(6).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('applies 0.5% slippage', () => {
// 50 bips
const newQuery = applySlippage(query, 50, 10000)
expect(newQuery).toEqual({
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(6).mul(995_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('applies 10% slippage', () => {
const newQuery = applySlippage(query, 10, 100)
expect(newQuery).toEqual({
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(6).mul(900_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('applies 100% slippage', () => {
const newQuery = applySlippage(query, 1, 1)
expect(newQuery).toEqual({
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(0),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('rounds down', () => {
const queryPlusOne = {
...query,
minAmountOut: query.minAmountOut.add(1),
}
const newQuery = applySlippage(queryPlusOne, 50, 10000)
expect(newQuery).toEqual({
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(6).mul(995_000).add(1),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('does not modify the original query', () => {
applySlippage(query, 50, 10000)
expect(query).toEqual({
routerAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(6).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})
})

describe('errors', () => {
it('throws if slippage denominator is zero', () => {
expect(() => applySlippage(routerQuery, 1, 0)).toThrow(
'Slippage denominator cannot be zero'
)
})

it('throws if slippage numerator is negative', () => {
expect(() => applySlippage(routerQuery, -1, 1)).toThrow(
'Slippage numerator cannot be negative'
)
})

it('throws if slippage numerator is greater than denominator', () => {
expect(() => applySlippage(routerQuery, 2, 1)).toThrow(
'Slippage cannot be greater than 1'
)
})
})
})

describe('applySlippageInBips parity', () => {
// 1M in 18 decimals
const query: RouterQuery = {
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
}

it('applies 0% slippage', () => {
const newQuery = applySlippage(query, 0, 10000)
const newQueryInBips = applySlippageInBips(query, 0)
expect(newQuery).toEqual(newQueryInBips)
})

it('applies 0.5% slippage', () => {
// 50 bips
const newQuery = applySlippage(query, 50, 10000)
const newQueryInBips = applySlippageInBips(query, 50)
expect(newQuery).toEqual(newQueryInBips)
})

it('applies 10% slippage', () => {
const newQuery = applySlippage(query, 10, 100)
const newQueryInBips = applySlippageInBips(query, 1000)
expect(newQuery).toEqual(newQueryInBips)
})

it('applies 100% slippage', () => {
const newQuery = applySlippage(query, 1, 1)
const newQueryInBips = applySlippageInBips(query, 10000)
expect(newQuery).toEqual(newQueryInBips)
})

it('rounds down', () => {
const queryPlusOne = {
...query,
minAmountOut: query.minAmountOut.add(1),
}
const newQuery = applySlippage(queryPlusOne, 50, 10000)
const newQueryInBips = applySlippageInBips(queryPlusOne, 50)
expect(newQuery).toEqual(newQueryInBips)
})

it('does not modify the original query', () => {
applySlippageInBips(query, 50)
expect(query).toEqual({
swapAdapter: '1',
tokenOut: '2',
minAmountOut: BigNumber.from(10).pow(18).mul(1_000_000),
deadline: BigNumber.from(4),
rawParams: '5',
})
})

it('throws if basis points are negative', () => {
expect(() => applySlippageInBips(routerQuery, -1)).toThrow(
'Slippage numerator cannot be negative'
)
})

it('throws if basis points are greater than 10000', () => {
expect(() => applySlippageInBips(routerQuery, 10001)).toThrow(
'Slippage cannot be greater than 1'
)
})
})
})
64 changes: 64 additions & 0 deletions packages/sdk-router/src/module/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,67 @@ export const hasComplexBridgeAction = (destQuery: Query): boolean => {
destQuery.tokenOut !== ETH_NATIVE_TOKEN_ADDRESS
)
}

/**
* Modifies the deadline of the query and returns the modified query.
* Note: the original query is preserved unchanged.
*
* @param query - The query to modify.
* @param deadline - The new deadline.
* @returns The modified query with the new deadline.
*/
export const modifyDeadline = (query: Query, deadline: BigNumber): Query => {
return {
...query,
deadline,
}
}

/**
* Applies the slippage to the query's minAmountOut (rounded down), and returns the modified query
* with the reduced minAmountOut.
* Note: the original query is preserved unchanged.
*
* @param query - The query to modify.
* @param slipNumerator - The numerator of the slippage.
* @param slipDenominator - The denominator of the slippage.
* @returns The modified query with the reduced minAmountOut.
* @throws If the slippage fraction is invalid (<0, >1, or NaN)
*/
export const applySlippage = (
query: Query,
slipNumerator: number,
slipDenominator: number
): Query => {
invariant(slipDenominator > 0, 'Slippage denominator cannot be zero')
invariant(slipNumerator >= 0, 'Slippage numerator cannot be negative')
invariant(
slipNumerator <= slipDenominator,
'Slippage cannot be greater than 1'
)
const slippageAmount = query.minAmountOut
.mul(slipNumerator)
.div(slipDenominator)
return {
...query,
minAmountOut: query.minAmountOut.sub(slippageAmount),
}
}

/**
* Applies the slippage (in basis points) to the query's minAmountOut (rounded down), and returns the modified query
* with the reduced minAmountOut.
* Note: the original query is preserved unchanged.
* Note: the slippage is applied as a fraction of 10000, e.g. 100 bips = 1%.
*
* @param query - The query to modify.
* @param slipBasisPoints - The slippage in basis points.
* @returns The modified query with the reduced minAmountOut.
* @throws If the basis points are invalid (<0, >10000)
*/
export const applySlippageInBips = (
query: Query,
slipBasisPoints: number
): Query => {
return applySlippage(query, slipBasisPoints, 10000)
}
Loading

0 comments on commit b5bf2b5

Please sign in to comment.