Skip to content

Commit

Permalink
TP-1649: Native support for getOurQuoteReqAmount (#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
keithbro-imx authored Oct 18, 2023
1 parent f4b0610 commit d7d2004
Show file tree
Hide file tree
Showing 16 changed files with 301 additions and 57 deletions.
18 changes: 12 additions & 6 deletions packages/internal/dex/sdk/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ describe('ExchangeConfiguration', () => {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens,
nativeToken: test.IMX_TEST_TOKEN,
nativeToken: test.NATIVE_TEST_TOKEN,
wrappedNativeToken: test.WIMX_TEST_TOKEN,
};

const config = new ExchangeConfiguration({
Expand Down Expand Up @@ -169,7 +170,8 @@ describe('ExchangeConfiguration', () => {
rpcURL,
exchangeContracts: invalidContractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
nativeToken: test.NATIVE_TEST_TOKEN,
wrappedNativeToken: test.WIMX_TEST_TOKEN,
};

expect(
Expand All @@ -192,7 +194,8 @@ describe('ExchangeConfiguration', () => {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
nativeToken: test.NATIVE_TEST_TOKEN,
wrappedNativeToken: test.WIMX_TEST_TOKEN,
};

expect(
Expand Down Expand Up @@ -224,7 +227,8 @@ describe('ExchangeConfiguration', () => {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
nativeToken: test.NATIVE_TEST_TOKEN,
wrappedNativeToken: test.WIMX_TEST_TOKEN,
};

expect(
Expand Down Expand Up @@ -281,7 +285,8 @@ describe('ExchangeConfiguration', () => {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
nativeToken: test.NATIVE_TEST_TOKEN,
wrappedNativeToken: test.WIMX_TEST_TOKEN,
};

expect(
Expand Down Expand Up @@ -329,7 +334,8 @@ describe('ExchangeConfiguration', () => {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
nativeToken: test.NATIVE_TEST_TOKEN,
wrappedNativeToken: test.WIMX_TEST_TOKEN,
};

const config = new ExchangeConfiguration({
Expand Down
5 changes: 4 additions & 1 deletion packages/internal/dex/sdk/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IMMUTABLE_TESTNET_COMMON_ROUTING_TOKENS,
IMMUTABLE_TESTNET_RPC_URL,
MAX_SECONDARY_FEE_BASIS_POINTS,
NATIVE_IMX_IMMUTABLE_TESTNET,
TIMX_IMMUTABLE_TESTNET,
} from '../constants';

Expand Down Expand Up @@ -34,7 +35,8 @@ export const SUPPORTED_SANDBOX_CHAINS: Record<number, Chain> = {
rpcUrl: IMMUTABLE_TESTNET_RPC_URL,
contracts: CONTRACTS_FOR_CHAIN_ID[IMMUTABLE_TESTNET_CHAIN_ID],
commonRoutingTokens: IMMUTABLE_TESTNET_COMMON_ROUTING_TOKENS,
nativeToken: TIMX_IMMUTABLE_TESTNET,
nativeToken: NATIVE_IMX_IMMUTABLE_TESTNET,
wrappedNativeToken: TIMX_IMMUTABLE_TESTNET, // TODO: TP-1649: Change to WIMX when ready.
},
};

Expand Down Expand Up @@ -108,6 +110,7 @@ export class ExchangeConfiguration {
contracts: overrides.exchangeContracts,
commonRoutingTokens: overrides.commonRoutingTokens,
nativeToken: overrides.nativeToken,
wrappedNativeToken: overrides.wrappedNativeToken,
};

this.secondaryFees = secondaryFees || [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { IMMUTABLE_TESTNET_CHAIN_ID } from 'constants/chains';
import { ERC20 } from 'types';
import { ERC20, Native } from 'types';

export const NATIVE_IMX_IMMUTABLE_TESTNET: Native = {
chainId: IMMUTABLE_TESTNET_CHAIN_ID,
decimals: 18,
symbol: 'tIMX',
name: 'Immutable Testnet Token',
type: 'native',
};

export const TIMX_IMMUTABLE_TESTNET: ERC20 = {
chainId: IMMUTABLE_TESTNET_CHAIN_ID,
Expand All @@ -10,6 +18,16 @@ export const TIMX_IMMUTABLE_TESTNET: ERC20 = {
type: 'erc20',
};

export const WIMX_IMMUTABLE_TESTNET: ERC20 = {
chainId: IMMUTABLE_TESTNET_CHAIN_ID,
address: '0xAf7cf5D4Af0BFAa85d384d42b8D410762Ccbce69',
decimals: 18,
symbol: 'WIMX',
name: 'Wrapped Immutable Testnet Token',
type: 'erc20',
};

export const IMMUTABLE_TESTNET_COMMON_ROUTING_TOKENS: ERC20[] = [
TIMX_IMMUTABLE_TESTNET,
WIMX_IMMUTABLE_TESTNET,
];
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
TEST_PERIPHERY_ROUTER_ADDRESS,
TEST_DEX_CONFIGURATION,
TEST_GAS_PRICE,
IMX_TEST_TOKEN,
TEST_TRANSACTION_GAS_USAGE,
TEST_FEE_RECIPIENT,
TEST_MAX_FEE_BASIS_POINTS,
Expand All @@ -29,6 +28,7 @@ import {
expectInstanceOf,
newAmountFromString,
formatTokenAmount,
WIMX_TEST_TOKEN,
} from './test/utils';
import {
addAmount, Router, SecondaryFee,
Expand Down Expand Up @@ -117,11 +117,11 @@ describe('getUnsignedSwapTxFromAmountIn', () => {

expectToBeDefined(tx.approval?.gasFeeEstimate);
expect(tx.approval.gasFeeEstimate.value).toEqual(TEST_GAS_PRICE.mul(APPROVE_GAS_ESTIMATE));
expect(tx.approval.gasFeeEstimate.token.chainId).toEqual(IMX_TEST_TOKEN.chainId);
expect(tx.approval.gasFeeEstimate.token.address).toEqual(IMX_TEST_TOKEN.address);
expect(tx.approval.gasFeeEstimate.token.decimals).toEqual(IMX_TEST_TOKEN.decimals);
expect(tx.approval.gasFeeEstimate.token.symbol).toEqual(IMX_TEST_TOKEN.symbol);
expect(tx.approval.gasFeeEstimate.token.name).toEqual(IMX_TEST_TOKEN.name);
expect(tx.approval.gasFeeEstimate.token.chainId).toEqual(WIMX_TEST_TOKEN.chainId);
expect(tx.approval.gasFeeEstimate.token.address).toEqual(WIMX_TEST_TOKEN.address);
expect(tx.approval.gasFeeEstimate.token.decimals).toEqual(WIMX_TEST_TOKEN.decimals);
expect(tx.approval.gasFeeEstimate.token.symbol).toEqual(WIMX_TEST_TOKEN.symbol);
expect(tx.approval.gasFeeEstimate.token.name).toEqual(WIMX_TEST_TOKEN.name);
});
});

Expand Down Expand Up @@ -429,11 +429,11 @@ describe('getUnsignedSwapTxFromAmountIn', () => {
expectToBeDefined(tx.swap.gasFeeEstimate);

expect(tx.swap.gasFeeEstimate.value).toEqual(TEST_TRANSACTION_GAS_USAGE.mul(TEST_GAS_PRICE));
expect(tx.swap.gasFeeEstimate.token.chainId).toEqual(IMX_TEST_TOKEN.chainId);
expect(tx.swap.gasFeeEstimate.token.address).toEqual(IMX_TEST_TOKEN.address);
expect(tx.swap.gasFeeEstimate.token.decimals).toEqual(IMX_TEST_TOKEN.decimals);
expect(tx.swap.gasFeeEstimate.token.symbol).toEqual(IMX_TEST_TOKEN.symbol);
expect(tx.swap.gasFeeEstimate.token.name).toEqual(IMX_TEST_TOKEN.name);
expect(tx.swap.gasFeeEstimate.token.chainId).toEqual(WIMX_TEST_TOKEN.chainId);
expect(tx.swap.gasFeeEstimate.token.address).toEqual(WIMX_TEST_TOKEN.address);
expect(tx.swap.gasFeeEstimate.token.decimals).toEqual(WIMX_TEST_TOKEN.decimals);
expect(tx.swap.gasFeeEstimate.token.symbol).toEqual(WIMX_TEST_TOKEN.symbol);
expect(tx.swap.gasFeeEstimate.token.name).toEqual(WIMX_TEST_TOKEN.name);
});

it('returns valid quote', async () => {
Expand Down
15 changes: 12 additions & 3 deletions packages/internal/dex/sdk/src/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getApproval, prepareApproval } from 'lib/transactionUtils/approval';
import { getOurQuoteReqAmount, prepareUserQuote } from 'lib/transactionUtils/getQuote';
import { Fees } from 'lib/fees';
import { SecondaryFee__factory } from 'contracts/types';
import { NativeTokenService } from 'lib/nativeTokenService';
import {
DEFAULT_DEADLINE,
DEFAULT_MAX_HOPS,
Expand All @@ -25,7 +26,7 @@ import {
} from './lib/utils';
import {
ERC20,
ExchangeModuleConfiguration, SecondaryFee, TransactionResponse,
ExchangeModuleConfiguration, Native, SecondaryFee, TransactionResponse,
} from './types';
import { getSwap, prepareSwap } from './lib/transactionUtils/swap';
import { ExchangeConfiguration } from './config';
Expand All @@ -37,15 +38,21 @@ export class Exchange {

private chainId: number;

private nativeToken: ERC20;
private nativeToken: Native;

private wrappedNativeToken: ERC20;

private secondaryFees: SecondaryFee[];

private nativeTokenService: NativeTokenService;

constructor(configuration: ExchangeModuleConfiguration) {
const config = new ExchangeConfiguration(configuration);

this.chainId = config.chain.chainId;
this.nativeToken = config.chain.nativeToken;
this.wrappedNativeToken = config.chain.wrappedNativeToken;
this.nativeTokenService = new NativeTokenService(this.nativeToken, this.wrappedNativeToken);
this.secondaryFees = config.secondaryFees;

this.provider = new ethers.providers.JsonRpcProvider(
Expand Down Expand Up @@ -140,7 +147,7 @@ export class Exchange {

const fees = new Fees(secondaryFees, tokenIn);

const ourQuoteReqAmount = getOurQuoteReqAmount(amountSpecified, fees, tradeType);
const ourQuoteReqAmount = getOurQuoteReqAmount(amountSpecified, fees, tradeType, this.nativeTokenService);

// get quote and gas details
const [ourQuote, gasPrice] = await Promise.all([
Expand All @@ -164,6 +171,7 @@ export class Exchange {
this.router.routingContracts.secondaryFeeAddress,
gasPrice,
secondaryFees,
this.nativeTokenService,
);

const userQuote = prepareUserQuote(otherToken, adjustedQuote, slippagePercent, fees);
Expand All @@ -182,6 +190,7 @@ export class Exchange {
fromAddress,
preparedApproval,
gasPrice,
this.nativeTokenService,
);

return {
Expand Down
87 changes: 87 additions & 0 deletions packages/internal/dex/sdk/src/lib/nativeTokenService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
expectERC20,
expectNative,
formatAmount,
FUN_TEST_TOKEN,
nativeTokenService,
newAmountFromString,
} from 'test/utils';

describe('NativeTokenService', () => {
describe('wrapAmount', () => {
it('converts native token amounts to their wrapped equivalent', () => {
const nativeAmount = newAmountFromString('1', nativeTokenService.nativeToken);
const wrappedEquivalent = nativeTokenService.wrapAmount(nativeAmount);
expectERC20(wrappedEquivalent.token, nativeTokenService.wrappedToken.address);
expect(formatAmount(wrappedEquivalent)).toEqual('1.0');
});
});

describe('unwrapAmount', () => {
it('converts wrapped token amounts to their native equivalent', () => {
const wrappedAmount = newAmountFromString('1', nativeTokenService.wrappedToken);
const nativeEquivalent = nativeTokenService.unwrapAmount(wrappedAmount);
expectNative(nativeEquivalent.token);
expect(formatAmount(nativeEquivalent)).toEqual('1.0');
});

it('throws an error if the token is not a wrapped native amount', () => {
const erc20Amount = newAmountFromString('1', FUN_TEST_TOKEN);
expect(() => nativeTokenService.unwrapAmount(erc20Amount)).toThrowError(
'token 0xCc7bb2D219A0FC08033E130629C2B854b7bA9195 is not wrapped',
);
});
});

describe('maybeWrapToken', () => {
it('wraps native tokens', () => {
const wrappedToken = nativeTokenService.maybeWrapToken(nativeTokenService.nativeToken);
expectERC20(wrappedToken);
});

it('does not wrap an already wrapped token', () => {
const stillTheWrappedToken = nativeTokenService.maybeWrapToken(nativeTokenService.wrappedToken);
expect(stillTheWrappedToken).toEqual(nativeTokenService.wrappedToken);
});

it('does not wrap a normal ERC20 token', () => {
const stillTheFunToken = nativeTokenService.maybeWrapToken(FUN_TEST_TOKEN);
expect(stillTheFunToken).toEqual(stillTheFunToken);
});
});

describe('maybeWrapAmount', () => {
it('wraps native amounts', () => {
const nativeAmount = newAmountFromString('1', nativeTokenService.nativeToken);
const wrappedEquivalent = nativeTokenService.maybeWrapAmount(nativeAmount);
expectERC20(wrappedEquivalent.token, nativeTokenService.wrappedToken.address);
expect(formatAmount(wrappedEquivalent)).toEqual('1.0');
});

it('does not wrap an already wrapped amount', () => {
const wrappedAmount = newAmountFromString('1', nativeTokenService.wrappedToken);
const stillTheWrappedAmount = nativeTokenService.maybeWrapAmount(wrappedAmount);
expect(stillTheWrappedAmount).toEqual(wrappedAmount);
});

it('does not wrap a normal ERC20 amount', () => {
const erc20Amount = newAmountFromString('1', FUN_TEST_TOKEN);
const stillTheFunAmount = nativeTokenService.maybeWrapAmount(erc20Amount);
expect(stillTheFunAmount).toEqual(erc20Amount);
});
});

describe('isNativeToken', () => {
it('returns true for the native token', () => {
expect(nativeTokenService.isNativeToken(nativeTokenService.nativeToken)).toEqual(true);
});

it('returns false for the wrapped token', () => {
expect(nativeTokenService.isNativeToken(nativeTokenService.wrappedToken)).toEqual(false);
});

it('returns false for a normal ERC20 token', () => {
expect(nativeTokenService.isNativeToken(FUN_TEST_TOKEN)).toEqual(false);
});
});
});
34 changes: 34 additions & 0 deletions packages/internal/dex/sdk/src/lib/nativeTokenService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
Amount, Coin, ERC20, Native,
} from 'types';
import { newAmount } from './utils';

export class NativeTokenService {
constructor(readonly nativeToken: Native, readonly wrappedToken: ERC20) {}

wrapAmount(amount: Amount<Native>): Amount<ERC20> {
return newAmount(amount.value, this.wrappedToken);
}

unwrapAmount(amount: Amount<ERC20>): Amount<Native> {
if (amount.token !== this.wrappedToken) {
throw new Error(`token ${amount.token.address} is not wrapped`);
}
return newAmount(amount.value, this.nativeToken);
}

maybeWrapToken(token: Coin): ERC20 {
if (this.isNativeToken(token)) {
return this.wrappedToken;
}
return token as ERC20;
}

maybeWrapAmount(amount: Amount<Coin>): Amount<ERC20> {
return newAmount(amount.value, this.maybeWrapToken(amount.token));
}

isNativeToken(token: Coin): token is Native {
return token === this.nativeToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { ethers } from 'ethers';
import { TradeType } from '@uniswap/sdk-core';
import { RoutingContracts } from 'lib/router';
import { newAmount } from 'lib/utils';
import { NativeTokenService } from 'lib/nativeTokenService';
import {
Amount, ERC20, SecondaryFee, TransactionDetails,
Amount, ERC20, Native, SecondaryFee, TransactionDetails,
} from '../../types';
import { calculateGasFee } from './gas';

Expand Down Expand Up @@ -154,7 +155,8 @@ export const getApproval = async (
provider: JsonRpcProvider,
ownerAddress: string,
preparedApproval: PreparedApproval,
gasPrice: Amount<ERC20> | null,
gasPrice: Amount<Native> | null,
nativeTokenService: NativeTokenService,
): Promise<TransactionDetails | null> => {
const approveTransaction = await getApproveTransaction(
provider,
Expand All @@ -178,6 +180,7 @@ export const getApproval = async (

return {
transaction: approveTransaction,
gasFeeEstimate,
// TODO: TP-1649: Remove the wrapping here
gasFeeEstimate: gasFeeEstimate ? nativeTokenService.maybeWrapAmount(gasFeeEstimate) : null,
};
};
Loading

0 comments on commit d7d2004

Please sign in to comment.