Skip to content

Commit

Permalink
[NO-CHANGELOG] secondary fees config (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjimmutable committed Jul 21, 2023
1 parent 80ae0ef commit 9a21498
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 42 deletions.
160 changes: 123 additions & 37 deletions packages/internal/dex/sdk/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ import { ExchangeConfiguration, ExchangeContracts } from './index';
import { IMMUTABLE_TESTNET_CHAIN_ID } from '../constants/chains';

describe('ExchangeConfiguration', () => {
const chainId = 999999999;
// 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 commonRoutingTokensSingle: TokenInfo[] = [
{
chainId,
address: '0x12958b06abdf2701ace6ceb3ce0b8b1ce11e0851',
decimals: 18,
symbol: 'FUN',
name: 'The Fungibles Token',
},
];

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,
};

describe('when given sandbox environment with supported chain id', () => {
it('should create successfully', () => {
const baseConfig = new ImmutableConfiguration({
Expand Down Expand Up @@ -42,19 +62,12 @@ 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,
}); // 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[] = [
Expand All @@ -81,12 +94,20 @@ describe('ExchangeConfiguration', () => {
},
];

const secondaryFees = [
{
feeRecipient: dummyFeeRecipient,
feeBasisPoints: 100,
},
];

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,84 +133,149 @@ 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].feeBasisPoints.toString())
.toEqual(secondaryFees[0].feeBasisPoints.toString());
});

it('should throw when missing configuration', () => {
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 = {
const invalidContractOverrides: ExchangeContracts = {
multicall: '',
coreFactory: test.TEST_V3_CORE_FACTORY_ADDRESS,
quoterV2: test.TEST_QUOTER_ADDRESS,
peripheryRouter: test.TEST_PERIPHERY_ROUTER_ADDRESS,
};

const commonRoutingTokens: TokenInfo[] = [
const rpcURL = 'https://anrpcurl.net';
const overrides: ExchangeOverrides = {
rpcURL,
exchangeContracts: invalidContractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
};

expect(() => new ExchangeConfiguration({
chainId,
baseConfig: immutableConfig,
overrides,
})).toThrow(new InvalidConfigurationError('Invalid exchange contract address for multicall'));
});

it('show throw when given an invalid RPC URL', () => {
const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
}); // environment isn't used because we override all of the config values

const rpcURL = '';
const overrides: ExchangeOverrides = {
rpcURL,
exchangeContracts: contractOverrides,
commonRoutingTokens: commonRoutingTokensSingle,
nativeToken: test.IMX_TEST_TOKEN,
};

expect(() => new ExchangeConfiguration({
chainId,
baseConfig: immutableConfig,
overrides,
})).toThrow(new InvalidConfigurationError('Missing override: rpcURL'));
});

it('should throw when given an invalid secondary fee recipient address', () => {
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 secondaryFees = [
{
chainId,
address: '0x12958b06abdf2701ace6ceb3ce0b8b1ce11e0851',
decimals: 18,
symbol: 'FUN',
name: 'The Fungibles Token',
feeRecipient: invalidFeeRecipient,
feeBasisPoints: 100,
},
];

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

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

it('show throw when given an invalid RPC URL', () => {
const chainId = 999999999;
it('should throw when given invalid secondary fee basis points', () => {
const dummyFeeRecipient = '0xb18c44b211065E69844FbA9AE146DA362104AfBf';

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

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

const commonRoutingTokens: TokenInfo[] = [
const secondaryFees = [
{
chainId,
address: '0x12958b06abdf2701ace6ceb3ce0b8b1ce11e0851',
decimals: 18,
symbol: 'FUN',
name: 'The Fungibles Token',
feeRecipient: dummyFeeRecipient,
feeBasisPoints: 10001,
},
];

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

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

it('should not set secondary fees when not given', () => {
const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
}); // environment isn't used because we override all of the config values

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

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

expect(config.secondaryFees).toEqual([]);
});
});
});
27 changes: 23 additions & 4 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 { SecondaryFee, isValidNonZeroAddress } from 'lib';
import { Chain, ExchangeModuleConfiguration, ExchangeOverrides } from '../types';

export type ExchangeContracts = {
Expand Down Expand Up @@ -39,17 +39,31 @@ export const SUPPORTED_CHAIN_IDS_FOR_ENVIRONMENT: Record<Environment, Record<num
};

function validateOverrides(overrides: ExchangeOverrides) {
Object.entries(overrides).forEach(([key, value]) => {
if (!value) {
const keysToCheck = ['rpcURL', 'exchangeContracts', 'commonRoutingTokens', 'nativeToken'] as const;
for (const key of keysToCheck) {
if (!overrides[key]) {
throw new InvalidConfigurationError(`Missing override: ${key}`);
}
});
}

Object.entries(overrides.exchangeContracts).forEach(([key, contract]) => {
if (!isValidNonZeroAddress(contract)) {
throw new InvalidConfigurationError(`Invalid exchange contract address for ${key}`);
}
});

if (!overrides.secondaryFees) {
return;
}

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

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

public chain: Chain;

public secondaryFees: SecondaryFee[];

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

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

this.secondaryFees = overrides.secondaryFees ? overrides.secondaryFees : [];

return;
}
this.secondaryFees = [];

const chain = SUPPORTED_CHAIN_IDS_FOR_ENVIRONMENT[baseConfig.environment][chainId];
if (!chain) {
Expand Down
12 changes: 11 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, SecondaryFee, 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: SecondaryFee[];

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 Expand Up @@ -212,4 +218,8 @@ export class Exchange {
TradeType.EXACT_OUTPUT,
);
}

private thereAreSecondaryFees(): boolean {
return this.secondaryFees.length > 0;
}
}
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 {number} feeBasisPoints - The fee percentage in basis points
* @example 100 basis points = 1%
*/
export type SecondaryFee = {
feeRecipient: string;
feeBasisPoints: number;
};

/**
* 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?: SecondaryFee[];
}

export interface ExchangeModuleConfiguration
Expand Down

0 comments on commit 9a21498

Please sign in to comment.