Skip to content

Commit

Permalink
feat: estimate message fee (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
gidonkatten authored Sep 17, 2024
1 parent fb29c4c commit 1c4d754
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/tender-bobcats-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@folks-finance/xchain-sdk": patch
---

estimate message fee
39 changes: 38 additions & 1 deletion src/chains/evm/hub/modules/folks-hub-gmp.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { FINALITY } from "../../../../common/constants/message.js";
import { getRandomGenericAddress } from "../../../../common/utils/address.js";
import { getRandomBytes } from "../../../../common/utils/bytes.js";
import { GAS_LIMIT_ESTIMATE_INCREASE } from "../../common/constants/contract.js";
import { getEvmSignerAccount } from "../../common/utils/chain.js";
import { buildMessageParams, buildSendTokenExtraArgsWhenRemoving } from "../../common/utils/message.js";
import { getBridgeRouterHubContract } from "../utils/contract.js";

import type { EvmAddress } from "../../../../common/types/address.js";
import type { FolksChainId } from "../../../../common/types/chain.js";
import type { MessageId } from "../../../../common/types/gmp.js";
import type { AdapterType } from "../../../../common/types/message.js";
import type { AdapterType, MessageToSend } from "../../../../common/types/message.js";
import type { MessageReceived } from "../../common/types/gmp.js";
import type { PrepareRetryMessageCall, PrepareReverseMessageCall } from "../../common/types/module.js";
import type { HubChain } from "../types/chain.js";
import type { HubTokenData } from "../types/token.js";
import type { Client, EstimateGasParameters, Hex, WalletClient } from "viem";

export const prepare = {
Expand Down Expand Up @@ -111,3 +117,34 @@ export const write = {
});
},
};

export async function getSendMessageFee(
provider: Client,
toFolksChainId: FolksChainId,
adapterId: AdapterType,
gasLimit: bigint,
hubChain: HubChain,
hubTokenData: HubTokenData,
): Promise<bigint> {
const hubBridgeRouter = getBridgeRouterHubContract(provider, hubChain.bridgeRouterAddress);

// construct return message
const message: MessageToSend = {
params: buildMessageParams({
adapters: {
adapterId,
returnAdapterId: 0 as AdapterType,
},
gasLimit,
}),
sender: hubChain.hubAddress,
destinationChainId: toFolksChainId,
handler: getRandomGenericAddress(),
payload: getRandomBytes(256),
finalityLevel: FINALITY.FINALISED,
extraArgs: buildSendTokenExtraArgsWhenRemoving(getRandomGenericAddress(), hubTokenData.token, 1n),
};

// get return adapter fee
return await hubBridgeRouter.read.getSendFee([message]);
}
48 changes: 46 additions & 2 deletions src/chains/evm/spoke/modules/folks-evm-gmp.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { FINALITY } from "../../../../common/constants/message.js";
import { ChainType } from "../../../../common/types/chain.js";
import { convertFromGenericAddress, convertToGenericAddress } from "../../../../common/utils/address.js";
import {
convertFromGenericAddress,
convertToGenericAddress,
getRandomGenericAddress,
} from "../../../../common/utils/address.js";
import { getRandomBytes } from "../../../../common/utils/bytes.js";
import { getWormholeData } from "../../../../common/utils/gmp.js";
import { GAS_LIMIT_ESTIMATE_INCREASE } from "../../common/constants/contract.js";
import { getEvmSignerAccount } from "../../common/utils/chain.js";
import { getWormholeRelayerContract } from "../../common/utils/contract.js";
import { buildMessageParams, buildSendTokenExtraArgsWhenAdding } from "../../common/utils/message.js";
import { getBridgeRouterSpokeContract } from "../utils/contract.js";

import type { EvmAddress, GenericAddress } from "../../../../common/types/address.js";
import type { FolksChainId, SpokeChain } from "../../../../common/types/chain.js";
import type { MessageId } from "../../../../common/types/gmp.js";
import type { AdapterType } from "../../../../common/types/message.js";
import type { AdapterType, MessageToSend } from "../../../../common/types/message.js";
import type { SpokeTokenData } from "../../../../common/types/token.js";
import type { MessageReceived } from "../../common/types/gmp.js";
import type {
PrepareResendWormholeMessageCall,
PrepareRetryMessageCall,
PrepareReverseMessageCall,
} from "../../common/types/module.js";
import type { HubChain } from "../../hub/types/chain.js";
import type { Client, EstimateGasParameters, Hex, WalletClient } from "viem";

export const prepare = {
Expand Down Expand Up @@ -201,3 +210,38 @@ export const write = {
);
},
};

export async function getSendMessageFee(
provider: Client,
adapterId: AdapterType,
receiverValue: bigint,
gasLimit: bigint,
hubChain: HubChain,
spokeChain: SpokeChain,
spokeTokenData?: SpokeTokenData,
): Promise<bigint> {
const bridgeRouter = getBridgeRouterSpokeContract(provider, spokeChain.bridgeRouterAddress);

// construct return message
const message: MessageToSend = {
params: buildMessageParams({
adapters: {
adapterId,
returnAdapterId: 0 as AdapterType,
},
gasLimit,
receiverValue,
}),
sender: spokeChain.spokeCommonAddress,
destinationChainId: hubChain.folksChainId,
handler: getRandomGenericAddress(),
payload: getRandomBytes(256),
finalityLevel: FINALITY.FINALISED,
extraArgs: spokeTokenData
? buildSendTokenExtraArgsWhenAdding(getRandomGenericAddress(), spokeTokenData.token, 1n)
: "0x",
};

// get return adapter fee
return await bridgeRouter.read.getSendFee([message]);
}
13 changes: 13 additions & 0 deletions src/common/utils/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ export function assertAdapterSupportsTokenMessage(folksChainId: FolksChainId, ad
throw Error(`Adapter ${adapterId} does not support token message for folksChainId: ${folksChainId}`);
}

export function doesAdapterSupportReceiverValue(folksChainId: FolksChainId, adapterId: AdapterType): boolean {
const isHub = isHubChain(folksChainId, FolksCore.getSelectedNetwork());
return (
(isHub && adapterId === AdapterType.HUB) ||
(!isHub && (adapterId === AdapterType.WORMHOLE_DATA || adapterId === AdapterType.WORMHOLE_CCTP))
);
}

export function assertAdapterSupportsReceiverValue(folksChainId: FolksChainId, adapterId: AdapterType): void {
if (!doesAdapterSupportReceiverValue(folksChainId, adapterId))
throw Error(`Adapter ${adapterId} does not support receiver value for folksChainId: ${folksChainId}`);
}

function getAdapterIds(messageAdapterParams: MessageAdapterParams) {
const { sourceFolksChainId, network, messageAdapterParamType } = messageAdapterParams;
if (isHubChain(sourceFolksChainId, network)) return HUB_ADAPTERS;
Expand Down
90 changes: 87 additions & 3 deletions src/xchain/modules/folks-gmp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { getEvmSignerAddress } from "../../chains/evm/common/utils/chain.js";
import { encodeRetryMessageExtraArgs, encodeReverseMessageExtraArgs } from "../../chains/evm/common/utils/gmp.js";
import { FolksHubGmp } from "../../chains/evm/hub/modules/index.js";
import { getHubChain } from "../../chains/evm/hub/utils/chain.js";
import { getHubChain, getHubTokenData } from "../../chains/evm/hub/utils/chain.js";
import { FolksEvmGmp } from "../../chains/evm/spoke/modules/index.js";
import { assertHubChainSelected, getSpokeChain } from "../../common/utils/chain.js";
import {
assertAdapterSupportsDataMessage,
assertAdapterSupportsReceiverValue,
assertAdapterSupportsTokenMessage,
} from "../../common/utils/adapter.js";
import { assertHubChainSelected, getSpokeChain, getSpokeTokenData } from "../../common/utils/chain.js";
import { isCircleToken } from "../../common/utils/token.js";
import { FolksCore } from "../core/folks-core.js";

import type {
Expand All @@ -14,12 +20,13 @@ import type {
import type { GenericAddress } from "../../common/types/address.js";
import type { ChainType, FolksChainId } from "../../common/types/chain.js";
import type { MessageId } from "../../common/types/gmp.js";
import type { AdapterType } from "../../common/types/message.js";
import type { AdapterType, MessageAdapters } from "../../common/types/message.js";
import type {
PrepareRetryMessageCall,
PrepareReverseMessageCall,
PrepareResendWormholeMessageCall,
} from "../../common/types/module.js";
import type { FolksTokenId } from "../../common/types/token.js";

export const prepare = {
async retryMessage(
Expand Down Expand Up @@ -176,3 +183,80 @@ export const write = {
);
},
};

export const util = {
async spokeToHubMessageFee(
adapterId: AdapterType,
fromFolksChainId: FolksChainId,
sendFolksTokenId?: FolksTokenId,
receiverValue = 0n,
gasLimit = 1_500_000n,
): Promise<bigint> {
const network = FolksCore.getSelectedNetwork();
const spokeChain = getSpokeChain(fromFolksChainId, network);
const hubChain = getHubChain(network);

// check receiver value is support
if (receiverValue > 0n) assertAdapterSupportsReceiverValue(fromFolksChainId, adapterId);

// check adapter id is supported
sendFolksTokenId && isCircleToken(sendFolksTokenId)
? assertAdapterSupportsTokenMessage(fromFolksChainId, adapterId)
: assertAdapterSupportsDataMessage(fromFolksChainId, adapterId);
const spokeTokenData = sendFolksTokenId ? getSpokeTokenData(spokeChain, sendFolksTokenId) : undefined;

return FolksEvmGmp.getSendMessageFee(
FolksCore.getProvider<ChainType.EVM>(fromFolksChainId),
adapterId,
receiverValue,
gasLimit,
hubChain,
spokeChain,
spokeTokenData,
);
},

async hubToSpokeMessageFee(
adapterId: AdapterType,
toFolksChainId: FolksChainId,
receiveFolksTokenId: FolksTokenId,
gasLimit = 500_000n,
): Promise<bigint> {
const network = FolksCore.getSelectedNetwork();
const hubChain = getHubChain(network);

// check adapter id is supported
isCircleToken(receiveFolksTokenId)
? assertAdapterSupportsTokenMessage(toFolksChainId, adapterId)
: assertAdapterSupportsDataMessage(toFolksChainId, adapterId);

const hubTokenData = getHubTokenData(receiveFolksTokenId, network);

return await FolksHubGmp.getSendMessageFee(
FolksCore.getHubProvider(),
toFolksChainId,
adapterId,
gasLimit,
hubChain,
hubTokenData,
);
},

async roundTripMessageFee(
adapters: MessageAdapters,
startFolksChainId: FolksChainId,
endFolksChainId: FolksChainId,
receiveFolksTokenId: FolksTokenId,
gasLimit = 1_500_000n,
returnGasLimit = 500_000n,
): Promise<bigint> {
const { adapterId, returnAdapterId } = adapters;
const receiverValue = await this.hubToSpokeMessageFee(
returnAdapterId,
endFolksChainId,
receiveFolksTokenId,
returnGasLimit,
);
return await this.spokeToHubMessageFee(adapterId, startFolksChainId, undefined, receiverValue, gasLimit);
},
};

0 comments on commit 1c4d754

Please sign in to comment.