Skip to content

Commit

Permalink
Sdk: include rfq protocol fees (#1871)
Browse files Browse the repository at this point in the history
* Add unit tests for disabled/enabled protocol fees

* Take into account the protocol fees on origin chain

* Update tests: origin queries should not be affected by protocol fees

* Fix: apply protocol fee without changing origin queries
  • Loading branch information
ChiTimesChi authored Jan 19, 2024
1 parent 3e173bd commit 8245ea5
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 2 deletions.
104 changes: 104 additions & 0 deletions packages/sdk-router/src/rfq/fastBridgeRouter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jest.mock('@ethersproject/contracts', () => {
interface: args[1],
bridgeRelays: jest.fn(),
fastBridge: jest.fn(),
getOriginAmountOut: jest.fn(),
protocolFeeRate: jest.fn(),
populateTransaction: {
bridge: actualInstance.populateTransaction.bridge,
},
Expand Down Expand Up @@ -223,4 +225,106 @@ describe('FastBridgeRouter', () => {
createBridgeTest(fastBridgeRouter, bridgeParams, originQuery, destQuery)
})
})

describe('getOriginAmountOut', () => {
const mockQueryFragment = {
routerAdapter: '1',
deadline: BigNumber.from(2),
rawParams: '3',
}

const mockTokenIn = '0xA'
const mockRfqTokens = ['0xA', '0xB']

beforeAll(() => {
jest
.spyOn(fastBridgeRouter['routerContract'], 'getOriginAmountOut')
.mockImplementation((token, rfqTokens, amountIn) =>
Promise.resolve(
token === mockTokenIn
? rfqTokens.map((rfqToken, index) => {
const query = {
...mockQueryFragment,
tokenOut: rfqToken,
minAmountOut: BigNumber.from(amountIn).mul(index + 1),
}
const tuple: [string, string, BigNumber, BigNumber, string] =
[
query.routerAdapter,
query.tokenOut,
query.minAmountOut,
query.deadline,
query.rawParams,
]
return Object.assign(tuple, {
routerAdapter: query.routerAdapter,
tokenOut: query.tokenOut,
minAmountOut: query.minAmountOut,
deadline: query.deadline,
rawParams: query.rawParams,
})
})
: []
)
)
})

it('Returns correct values with protocol fee = 0 bps', async () => {
jest
.spyOn(fastBridgeRouter['fastBridgeContractCache']!, 'protocolFeeRate')
.mockImplementation(() => Promise.resolve(BigNumber.from(0)))
const result = await fastBridgeRouter.getOriginAmountOut(
mockTokenIn,
mockRfqTokens,
1_000_001
)
expect(result).toEqual([
{
tokenOut: '0xA',
minAmountOut: BigNumber.from(1_000_001),
...mockQueryFragment,
},
{
tokenOut: '0xB',
minAmountOut: BigNumber.from(2_000_002),
...mockQueryFragment,
},
])
})

it('Returns correct values with protocol fee = 10 bps', async () => {
// protocolFeeRate uses 10**6 as the denominator
jest
.spyOn(fastBridgeRouter['fastBridgeContractCache']!, 'protocolFeeRate')
.mockImplementation(() => Promise.resolve(BigNumber.from(1000)))
const result = await fastBridgeRouter.getOriginAmountOut(
mockTokenIn,
mockRfqTokens,
1_000_001
)
// Protocol fees should have no effect on the result
expect(result).toEqual([
{
tokenOut: '0xA',
minAmountOut: BigNumber.from(1_000_001),
...mockQueryFragment,
},
{
tokenOut: '0xB',
minAmountOut: BigNumber.from(2_000_002),
...mockQueryFragment,
},
])
})
})

describe('getProtocolFeeRate', () => {
it('Returns correct value', async () => {
jest
.spyOn(fastBridgeRouter['fastBridgeContractCache']!, 'protocolFeeRate')
.mockImplementation(() => Promise.resolve(BigNumber.from(1000)))
const result = await fastBridgeRouter.getProtocolFeeRate()
expect(result).toEqual(BigNumber.from(1000))
})
})
})
8 changes: 8 additions & 0 deletions packages/sdk-router/src/rfq/fastBridgeRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,12 @@ export class FastBridgeRouter implements SynapseModule {
)
return queries.map(reduceToQuery)
}

/**
* @returns The protocol fee rate, multiplied by 1_000_000 (e.g. 1 basis point = 100).
*/
public async getProtocolFeeRate(): Promise<BigNumber> {
const fastBridgeContract = await this.getFastBridgeContract()
return fastBridgeContract.protocolFeeRate()
}
}
16 changes: 16 additions & 0 deletions packages/sdk-router/src/rfq/fastBridgeRouterSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,20 @@ describe('FastBridgeRouterSet', () => {
)
})
})

describe('applyProtocolFeeRate', () => {
const amount = BigNumber.from(1_000_001)

it('Applies 0 bps fee rate', () => {
const protocolFeeRate = BigNumber.from(0)
const result = routerSet.applyProtocolFeeRate(amount, protocolFeeRate)
expect(result).toEqual(amount)
})

it('Applies 10 bps fee rate', () => {
const protocolFeeRate = BigNumber.from(1_000)
const result = routerSet.applyProtocolFeeRate(amount, protocolFeeRate)
expect(result).toEqual(BigNumber.from(999_001))
})
})
})
23 changes: 21 additions & 2 deletions packages/sdk-router/src/rfq/fastBridgeRouterSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,19 @@ export class FastBridgeRouterSet extends SynapseModuleSet {
amountIn,
allQuotes
)
const protocolFeeRate = await this.getFastBridgeRouter(
originChainId
).getProtocolFeeRate()
return filteredQuotes
.map(({ quote, originQuery }) => ({
quote,
originQuery,
// Apply quote to the proceeds of the origin swap
// Apply quote to the proceeds of the origin swap with protocol fee applied
// TODO: handle optional gas airdrop pricing
destAmountOut: applyQuote(quote, originQuery.minAmountOut),
destAmountOut: applyQuote(
quote,
this.applyProtocolFeeRate(originQuery.minAmountOut, protocolFeeRate)
),
}))
.filter(({ destAmountOut }) => destAmountOut.gt(0))
.map(({ quote, originQuery, destAmountOut }) => ({
Expand Down Expand Up @@ -228,6 +234,19 @@ export class FastBridgeRouterSet extends SynapseModuleSet {
return fastBridgeContract.address
}

/**
* Applies the protocol fee to the amount.
*
* @returns The amount after the fee.
*/
public applyProtocolFeeRate(
amount: BigNumber,
protocolFeeRate: BigNumber
): BigNumber {
const protocolFee = amount.mul(protocolFeeRate).div(1_000_000)
return amount.sub(protocolFee)
}

/**
* Filters the list of quotes to only include those that can be used for given amount of input token.
* For every filtered quote, the origin query is returned with the information for tokenIn -> RFQ token swaps.
Expand Down

0 comments on commit 8245ea5

Please sign in to comment.