Skip to content

Commit

Permalink
Feat: [TP-1627] Add support for swapping ERC20s into native tokens (#…
Browse files Browse the repository at this point in the history
…1078)

Co-authored-by: Pano Skylakis <[email protected]>
  • Loading branch information
pano-skylakis and Pano Skylakis authored Oct 27, 2023
1 parent b492fff commit acf8365
Show file tree
Hide file tree
Showing 7 changed files with 847 additions and 520 deletions.
1,022 changes: 581 additions & 441 deletions packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
WIMX_TEST_TOKEN,
expectToBeString,
refundETHFunctionSignature,
NATIVE_TEST_TOKEN,
} from './test/utils';

jest.mock('@ethersproject/providers');
Expand Down Expand Up @@ -366,6 +367,136 @@ describe('getUnsignedSwapTxFromAmountOut', () => {
expect(decodedRefundEthTx.length).toEqual(0); // expect that the refundETH call has no parameters
});
});

describe('when the output token is native', () => {
it('should not include any amount as the value of the transaction', async () => {
mockRouterImplementation({
pools: [createPool(nativeTokenService.wrappedToken, FUN_TEST_TOKEN)],
});

const exchange = new Exchange(TEST_DEX_CONFIGURATION);

const { swap } = await exchange.getUnsignedSwapTxFromAmountOut(
TEST_FROM_ADDRESS,
FUN_TEST_TOKEN.address,
'native',
newAmountFromString('100', NATIVE_TEST_TOKEN).value,
);

expectToBeDefined(swap.transaction.data);
expectToBeDefined(swap.transaction.value);
const data = swap.transaction.data.toString();

const { swapParams } = decodeMulticallExactOutputSingleWithoutFees(data);
expectInstanceOf(BigNumber, swapParams.amountOut);

expect(swapParams.tokenIn).toBe(FUN_TEST_TOKEN.address); // should be the token-in
expect(swapParams.tokenOut).toBe(WIMX_TEST_TOKEN.address); // should be the wrapped native token
expect(swap.transaction.value).toBe('0x00'); // should not have a value
});

it('should include a call to unwrapWETH9 as the final method call of the calldata', async () => {
mockRouterImplementation({
pools: [createPool(nativeTokenService.wrappedToken, FUN_TEST_TOKEN)],
});

const swapRouterInterface = SwapRouter.INTERFACE;
const paymentsInterface = PaymentsExtended.INTERFACE;
const exchange = new Exchange(TEST_DEX_CONFIGURATION);

// Buy 100 native tokens for X amount of FUN where the exchange rate is 1 token-in : 10 token-out
const { swap } = await exchange.getUnsignedSwapTxFromAmountOut(
TEST_FROM_ADDRESS,
FUN_TEST_TOKEN.address,
'native',
newAmountFromString('100', NATIVE_TEST_TOKEN).value,
3, // 3 % slippage
);

expectToBeDefined(swap.transaction.data);
expectToBeDefined(swap.transaction.value);
const calldata = swap.transaction.data.toString();

const topLevelParams = swapRouterInterface.decodeFunctionData('multicall(uint256,bytes[])', calldata);

expect(topLevelParams.data.length).toBe(2); // expect that there are two calls in the multicall
const swapFunctionCalldata = topLevelParams.data[0];
const unwrapWETHFunctionCalldata = topLevelParams.data[1];

expectToBeString(swapFunctionCalldata);
expectToBeString(unwrapWETHFunctionCalldata);

// Get the first 4 bytes of the swap and unwrap function calldata to get the function selector
const swapFunctionFragment = swapRouterInterface.getFunction(swapFunctionCalldata.slice(0, 10));
const unwrapFunctionFragment = paymentsInterface.getFunction(unwrapWETHFunctionCalldata.slice(0, 10));

expect(swapFunctionFragment.name).toEqual('exactOutputSingle');
expect(unwrapFunctionFragment.name).toEqual('unwrapWETH9');
});

it('should specify the Router contract as the recipient of the swap function call', async () => {
mockRouterImplementation({
pools: [createPool(nativeTokenService.wrappedToken, FUN_TEST_TOKEN)],
});

const exchange = new Exchange(TEST_DEX_CONFIGURATION);

// Buy 100 native tokens for X amount of FUN where the exchange rate is 1 token-in : 10 token-out
const { swap } = await exchange.getUnsignedSwapTxFromAmountOut(
TEST_FROM_ADDRESS,
FUN_TEST_TOKEN.address,
'native',
newAmountFromString('100', NATIVE_TEST_TOKEN).value,
3, // 3 % slippage
);

expectToBeDefined(swap.transaction.data);
expectToBeDefined(swap.transaction.value);

const { swapParams } = decodeMulticallExactOutputSingleWithoutFees(swap.transaction.data);

expect(swapParams.recipient).toEqual(TEST_ROUTER_ADDRESS);
});

it('should specify the quoted amount with slippage applied in the unwrapWETH9 function calldata', async () => {
mockRouterImplementation({
pools: [createPool(nativeTokenService.wrappedToken, FUN_TEST_TOKEN)],
});

const swapRouterInterface = SwapRouter.INTERFACE;
const paymentsInterface = PaymentsExtended.INTERFACE;
const exchange = new Exchange(TEST_DEX_CONFIGURATION);

// Buy 100 native tokens for X amount of FUN where the exchange rate is 1 token-in : 10 token-out
const { swap } = await exchange.getUnsignedSwapTxFromAmountOut(
TEST_FROM_ADDRESS,
FUN_TEST_TOKEN.address,
'native',
newAmountFromString('100', NATIVE_TEST_TOKEN).value,
10, // 10 % slippage for easier test math
);

expectToBeDefined(swap.transaction.data);
expectToBeDefined(swap.transaction.value);
const calldata = swap.transaction.data.toString();

const topLevelParams = swapRouterInterface.decodeFunctionData('multicall(uint256,bytes[])', calldata);

expect(topLevelParams.data.length).toBe(2); // expect that there are two calls in the multicall
const swapFunctionCalldata = topLevelParams.data[0];
const unwrapWETHFunctionCalldata = topLevelParams.data[1];

expectToBeString(swapFunctionCalldata);
expectToBeString(unwrapWETHFunctionCalldata);

const decodedUnwrapWETH9FunctionData = paymentsInterface.decodeFunctionData(
'unwrapWETH9(uint256)',
unwrapWETHFunctionCalldata,
);

expect(formatEther(decodedUnwrapWETH9FunctionData.toString())).toEqual('100.0'); // expect the user-specified amount
});
});
});

describe('Swap with multiple pools and secondary fees', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/internal/dex/sdk/src/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export class Exchange {

const swap = getSwap(
tokenIn,
tokenOut,
adjustedQuote,
fromAddress,
slippagePercent,
Expand Down
12 changes: 12 additions & 0 deletions packages/internal/dex/sdk/src/lib/transactionUtils/swap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('getSwap', () => {

const swap = getSwap(
quote.amountIn.token,
quote.amountOut.token,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -95,6 +96,7 @@ describe('getSwap', () => {

const swap = getSwap(
quote.amountIn.token,
quote.amountOut.token,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -120,6 +122,7 @@ describe('getSwap', () => {

const swap = getSwap(
quote.amountIn.token,
quote.amountOut.token,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -143,6 +146,7 @@ describe('getSwap', () => {

const swap = getSwap(
quote.amountIn.token,
quote.amountOut.token,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -164,11 +168,13 @@ describe('getSwap', () => {
describe('with EXACT_INPUT + native amount in', () => {
it('uses the amountSpecified as the transaction value', () => {
const originalTokenIn = nativeTokenService.nativeToken;
const originalTokenOut = FUN_TEST_TOKEN;
const quote = buildExactInputQuote(nativeTokenService.wrappedToken, FUN_TEST_TOKEN);
quote.amountIn.value = utils.parseEther('99');

const swap = getSwap(
originalTokenIn,
originalTokenOut,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -186,10 +192,12 @@ describe('getSwap', () => {
describe('with EXACT_INPUT + native amount out', () => {
it('sets a transaction value of zero', () => {
const originalTokenIn = FUN_TEST_TOKEN;
const originalTokenOut = NATIVE_TEST_TOKEN;
const quote = buildExactInputQuote(FUN_TEST_TOKEN, nativeTokenService.wrappedToken);

const swap = getSwap(
originalTokenIn,
originalTokenOut,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -207,11 +215,13 @@ describe('getSwap', () => {
describe('with EXACT_OUTPUT + native amount in', () => {
it('sets the transaction value to the max amount in including slippage', () => {
const originalTokenIn = nativeTokenService.nativeToken;
const originalTokenOut = FUN_TEST_TOKEN;
const quote = buildExactOutputQuote(nativeTokenService.wrappedToken, FUN_TEST_TOKEN);
quote.amountIn.value = utils.parseEther('100');

const swap = getSwap(
originalTokenIn,
originalTokenOut,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand All @@ -229,10 +239,12 @@ describe('getSwap', () => {
describe('with EXACT_OUTPUT + native amount out', () => {
it('sets a transaction value of zero', () => {
const originalTokenIn = FUN_TEST_TOKEN;
const originalTokenOut = NATIVE_TEST_TOKEN;
const quote = buildExactOutputQuote(FUN_TEST_TOKEN, nativeTokenService.wrappedToken);

const swap = getSwap(
originalTokenIn,
originalTokenOut,
quote,
makeAddr('fromAddress'),
slippagePercentage,
Expand Down
Loading

0 comments on commit acf8365

Please sign in to comment.