Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TP-1353][NO-CHANGELOG] secondary fees config #579

Merged
merged 5 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions packages/internal/dex/sdk/src/config/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Environment, ImmutableConfiguration } from '@imtbl/config';
import { ChainNotSupportedError, InvalidConfigurationError } from 'errors';
import * as test from 'utils/testUtils';
import { ethers } from 'ethers';
import { ExchangeModuleConfiguration, ExchangeOverrides, TokenInfo } from '../types';
import { ExchangeConfiguration, ExchangeContracts } from './index';
import { IMMUTABLE_TESTNET_CHAIN_ID } from '../constants/chains';
Expand Down Expand Up @@ -43,6 +44,7 @@ describe('ExchangeConfiguration', () => {
describe('when given overrides', () => {
it('should override any configuration with given values', () => {
const chainId = 999999999;
const dummyFeeRecipient = '0xb18c44b211065E69844FbA9AE146DA362104AfBf';

const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
Expand Down Expand Up @@ -81,12 +83,20 @@ describe('ExchangeConfiguration', () => {
},
];

const secondaryFees = [
{
feeRecipient: dummyFeeRecipient,
feePrcntInBasisPoints: ethers.BigNumber.from('100'),
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
},
];

const rpcURL = 'https://anrpcurl.net';
const overrides: ExchangeOverrides = {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens,
nativeToken: test.IMX_TEST_TOKEN,
secondaryFees,
};

const config = new ExchangeConfiguration({
Expand All @@ -112,6 +122,16 @@ describe('ExchangeConfiguration', () => {

expect(config.chain.commonRoutingTokens[2].address.toLowerCase())
.toEqual(commonRoutingTokens[2].address.toLowerCase());

expect(config.secondaryFees).toBeDefined();
if (!config.secondaryFees) {
// This should never happen
throw new Error('Secondary fees should be defined');
}
expect(config.secondaryFees[0].feeRecipient.toLowerCase())
.toEqual(dummyFeeRecipient.toLowerCase());
expect(config.secondaryFees[0].feePrcntInBasisPoints.toString())
.toEqual(secondaryFees[0].feePrcntInBasisPoints.toString());
});

it('should throw when missing configuration', () => {
Expand Down Expand Up @@ -191,5 +211,152 @@ describe('ExchangeConfiguration', () => {
overrides,
})).toThrow(new InvalidConfigurationError('Missing override: rpcURL'));
});

it('should throw when given an invalid secondary fee recipient address', () => {
const chainId = 999999999;
const invalidFeeRecipient = '0x18c44b211065E69844FbA9AE146DA362104AfBf'; // too short

const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
}); // environment isn't used because we override all of the config values

const contractOverrides: ExchangeContracts = {
multicall: test.TEST_MULTICALL_ADDRESS,
coreFactory: test.TEST_V3_CORE_FACTORY_ADDRESS,
quoterV2: test.TEST_QUOTER_ADDRESS,
peripheryRouter: test.TEST_PERIPHERY_ROUTER_ADDRESS,
};

// This list can be updated with any Tokens that are deployed to the chain being configured
// These tokens will be used to find available pools for a swap
const commonRoutingTokens: TokenInfo[] = [
{
chainId,
address: '0x12958b06abdf2701ace6ceb3ce0b8b1ce11e0851',
decimals: 18,
symbol: 'FUN',
name: 'The Fungibles Token',
},
];

const secondaryFees = [
{
feeRecipient: invalidFeeRecipient,
feePrcntInBasisPoints: ethers.BigNumber.from('100'),
},
];

const rpcURL = 'https://anrpcurl.net';
const overrides: ExchangeOverrides = {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens,
nativeToken: test.IMX_TEST_TOKEN,
secondaryFees,
};

expect(() => new ExchangeConfiguration({
chainId,
baseConfig: immutableConfig,
overrides,
})).toThrow(new InvalidConfigurationError(
`Invalid secondary fee recipient address: ${secondaryFees[0].feeRecipient}`,
));
});

it('should throw when given an invalid secondary fee percentage', () => {
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
const chainId = 999999999;
const dummyFeeRecipient = '0xb18c44b211065E69844FbA9AE146DA362104AfBf'; // too short
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved

const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
}); // environment isn't used because we override all of the config values

const contractOverrides: ExchangeContracts = {
multicall: test.TEST_MULTICALL_ADDRESS,
coreFactory: test.TEST_V3_CORE_FACTORY_ADDRESS,
quoterV2: test.TEST_QUOTER_ADDRESS,
peripheryRouter: test.TEST_PERIPHERY_ROUTER_ADDRESS,
};

// This list can be updated with any Tokens that are deployed to the chain being configured
// These tokens will be used to find available pools for a swap
const commonRoutingTokens: TokenInfo[] = [
{
chainId,
address: '0x12958b06abdf2701ace6ceb3ce0b8b1ce11e0851',
decimals: 18,
symbol: 'FUN',
name: 'The Fungibles Token',
},
];
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved

const secondaryFees = [
{
feeRecipient: dummyFeeRecipient,
feePrcntInBasisPoints: ethers.BigNumber.from('10001'),
},
];

const rpcURL = 'https://anrpcurl.net';
const overrides: ExchangeOverrides = {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens,
nativeToken: test.IMX_TEST_TOKEN,
secondaryFees,
};

expect(() => new ExchangeConfiguration({
chainId,
baseConfig: immutableConfig,
overrides,
})).toThrow(new InvalidConfigurationError(
`Invalid secondary fee percentage: ${secondaryFees[0].feePrcntInBasisPoints}`,
));
});

it('should not set secondary fees when not given', () => {
const chainId = 999999999;

const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
}); // environment isn't used because we override all of the config values

const contractOverrides: ExchangeContracts = {
multicall: test.TEST_MULTICALL_ADDRESS,
coreFactory: test.TEST_V3_CORE_FACTORY_ADDRESS,
quoterV2: test.TEST_QUOTER_ADDRESS,
peripheryRouter: test.TEST_PERIPHERY_ROUTER_ADDRESS,
};

// This list can be updated with any Tokens that are deployed to the chain being configured
// These tokens will be used to find available pools for a swap
const commonRoutingTokens: TokenInfo[] = [
{
chainId,
address: '0x12958b06abdf2701ace6ceb3ce0b8b1ce11e0851',
decimals: 18,
symbol: 'FUN',
name: 'The Fungibles Token',
},
];

const rpcURL = 'https://anrpcurl.net';
const overrides: ExchangeOverrides = {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens,
nativeToken: test.IMX_TEST_TOKEN,
};

const config = new ExchangeConfiguration({
chainId,
baseConfig: immutableConfig,
overrides,
});

expect(config.secondaryFees).toBeUndefined();
});
});
});
21 changes: 19 additions & 2 deletions packages/internal/dex/sdk/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Environment, ImmutableConfiguration } from '@imtbl/config';
import { IMMUTABLE_TESTNET_COMMON_ROUTING_TOKENS, TIMX_IMMUTABLE_TESTNET } from 'constants/tokens';
import { ChainNotSupportedError, InvalidConfigurationError } from 'errors';
import { IMMUTABLE_TESTNET_CHAIN_ID, IMMUTABLE_TESTNET_RPC_URL } from 'constants/chains';
import { isValidNonZeroAddress } from 'lib';
import { SecondaryFees, isValidNonZeroAddress } from 'lib';
import { Chain, ExchangeModuleConfiguration, ExchangeOverrides } from '../types';

export type ExchangeContracts = {
Expand Down Expand Up @@ -40,7 +40,7 @@ export const SUPPORTED_CHAIN_IDS_FOR_ENVIRONMENT: Record<Environment, Record<num

function validateOverrides(overrides: ExchangeOverrides) {
Object.entries(overrides).forEach(([key, value]) => {
if (!value) {
if (!value && key !== 'secondaryFees') {
throw new InvalidConfigurationError(`Missing override: ${key}`);
}
});
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -50,6 +50,19 @@ function validateOverrides(overrides: ExchangeOverrides) {
throw new InvalidConfigurationError(`Invalid exchange contract address for ${key}`);
}
});

if (!overrides.secondaryFees) {
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
return;
}

for (const secondaryFee of overrides.secondaryFees) {
if (!isValidNonZeroAddress(secondaryFee.feeRecipient)) {
throw new InvalidConfigurationError(`Invalid secondary fee recipient address: ${secondaryFee.feeRecipient}`);
}
if (secondaryFee.feePrcntInBasisPoints.isNegative() || secondaryFee.feePrcntInBasisPoints.gt(10000)) {
throw new InvalidConfigurationError(`Invalid secondary fee percentage: ${secondaryFee.feePrcntInBasisPoints}`);
}
}
}

/**
Expand All @@ -62,6 +75,8 @@ export class ExchangeConfiguration {

public chain: Chain;

public secondaryFees?: SecondaryFees[];

constructor({ chainId, baseConfig, overrides }: ExchangeModuleConfiguration) {
this.baseConfig = baseConfig;

Expand All @@ -75,6 +90,8 @@ export class ExchangeConfiguration {
nativeToken: overrides.nativeToken,
};

this.secondaryFees = overrides.secondaryFees;

return;
}

Expand Down
8 changes: 7 additions & 1 deletion packages/internal/dex/sdk/src/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {

import { Router } from './lib/router';
import { getERC20Decimals, isValidNonZeroAddress } from './lib/utils';
import { ExchangeModuleConfiguration, TokenInfo, TransactionResponse } from './types';
import {
ExchangeModuleConfiguration, SecondaryFees, TokenInfo, TransactionResponse,
} from './types';
import { getSwap } from './lib/transactionUtils/swap';
import { ExchangeConfiguration } from './config';

Expand All @@ -26,6 +28,8 @@ export class Exchange {

private nativeToken: TokenInfo;

private secondaryFees?: SecondaryFees[];
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved

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

Expand All @@ -36,6 +40,8 @@ export class Exchange {
config.chain.rpcUrl,
);

this.secondaryFees = config.secondaryFees;

this.router = new Router(
this.provider,
config.chain.commonRoutingTokens,
Expand Down
12 changes: 12 additions & 0 deletions packages/internal/dex/sdk/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export type Chain = {
nativeToken: TokenInfo;
};

/**
* Interface representing the secondary fees for a swap
* @property {string} feeRecipient - The fee recipient address
* @property {ethers.BigNumber} feePrcntInBasisPoints - The fee percentage in basis points
* @example 100 basis points = 1%
*/
export type SecondaryFees = {
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
feeRecipient: string;
feePrcntInBasisPoints: ethers.BigNumber;
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* Interface representing an amount with the token information
* @property {TokenInfo} token - The token information
Expand Down Expand Up @@ -83,6 +94,7 @@ export interface ExchangeOverrides {
exchangeContracts: ExchangeContracts;
commonRoutingTokens: TokenInfo[];
nativeToken: TokenInfo;
secondaryFees?: SecondaryFees[];
Benjimmutable marked this conversation as resolved.
Show resolved Hide resolved
}

export interface ExchangeModuleConfiguration
Expand Down