Skip to content

Commit

Permalink
feat: Substrate Percentage Fee (#297)
Browse files Browse the repository at this point in the history
* substrate percentage fee

* remove console

* fix lint

* add evm handler:

* add missing parachain param

* remove unused var

* fix ts errors

* fix fee tests

* fix test

* update imports

* address comments

---------

Co-authored-by: Matija Petrunić <[email protected]>
  • Loading branch information
FSM1 and mpetrun5 authored Sep 20, 2023
1 parent 41ea37a commit 97eadc5
Show file tree
Hide file tree
Showing 17 changed files with 1,591 additions and 1,214 deletions.
2 changes: 1 addition & 1 deletion examples/substrate-to-evm-fungible-transfer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
},
"dependencies": {
"@buildwithsygma/sygma-sdk-core": "2.2.0",
"@polkadot/api": "10.7.3",
"@polkadot/api": "10.7.2",
"@polkadot/keyring": "12.2.1",
"@polkadot/util-crypto": "^12.2.1"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"typescript": "5.0.4"
},
"dependencies": {
"@buildwithsygma/sygma-contracts": "2.1.2",
"@buildwithsygma/sygma-contracts": "2.4.1",
"@ethersproject/abi": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
Expand Down
18 changes: 18 additions & 0 deletions packages/sdk/src/chains/EVM/assetTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
erc20Transfer,
erc721Transfer,
getERC20Allowance,
getPercentageFee,
isApproved,
} from '.';

Expand Down Expand Up @@ -125,6 +126,23 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
depositData: createERCDepositData(
fungibleTransfer.details.amount,
fungibleTransfer.details.recipient,
fungibleTransfer.details.parachainId,
),
});
}
case FeeHandlerType.PERCENTAGE: {
const fungibleTransfer = transfer as Transfer<Fungible>;
return await getPercentageFee({
precentageFeeHandlerAddress: feeHandlerAddress,
provider: this.provider,
sender: transfer.sender,
fromDomainID: Number(transfer.from.id),
toDomainID: Number(transfer.to.id),
resourceID: transfer.resource.resourceId,
depositData: createERCDepositData(
fungibleTransfer.details.amount,
fungibleTransfer.details.recipient,
fungibleTransfer.details.parachainId,
),
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/chains/EVM/fee/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { calculateDynamicFee } from './dynamicFee.js';
export { calculateBasicfee } from './basicFee.js';
export { getFeeHandlerAddress } from './feeHandler.js';
export { getPercentageFee } from './percentageFee.js';
50 changes: 50 additions & 0 deletions packages/sdk/src/chains/EVM/fee/percentageFee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PercentageERC20FeeHandlerEVM__factory } from '@buildwithsygma/sygma-contracts';
import { ethers, utils } from 'ethers';
import { FeeHandlerType } from '../../../types';
import { EvmFee } from '../types';

/**
* Calculates and returns the fee in native currency.
*
* @category Fee
* @param {Object} - Object to get the fee data
* @returns {Promise<FeeDataResult>}
*/
export const getPercentageFee = async ({
precentageFeeHandlerAddress,
provider,
sender,
fromDomainID,
toDomainID,
resourceID,
depositData,
}: {
precentageFeeHandlerAddress: string;
provider: ethers.providers.Provider;
sender: string;
fromDomainID: number;
toDomainID: number;
resourceID: string;
depositData: string;
}): Promise<EvmFee> => {
const percentageFeeHandlerContract = PercentageERC20FeeHandlerEVM__factory.connect(
precentageFeeHandlerAddress,
provider,
);
const calculatedFee = await percentageFeeHandlerContract.calculateFee(
sender,
fromDomainID,
toDomainID,
resourceID,
depositData,
utils.formatBytes32String(''),
);

const [fee] = calculatedFee;
return {
fee,
feeData: fee.toHexString(),
type: FeeHandlerType.PERCENTAGE,
handlerAddress: precentageFeeHandlerAddress,
};
};
2 changes: 1 addition & 1 deletion packages/sdk/src/chains/EVM/utils/eventListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const createProposalExecutionEventListener = (
originDomainId: number,
depositNonce: BigNumber,
dataHash: string,
tx: Event,
tx: string,
) => void,
): Bridge => {
const proposalFilter = bridge.filters.ProposalExecution(null, null, null);
Expand Down
35 changes: 14 additions & 21 deletions packages/sdk/src/chains/Substrate/__test__/deposit.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiPromise } from '@polkadot/api';
import { XcmMultiAssetIdType } from '../types';

import * as Utils from '../utils/depositFns.js';
import * as Utils from '../utils/depositFns';
import { Environment } from '../../../types';

jest.mock('@polkadot/api');
Expand All @@ -25,6 +25,19 @@ describe('deposit', () => {
address = 'testaddress';
});

it('should return a multi-asset data object', () => {
const result = Utils.createMultiAssetData(
{ id: 1 } as unknown as XcmMultiAssetIdType,
'1000000000000',
);
expect(result).toEqual({
id: { id: 1 },
fun: {
fungible: '1000000000000',
},
});
});

it('should call calculateBigNumber with correct params', () => {
const signAndSendMockFn = jest.fn();
jest.spyOn(Utils, 'createMultiAssetData').mockReturnValue({
Expand Down Expand Up @@ -56,23 +69,3 @@ describe('deposit', () => {
jest.resetAllMocks();
});
});

describe('createMultiAssetData', () => {
let xcmMultiAssetId: XcmMultiAssetIdType;
let amount: string;

beforeEach(() => {
xcmMultiAssetId = { id: 1 } as unknown as XcmMultiAssetIdType;
amount = '1000000000000';
});
it('should return a multi-asset data object', () => {
const result = Utils.createMultiAssetData(xcmMultiAssetId, amount);

expect(result).toEqual({
id: xcmMultiAssetId,
fun: {
fungible: '1000000000000',
},
});
});
});
22 changes: 18 additions & 4 deletions packages/sdk/src/chains/Substrate/assetTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { U256 } from '@polkadot/types';
import { BN } from '@polkadot/util';
import {
Environment,
FeeHandlerType,
Fungible,
ResourceType,
SubstrateResource,
Expand All @@ -12,7 +13,7 @@ import {
} from '../../types';
import { Config } from '../..';
import { BaseAssetTransfer } from '../BaseAssetTransfer';
import { SubstrateFee, deposit, getBasicFee } from '.';
import { SubstrateFee, deposit, getBasicFee, getFeeHandler, getPercentageFee } from '.';

/**
* Class used for sending fungible and non-fungible transfers from Substrate based chains.
Expand Down Expand Up @@ -65,13 +66,26 @@ export class SubstrateAssetTransfer extends BaseAssetTransfer {
* @returns fee that needs to paid
*/
public async getFee(transfer: Transfer<TransferType>): Promise<SubstrateFee> {
const fee = await getBasicFee(
const substrateResource = transfer.resource as SubstrateResource;

const feeHandlerType = await getFeeHandler(
this.apiPromise,
transfer.to.id,
(transfer.resource as SubstrateResource).xcmMultiAssetId,
substrateResource.xcmMultiAssetId,
);

return fee;
switch (feeHandlerType) {
case FeeHandlerType.BASIC:
return await getBasicFee(
this.apiPromise,
transfer.to.id,
substrateResource.xcmMultiAssetId,
);
case FeeHandlerType.PERCENTAGE:
return await getPercentageFee(this.apiPromise, transfer as Transfer<Fungible>);
default:
throw new Error('Unable to retrieve fee');
}
}

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/src/chains/Substrate/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { AccountInfo } from '@polkadot/types/interfaces';
import { BN } from '@polkadot/util';
import { FeeHandlerType } from '../../../types';

export type SygmaFeeHandlerRouterFeeHandlerType =
| 'BasicFeeHandler'
| 'PercentageFeeHandler'
| 'DynamicFeeHandler';

export type XcmMultiAssetIdType = {
concrete: {
parents: number;
Expand Down
41 changes: 41 additions & 0 deletions packages/sdk/src/chains/Substrate/utils/getFeeHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ApiPromise } from '@polkadot/api';
import { Enum, Option } from '@polkadot/types';
import { FeeHandlerType } from '../../../types';
import { XcmMultiAssetIdType } from '../types';
/**
* Retrieves the fee handler for a given domainId and asset.
*
* @category Fee
* @param {ApiPromise} api - The Substrate API instance.
* @param {number} domainId - The ID of the domain.
* @param {XcmMultiAssetIdType} xcmMultiAssetId - The XCM MultiAsset ID of the asset. {@link https://github.com/sygmaprotocol/sygma-substrate-pallets#multiasset | More details}
* @returns {Promise} A promise that resolves to a FeeHandlerType object.
* @throws {Error} Unable to retrieve fee from pallet
*/
export const getFeeHandler = async (
api: ApiPromise,
destinationDomainId: number,
xcmMultiAssetId: XcmMultiAssetIdType,
): Promise<FeeHandlerType> => {
const feeHandler = await api.query.sygmaFeeHandlerRouter.handlerType<Option<Enum>>([
destinationDomainId,
xcmMultiAssetId,
]);

if (feeHandler.isNone) {
throw new Error('No Fee Handler configured');
}

const feeHandlerName = feeHandler.unwrap().toString();

switch (feeHandlerName) {
case 'PercentageFeeHandler':
return FeeHandlerType.PERCENTAGE;
case 'BasicFeeHandler':
return FeeHandlerType.BASIC;
case 'DynamicFeeHandler':
return FeeHandlerType.DYNAMIC;
default:
throw new Error('Invalid Fee Handler Type');
}
};
60 changes: 60 additions & 0 deletions packages/sdk/src/chains/Substrate/utils/getPercentageFee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ApiPromise } from '@polkadot/api';
import { BN } from '@polkadot/util';
import { Option, Tuple } from '@polkadot/types';
import { SubstrateFee } from '../types';
import { FeeHandlerType, Fungible, Transfer, SubstrateResource } from '../../../types';

/**
* Retrieves the basic fee for a given domainId and asset.
*
* @example
* // Assuming the api instance is already connected and ready to use
* const domainId = 1;
* const xcmMultiAssetId = {...} // XCM MultiAsset ID of the asset
* getBasicFee(api, domainId, xcmMultiAssetId)
* .catch((error) => {
* console.error('Error fetching basic fee:', error);
* });
*
* @category Fee
* @param {ApiPromise} api - The Substrate API instance.
* @param {number} domainId - The ID of the domain.
* @param {XcmMultiAssetIdType} xcmMultiAssetId - The XCM MultiAsset ID of the asset. {@link https://github.com/sygmaprotocol/sygma-substrate-pallets#multiasset | More details}
* @returns {Promise<SubstrateFee>} A promise that resolves to a SubstrateFee object.
* @throws {Error} Unable to retrieve fee from pallet
*/
export const getPercentageFee = async (
api: ApiPromise,
transfer: Transfer<Fungible>,
): Promise<SubstrateFee> => {
const assetId = (transfer.resource as SubstrateResource).xcmMultiAssetId;

const feeStructure = await api.query.sygmaPercentageFeeHandler.assetFeeRate<Option<Tuple>>([
transfer.to.id,
assetId,
]);
if (feeStructure.isNone) {
throw new Error('Error retrieving fee');
}

const [feeRate, min, max] = feeStructure
.unwrap()
.toArray()
.map(val => new BN(val.toString()));

const calculatedFee = new BN(transfer.details.amount).mul(feeRate).div(new BN(10000));

let fee: BN;
if (calculatedFee.lt(min)) {
fee = min;
} else if (calculatedFee.gt(max)) {
fee = max;
} else {
fee = calculatedFee;
}

return {
fee: fee,
type: FeeHandlerType.PERCENTAGE,
};
};
2 changes: 2 additions & 0 deletions packages/sdk/src/chains/Substrate/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * from './getNativeTokenBalance.js';
export * from './depositFns.js';
export * from './listenForEvent.js';
export * from '../types/index.js';
export * from './getFeeHandler.js';
export * from './getPercentageFee.js';
1 change: 0 additions & 1 deletion packages/sdk/src/chains/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ export * as EVM from './EVM/index.js';
* Functions for interacting with substrate pallet.
*
*/
export * from './Substrate/index.js';
export * as Substrate from './Substrate/index.js';
13 changes: 13 additions & 0 deletions packages/sdk/src/localConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,19 @@ export const localConfig: RawConfig = {
},
},
},
{
resourceId: '0x0000000000000000000000000000000000000000000000000000000000000000',
type: ResourceType.FUNGIBLE,
native: true,
decimals: 18,
assetName: 'PHA',
xcmMultiAssetId: {
concrete: {
parents: 0,
interior: 'here',
},
},
},
],
},
],
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type SubstrateResource = BaseResource & {
export enum FeeHandlerType {
DYNAMIC = 'oracle',
BASIC = 'basic',
PERCENTAGE = 'percentage',
}

type AssetTransfer = {
Expand Down
Loading

0 comments on commit 97eadc5

Please sign in to comment.