From 25a065dfc5cc7e677b4bb769741cb28abedf61d3 Mon Sep 17 00:00:00 2001 From: Ken Vu Date: Thu, 5 Oct 2023 16:49:52 +1300 Subject: [PATCH 1/4] Update `use-feeProxy` example to use a similar logging output --- .../use-feeProxy/src/callBatchAll.ts | 23 ++++- .../substrate/use-feeProxy/src/callEVMCall.ts | 96 ++++++++++++------ .../use-feeProxy/src/callProxyExtrinsic.ts | 45 ++++++--- .../use-feeProxy/src/callSystemRemark.ts | 23 ++++- .../use-futurepass/src/proxyEVMCall.ts | 99 +++++++++++++++++++ 5 files changed, 239 insertions(+), 47 deletions(-) create mode 100644 examples/substrate/use-futurepass/src/proxyEVMCall.ts diff --git a/examples/substrate/use-feeProxy/src/callBatchAll.ts b/examples/substrate/use-feeProxy/src/callBatchAll.ts index 65b877f..052cc9e 100644 --- a/examples/substrate/use-feeProxy/src/callBatchAll.ts +++ b/examples/substrate/use-feeProxy/src/callBatchAll.ts @@ -21,6 +21,16 @@ withChainApi("porcini", async (api, caller, logger) => { const transferToBobCall = api.tx.assets.transfer(ASTO_ASSET_ID, BOB, oneASTO.toString()); const transferToCharlieCall = api.tx.assets.transfer(ASTO_ASSET_ID, CHARLIE, oneASTO.toString()); + logger.info( + { + parameters: [ + transferToAliceCall.toJSON(), + transferToBobCall.toJSON(), + transferToCharlieCall.toJSON(), + ], + }, + `create a "system.remarkWithEvent"` + ); const batchAllCall = api.tx.utility.batchAll([ transferToAliceCall, transferToBobCall, @@ -47,13 +57,24 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + + logger.info( + { + parameters: { + paymentAsset: ASTO_ASSET_ID, + maxPayment, + call: batchAllCall.toJSON(), + }, + }, + `create a "feeProxy.callWithFeePreferences"` + ); const feeProxyCall = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, maxPayment, batchAllCall ); - logger.info(`dispatch a feeProxy call with maxPayment="${maxPayment}"`); + logger.info(`dispatch extrinsic as caller="${caller.address}"`); const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger }); const [proxyEvent, batchEvent, aliceTransferEvent, bobTransferEvent, charlieTransferEvent] = filterExtrinsicEvents(result.events, [ diff --git a/examples/substrate/use-feeProxy/src/callEVMCall.ts b/examples/substrate/use-feeProxy/src/callEVMCall.ts index ccfd7b0..969e0f7 100644 --- a/examples/substrate/use-feeProxy/src/callEVMCall.ts +++ b/examples/substrate/use-feeProxy/src/callEVMCall.ts @@ -1,9 +1,11 @@ +import { ApiPromise } from "@polkadot/api"; import { ERC20_ABI } from "@therootnetwork/evm"; import { ALICE } from "@trne/utils/accounts"; import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents"; import { formatEventData } from "@trne/utils/formatEventData"; import { getERC20Contract } from "@trne/utils/getERC20Contract"; import { getFeeProxyPricePair } from "@trne/utils/getFeeProxyPricePair"; +import { Logger } from "@trne/utils/getLogger"; import { ASTO_ASSET_ID, SYLO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets"; import { sendExtrinsic } from "@trne/utils/sendExtrinsic"; import { withChainApi } from "@trne/utils/withChainApi"; @@ -15,37 +17,13 @@ interface AmountsIn { } /** - * Use `feeProxy.callWithFeePreferencs` to trigger `evm.call` call + * Use `feeProxy.callWithFeePreferencs` to trigger `evm.call` * - * Assumes the caller has some ASTO balance. + * Assumes the caller has some ASTO balance to pay for gas and some SYLO balance to demonstrate + * the transfer */ withChainApi("porcini", async (api, caller, logger) => { - // setup the evmCall - const { provider, wallet } = await provideEthersProvider("porcini"); - const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet); - const transferAmount = utils.parseUnits("1.0", 18); // 1 SYLO - const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [ - ALICE, - transferAmount, - ]); - const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount); - const { maxFeePerGas, estimateGasCost } = await getFeeProxyPricePair( - provider, - transferEstimate, - ASTO_ASSET_ID, - 0.05 - ); - const evmCall = api.tx.evm.call( - caller.address, - transferToken.address, - transferInput, - 0, - transferEstimate.toString(), - maxFeePerGas.toString(), - 0, - null, - [] - ); + const { call: evmCall, estimateGasCost } = await createEVMCall(caller.address, api, logger); // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( @@ -60,8 +38,6 @@ withChainApi("porcini", async (api, caller, logger) => { .add(estimateGasCost) .toString(); - logger.info(`prepare a "evm.call" call with estimatedFee="${estimatedFee}"`); - // query the the `dex` to determine the `maxPayment` you are willing to pay const { Ok: [amountIn], @@ -72,8 +48,20 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + + logger.info( + { + parameters: { + paymentAsset: ASTO_ASSET_ID, + maxPayment, + call: evmCall.toJSON(), + }, + }, + `create a "feeProxy.callWithFeePreferences"` + ); const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(ASTO_ASSET_ID, maxPayment, evmCall); - logger.info(`dispatch a feeProxy call with maxPayment="${maxPayment}"`); + + logger.info(`dispatch extrinsic as caller="${caller.address}"`); const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger }); const [proxyEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [ @@ -93,3 +81,49 @@ withChainApi("porcini", async (api, caller, logger) => { "receive result" ); }); + +export async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) { + // setup the actual EVM transaction, as interface + const { provider, wallet } = await provideEthersProvider("porcini"); + const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet); + const transferAmount = utils.parseUnits("0.1", 18); // 0.1 SYLO + const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [ + ALICE, + transferAmount, + ]); + const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount); + const { maxFeePerGas, estimateGasCost } = await getFeeProxyPricePair( + provider, + transferEstimate, + ASTO_ASSET_ID, + 0.05 + ); + + logger.info( + { + parameters: { + source: caller, + target: transferToken.address, + input: transferInput, + gasLimit: transferEstimate.toString(), + maxFeePerGas: maxFeePerGas.toString(), + }, + }, + `create an "emv.call"` + ); + + return { + call: api.tx.evm.call( + caller, + transferToken.address, + transferInput, + 0, + transferEstimate.toString(), + maxFeePerGas.toString(), + 0, + null, + [] + ), + estimateGasCost, + }; +} diff --git a/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts b/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts index 10030b7..060dac9 100644 --- a/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts +++ b/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts @@ -13,7 +13,7 @@ interface AmountsIn { * Use `feeProxy.callWithFeePreferences` to trigger `system.remarkWithEvent` call via * `futurepass.proxyExtrinsic`, and have Futurepass account pays gas in ASTO. * - * Assumes the caller has a valid Futurepass account and some ASTO balance in that account. + * Assumes the caller has a valid Futurepass account and some ASTO balance to pay for gas. */ withChainApi("porcini", async (api, caller, logger) => { const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap(); @@ -32,6 +32,15 @@ withChainApi("porcini", async (api, caller, logger) => { // can be any extrinsic, using `system.remarkWithEvent` for simplicity const remarkCall = api.tx.system.remarkWithEvent("Hello World"); // wrap `remarkCall` with `proxyCall`, effetively request Futurepass account to pay for gas + logger.info( + { + parameters: { + futurepass: fpAccount, + call: remarkCall.toJSON(), + }, + }, + `create a "futurepass.proxyExtrinsic"` + ); const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, remarkCall); // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( @@ -42,8 +51,6 @@ withChainApi("porcini", async (api, caller, logger) => { const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address); const estimatedFee = paymentInfo.partialFee.toString(); - console.log(estimatedFee); - // query the the `dex` to determine the `maxPayment` you are willing to pay const { Ok: [amountIn], @@ -54,13 +61,24 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + + logger.info( + { + parameters: { + paymentAsset: ASTO_ASSET_ID, + maxPayment, + call: futurepassCall.toJSON(), + }, + }, + `create a "feeProxy.callWithFeePreferences"` + ); const feeProxyCall = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, maxPayment, futurepassCall ); - logger.info(`dispatch a feeProxy call with maxPayment="${maxPayment}"`); + logger.info(`dispatch extrinsic as caller="${caller.address}"`); const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger }); const [proxyEvent, futurepassEvent, remarkEvent] = filterExtrinsicEvents(result.events, [ "FeeProxy.CallWithFeePreferences", @@ -68,13 +86,16 @@ withChainApi("porcini", async (api, caller, logger) => { "System.Remarked", ]); - logger.info({ - result: { - extrinsicId, - blockNumber: result.blockNumber, - proxyEvent: formatEventData(proxyEvent.event), - futurepassEvent: formatEventData(futurepassEvent.event), - remarkEvent: formatEventData(remarkEvent.event), + logger.info( + { + result: { + extrinsicId, + blockNumber: result.blockNumber, + proxyEvent: formatEventData(proxyEvent.event), + futurepassEvent: formatEventData(futurepassEvent.event), + remarkEvent: formatEventData(remarkEvent.event), + }, }, - }); + "receive result" + ); }); diff --git a/examples/substrate/use-feeProxy/src/callSystemRemark.ts b/examples/substrate/use-feeProxy/src/callSystemRemark.ts index bd6803b..912d605 100644 --- a/examples/substrate/use-feeProxy/src/callSystemRemark.ts +++ b/examples/substrate/use-feeProxy/src/callSystemRemark.ts @@ -16,6 +16,14 @@ interface AmountsIn { */ withChainApi("porcini", async (api, caller, logger) => { // can be any extrinsic, using `system.remarkWithEvent` for simplicity + logger.info( + { + parameters: { + remark: "Hello World", + }, + }, + `create a "system.remarkWithEvent"` + ); const remarkCall = api.tx.system.remarkWithEvent("Hello World"); // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( @@ -26,8 +34,6 @@ withChainApi("porcini", async (api, caller, logger) => { const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address); const estimatedFee = paymentInfo.partialFee.toString(); - logger.info(`prepare a "system.remark" call with estimatedFee="${estimatedFee}"`); - // query the the `dex` to determine the `maxPayment` you are willing to pay const { Ok: [amountIn], @@ -38,13 +44,24 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + + logger.info( + { + parameters: { + paymentAsset: ASTO_ASSET_ID, + maxPayment, + call: remarkCall.toJSON(), + }, + }, + `create a "feeProxy.callWithFeePreferences"` + ); const feeProxyCall = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, maxPayment, remarkCall ); - logger.info(`dispatch a feeProxy call with maxPayment="${maxPayment}"`); + logger.info(`dispatch extrinsic as caller="${caller.address}"`); const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger }); const [proxyEvent, remarkEvent] = filterExtrinsicEvents(result.events, [ "FeeProxy.CallWithFeePreferences", diff --git a/examples/substrate/use-futurepass/src/proxyEVMCall.ts b/examples/substrate/use-futurepass/src/proxyEVMCall.ts new file mode 100644 index 0000000..58aece1 --- /dev/null +++ b/examples/substrate/use-futurepass/src/proxyEVMCall.ts @@ -0,0 +1,99 @@ +import { ApiPromise } from "@polkadot/api"; +import { ERC20_ABI } from "@therootnetwork/evm"; +import { ALICE } from "@trne/utils/accounts"; +import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents"; +import { formatEventData } from "@trne/utils/formatEventData"; +import { getERC20Contract } from "@trne/utils/getERC20Contract"; +import { Logger } from "@trne/utils/getLogger"; +import { SYLO_ASSET_ID } from "@trne/utils/porcini-assets"; +import { sendExtrinsic } from "@trne/utils/sendExtrinsic"; +import { withChainApi } from "@trne/utils/withChainApi"; +import { provideEthersProvider } from "@trne/utils/withEthersProvider"; +import { utils } from "ethers"; +import assert from "node:assert"; + +/** + * Use `futurepass.proxyExtrinsic` to trigger `evm.call` + * + * Assumes the caller has a Futurepass account, some XRP balance to pay for gas + * and some SYLO balance to demonstrate the transfer + */ +withChainApi("porcini", async (api, caller, logger) => { + const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap(); + assert(fpAccount); + + const evmCall = await createEVMCall(fpAccount.toString(), api, logger); + + logger.info( + { + parameters: { + futurepass: fpAccount, + call: evmCall.toJSON(), + }, + }, + `create a "futurepass.proxyExtrinsic"` + ); + const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, evmCall); + + logger.info({ extrinsic: futurepassCall.toJSON() }, `dispatch "futurepass.proxyExtrinsic"`); + + const { result, extrinsicId } = await sendExtrinsic(futurepassCall, caller, { log: logger }); + + const [futurepassEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [ + "Futurepass.ProxyExecuted", + "Evm.Log", + { name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } }, + ]); + + logger.info( + { + result: { + extrinsicId, + blockNumber: result.blockNumber, + futurepassEvent: formatEventData(futurepassEvent.event), + evmLogEvent: formatEventData(evmLogEvent.event), + aliceTransferEvent: formatEventData(aliceTransferEvent.event), + }, + }, + "dispatch result" + ); +}); + +export async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) { + // setup the actual EVM transaction, as interface + const { provider, wallet } = await provideEthersProvider("porcini"); + const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet); + const transferAmount = utils.parseUnits("0.1", 18); // 0.1 SYLO + const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [ + ALICE, + transferAmount, + ]); + const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount); + const { lastBaseFeePerGas: maxFeePerGas } = await provider.getFeeData(); + assert(maxFeePerGas); + + logger.info( + { + parameters: { + source: caller, + target: transferToken.address, + input: transferInput, + gasLimit: transferEstimate.toString(), + maxFeePerGas: maxFeePerGas.toString(), + }, + }, + `create an "emv.call"` + ); + + return api.tx.evm.call( + caller, + transferToken.address, + transferInput, + 0, + transferEstimate.toString(), + maxFeePerGas.toString(), + 0, + null, + [] + ); +} From b683eb2bf0d5e4ed6ece1bc80d0c055127b295b9 Mon Sep 17 00:00:00 2001 From: Ken Vu Date: Thu, 5 Oct 2023 17:08:50 +1300 Subject: [PATCH 2/4] Add `src/callProxyExtrinsicEVMCall.ts` --- examples/substrate/use-feeProxy/README.md | 3 + examples/substrate/use-feeProxy/package.json | 3 +- .../substrate/use-feeProxy/src/callEVMCall.ts | 10 +- .../use-feeProxy/src/callProxyExtrinsic.ts | 2 +- .../src/callProxyExtrinsicEVMCall.ts | 163 ++++++++++++++++++ 5 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts diff --git a/examples/substrate/use-feeProxy/README.md b/examples/substrate/use-feeProxy/README.md index 23254b2..9771fbf 100644 --- a/examples/substrate/use-feeProxy/README.md +++ b/examples/substrate/use-feeProxy/README.md @@ -28,4 +28,7 @@ pnpm call:callBatchAll # `feeProxy.callWithFeePreferences` that wraps around `evm.call` pnpm call:callEVMCall +# `feeProxy.callWithFeePreferences` that wraps around `futurepass.proxyExtrinsic` then `emv.call` +pnpm call:callProxyExtrinsicEVMCall + ``` diff --git a/examples/substrate/use-feeProxy/package.json b/examples/substrate/use-feeProxy/package.json index 67b03eb..ed50513 100644 --- a/examples/substrate/use-feeProxy/package.json +++ b/examples/substrate/use-feeProxy/package.json @@ -4,6 +4,7 @@ "call:callSystemRemark": "pnpm call src/callSystemRemark.ts", "call:callProxyExtrinsic": "pnpm call src/callProxyExtrinsic.ts", "call:callBatchAll": "pnpm call src/callBatchAll.ts", - "call:callEVMCall": "pnpm call src/callEVMCall.ts" + "call:callEVMCall": "pnpm call src/callEVMCall.ts", + "call:callProxyExtrinsicEVMCall": "pnpm call src/callProxyExtrinsicEVMCall.ts" } } diff --git a/examples/substrate/use-feeProxy/src/callEVMCall.ts b/examples/substrate/use-feeProxy/src/callEVMCall.ts index 969e0f7..e56333b 100644 --- a/examples/substrate/use-feeProxy/src/callEVMCall.ts +++ b/examples/substrate/use-feeProxy/src/callEVMCall.ts @@ -31,10 +31,10 @@ withChainApi("porcini", async (api, caller, logger) => { 0, evmCall ); - const feeProxyPaymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address); + const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address); // we need to add the actual estimate cost for the EVM layer call as part of the estimation - const estimatedFee = BigNumber.from(feeProxyPaymentInfo.partialFee.toString()) + const estimatedFee = BigNumber.from(paymentInfo.partialFee.toString()) .add(estimateGasCost) .toString(); @@ -64,8 +64,9 @@ withChainApi("porcini", async (api, caller, logger) => { logger.info(`dispatch extrinsic as caller="${caller.address}"`); const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger }); - const [proxyEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [ + const [proxyEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [ "FeeProxy.CallWithFeePreferences", + "Evm.Log", { name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } }, ]); @@ -75,6 +76,7 @@ withChainApi("porcini", async (api, caller, logger) => { extrinsicId, blockNumber: result.blockNumber, proxyEvent: formatEventData(proxyEvent.event), + evmLogEvent: formatEventData(evmLogEvent.event), aliceTransferEvent: formatEventData(aliceTransferEvent.event), }, }, @@ -82,7 +84,7 @@ withChainApi("porcini", async (api, caller, logger) => { ); }); -export async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) { +async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) { // setup the actual EVM transaction, as interface const { provider, wallet } = await provideEthersProvider("porcini"); const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet); diff --git a/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts b/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts index 060dac9..73e4f7a 100644 --- a/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts +++ b/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts @@ -3,7 +3,7 @@ import { formatEventData } from "@trne/utils/formatEventData"; import { ASTO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets"; import { sendExtrinsic } from "@trne/utils/sendExtrinsic"; import { withChainApi } from "@trne/utils/withChainApi"; -import assert from "assert"; +import assert from "node:assert"; interface AmountsIn { Ok: [number, number]; diff --git a/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts b/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts new file mode 100644 index 0000000..8e355eb --- /dev/null +++ b/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts @@ -0,0 +1,163 @@ +import { ApiPromise } from "@polkadot/api"; +import { ERC20_ABI } from "@therootnetwork/evm"; +import { ALICE } from "@trne/utils/accounts"; +import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents"; +import { formatEventData } from "@trne/utils/formatEventData"; +import { getERC20Contract } from "@trne/utils/getERC20Contract"; +import { getFeeProxyPricePair } from "@trne/utils/getFeeProxyPricePair"; +import { Logger } from "@trne/utils/getLogger"; +import { ASTO_ASSET_ID, SYLO_ASSET_ID, XRP_ASSET_ID } from "@trne/utils/porcini-assets"; +import { sendExtrinsic } from "@trne/utils/sendExtrinsic"; +import { withChainApi } from "@trne/utils/withChainApi"; +import { provideEthersProvider } from "@trne/utils/withEthersProvider"; +import { BigNumber, utils } from "ethers"; +import assert from "node:assert"; + +interface AmountsIn { + Ok: [number, number]; +} + +/** + * Use `feeProxy.callWithFeePreferences` to trigger `emv.call` call via + * `futurepass.proxyExtrinsic`, and have Futurepass account pays gas in ASTO. + * + * Assumes the caller has a valid Futurepass account, some ASTO balance to pay for gas and + * some SYLO balance to demonstrate the transfer + */ +withChainApi("porcini", async (api, caller, logger) => { + const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap(); + logger.info( + { + futurepass: { + holder: caller.address, + account: fpAccount.toString(), + }, + }, + "futurepass details" + ); + assert(fpAccount); + + // can be any extrinsic, using `system.remarkWithEvent` for simplicity + const { call: evmCall, estimateGasCost } = await createEVMCall(fpAccount.toString(), api, logger); + // wrap `remarkCall` with `proxyCall`, effetively request Futurepass account to pay for gas + logger.info( + { + parameters: { + futurepass: fpAccount, + call: evmCall.toJSON(), + }, + }, + `create a "futurepass.proxyExtrinsic"` + ); + const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, evmCall); + // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation + const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( + ASTO_ASSET_ID, + 0, + futurepassCall + ); + const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address); + // we need to add the actual estimate cost for the EVM layer call as part of the estimation + const estimatedFee = BigNumber.from(paymentInfo.partialFee.toString()) + .add(estimateGasCost) + .toString(); + + // query the the `dex` to determine the `maxPayment` you are willing to pay + const { + Ok: [amountIn], + } = (await api.rpc.dex.getAmountsIn(estimatedFee, [ + ASTO_ASSET_ID, + XRP_ASSET_ID, + ])) as unknown as AmountsIn; + + // allow a buffer to avoid slippage, 5% + const maxPayment = Number(amountIn * 1.05).toFixed(); + + logger.info( + { + parameters: { + paymentAsset: ASTO_ASSET_ID, + maxPayment, + call: futurepassCall.toJSON(), + }, + }, + `create a "feeProxy.callWithFeePreferences"` + ); + const feeProxyCall = api.tx.feeProxy.callWithFeePreferences( + ASTO_ASSET_ID, + maxPayment, + futurepassCall + ); + + logger.info(`dispatch extrinsic as caller="${caller.address}"`); + const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger }); + const [proxyEvent, futurepassEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents( + result.events, + [ + "FeeProxy.CallWithFeePreferences", + "Futurepass.ProxyExecuted", + "Evm.Log", + { name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } }, + ] + ); + + logger.info( + { + result: { + extrinsicId, + blockNumber: result.blockNumber, + proxyEvent: formatEventData(proxyEvent.event), + futurepassEvent: formatEventData(futurepassEvent.event), + evmLogEvent: formatEventData(evmLogEvent.event), + aliceTransferEvent: formatEventData(aliceTransferEvent.event), + }, + }, + "receive result" + ); +}); + +async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) { + // setup the actual EVM transaction, as interface + const { provider, wallet } = await provideEthersProvider("porcini"); + const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet); + const transferAmount = utils.parseUnits("0.1", 18); // 0.1 SYLO + const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [ + ALICE, + transferAmount, + ]); + const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount); + const { maxFeePerGas, estimateGasCost } = await getFeeProxyPricePair( + provider, + transferEstimate, + ASTO_ASSET_ID, + 0.05 + ); + + logger.info( + { + parameters: { + source: caller, + target: transferToken.address, + input: transferInput, + gasLimit: transferEstimate.toString(), + maxFeePerGas: maxFeePerGas.toString(), + }, + }, + `create an "emv.call"` + ); + + return { + call: api.tx.evm.call( + caller, + transferToken.address, + transferInput, + 0, + transferEstimate.toString(), + maxFeePerGas.toString(), + 0, + null, + [] + ), + estimateGasCost, + }; +} From dc4de6aa6cac11d6e93e73e48301114d5e033b2e Mon Sep 17 00:00:00 2001 From: Ken Vu Date: Thu, 5 Oct 2023 17:20:53 +1300 Subject: [PATCH 3/4] Add comment headers to make the code a bit clearer --- examples/substrate/use-feeProxy/src/callBatchAll.ts | 9 +++++++++ examples/substrate/use-feeProxy/src/callEVMCall.ts | 9 +++++++++ .../substrate/use-feeProxy/src/callProxyExtrinsic.ts | 11 ++++++++++- .../use-feeProxy/src/callProxyExtrinsicEVMCall.ts | 12 ++++++++++-- .../substrate/use-feeProxy/src/callSystemRemark.ts | 10 ++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/examples/substrate/use-feeProxy/src/callBatchAll.ts b/examples/substrate/use-feeProxy/src/callBatchAll.ts index 052cc9e..1a66027 100644 --- a/examples/substrate/use-feeProxy/src/callBatchAll.ts +++ b/examples/substrate/use-feeProxy/src/callBatchAll.ts @@ -16,6 +16,9 @@ interface AmountsIn { * Assumes the caller has some ASTO balance. */ withChainApi("porcini", async (api, caller, logger) => { + /** + * 1. Create `utility.batchAll` call that batches 3 transfer calls to ALICE, BOB & CHARLIE + */ const oneASTO = 1 * Math.pow(10, 18); // 1 ASTO in `wei` unit const transferToAliceCall = api.tx.assets.transfer(ASTO_ASSET_ID, ALICE, oneASTO.toString()); const transferToBobCall = api.tx.assets.transfer(ASTO_ASSET_ID, BOB, oneASTO.toString()); @@ -37,6 +40,9 @@ withChainApi("porcini", async (api, caller, logger) => { transferToCharlieCall, ]); + /** + * 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote + */ // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, @@ -58,6 +64,9 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + /** + * 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic + */ logger.info( { parameters: { diff --git a/examples/substrate/use-feeProxy/src/callEVMCall.ts b/examples/substrate/use-feeProxy/src/callEVMCall.ts index e56333b..6642305 100644 --- a/examples/substrate/use-feeProxy/src/callEVMCall.ts +++ b/examples/substrate/use-feeProxy/src/callEVMCall.ts @@ -23,8 +23,14 @@ interface AmountsIn { * the transfer */ withChainApi("porcini", async (api, caller, logger) => { + /** + * 1. Create `emv.call` call + */ const { call: evmCall, estimateGasCost } = await createEVMCall(caller.address, api, logger); + /** + * 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote + */ // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, @@ -49,6 +55,9 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + /** + * 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic + */ logger.info( { parameters: { diff --git a/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts b/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts index 73e4f7a..36b0d62 100644 --- a/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts +++ b/examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts @@ -16,6 +16,9 @@ interface AmountsIn { * Assumes the caller has a valid Futurepass account and some ASTO balance to pay for gas. */ withChainApi("porcini", async (api, caller, logger) => { + /** + * 1. Create `futurepass.proxyExtrinsic` call that wraps around `system.remarkWithEvent` call + */ const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap(); logger.info( { @@ -31,7 +34,6 @@ withChainApi("porcini", async (api, caller, logger) => { // can be any extrinsic, using `system.remarkWithEvent` for simplicity const remarkCall = api.tx.system.remarkWithEvent("Hello World"); - // wrap `remarkCall` with `proxyCall`, effetively request Futurepass account to pay for gas logger.info( { parameters: { @@ -42,6 +44,10 @@ withChainApi("porcini", async (api, caller, logger) => { `create a "futurepass.proxyExtrinsic"` ); const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, remarkCall); + + /** + * 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote + */ // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, @@ -62,6 +68,9 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + /** + * 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic + */ logger.info( { parameters: { diff --git a/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts b/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts index 8e355eb..29aee31 100644 --- a/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts +++ b/examples/substrate/use-feeProxy/src/callProxyExtrinsicEVMCall.ts @@ -25,6 +25,9 @@ interface AmountsIn { * some SYLO balance to demonstrate the transfer */ withChainApi("porcini", async (api, caller, logger) => { + /** + * 1. Create `futurepass.proxyExtrinsic` call that wraps around `evm.call` + */ const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap(); logger.info( { @@ -37,9 +40,7 @@ withChainApi("porcini", async (api, caller, logger) => { ); assert(fpAccount); - // can be any extrinsic, using `system.remarkWithEvent` for simplicity const { call: evmCall, estimateGasCost } = await createEVMCall(fpAccount.toString(), api, logger); - // wrap `remarkCall` with `proxyCall`, effetively request Futurepass account to pay for gas logger.info( { parameters: { @@ -50,6 +51,10 @@ withChainApi("porcini", async (api, caller, logger) => { `create a "futurepass.proxyExtrinsic"` ); const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, evmCall); + + /** + * 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote + */ // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, @@ -73,6 +78,9 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + /** + * 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic + */ logger.info( { parameters: { diff --git a/examples/substrate/use-feeProxy/src/callSystemRemark.ts b/examples/substrate/use-feeProxy/src/callSystemRemark.ts index 912d605..8e1b825 100644 --- a/examples/substrate/use-feeProxy/src/callSystemRemark.ts +++ b/examples/substrate/use-feeProxy/src/callSystemRemark.ts @@ -15,6 +15,9 @@ interface AmountsIn { * Assumes the caller has some ASTO balance. */ withChainApi("porcini", async (api, caller, logger) => { + /** + * 1. Create `system.remarkWithEvent` call + */ // can be any extrinsic, using `system.remarkWithEvent` for simplicity logger.info( { @@ -25,6 +28,10 @@ withChainApi("porcini", async (api, caller, logger) => { `create a "system.remarkWithEvent"` ); const remarkCall = api.tx.system.remarkWithEvent("Hello World"); + + /** + * 2. Determine the `maxPayment` in ASTO by estimate the gas cost and use `dex` to get a quote + */ // we need a dummy feeProxy call (with maxPayment=0) to do a proper fee estimation const feeProxyCallForEstimation = api.tx.feeProxy.callWithFeePreferences( ASTO_ASSET_ID, @@ -45,6 +52,9 @@ withChainApi("porcini", async (api, caller, logger) => { // allow a buffer to avoid slippage, 5% const maxPayment = Number(amountIn * 1.05).toFixed(); + /** + * 3. Create and dispatch `feeProxy.callWithFeePreferences` extrinsic + */ logger.info( { parameters: { From 29475ce5c5baeb600fa251ea16157175d8d0f633 Mon Sep 17 00:00:00 2001 From: Ken Vu Date: Thu, 5 Oct 2023 20:24:42 +1300 Subject: [PATCH 4/4] Remove the irrelevant code --- .../use-futurepass/src/proxyEVMCall.ts | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 examples/substrate/use-futurepass/src/proxyEVMCall.ts diff --git a/examples/substrate/use-futurepass/src/proxyEVMCall.ts b/examples/substrate/use-futurepass/src/proxyEVMCall.ts deleted file mode 100644 index 58aece1..0000000 --- a/examples/substrate/use-futurepass/src/proxyEVMCall.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ApiPromise } from "@polkadot/api"; -import { ERC20_ABI } from "@therootnetwork/evm"; -import { ALICE } from "@trne/utils/accounts"; -import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents"; -import { formatEventData } from "@trne/utils/formatEventData"; -import { getERC20Contract } from "@trne/utils/getERC20Contract"; -import { Logger } from "@trne/utils/getLogger"; -import { SYLO_ASSET_ID } from "@trne/utils/porcini-assets"; -import { sendExtrinsic } from "@trne/utils/sendExtrinsic"; -import { withChainApi } from "@trne/utils/withChainApi"; -import { provideEthersProvider } from "@trne/utils/withEthersProvider"; -import { utils } from "ethers"; -import assert from "node:assert"; - -/** - * Use `futurepass.proxyExtrinsic` to trigger `evm.call` - * - * Assumes the caller has a Futurepass account, some XRP balance to pay for gas - * and some SYLO balance to demonstrate the transfer - */ -withChainApi("porcini", async (api, caller, logger) => { - const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap(); - assert(fpAccount); - - const evmCall = await createEVMCall(fpAccount.toString(), api, logger); - - logger.info( - { - parameters: { - futurepass: fpAccount, - call: evmCall.toJSON(), - }, - }, - `create a "futurepass.proxyExtrinsic"` - ); - const futurepassCall = api.tx.futurepass.proxyExtrinsic(fpAccount, evmCall); - - logger.info({ extrinsic: futurepassCall.toJSON() }, `dispatch "futurepass.proxyExtrinsic"`); - - const { result, extrinsicId } = await sendExtrinsic(futurepassCall, caller, { log: logger }); - - const [futurepassEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [ - "Futurepass.ProxyExecuted", - "Evm.Log", - { name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } }, - ]); - - logger.info( - { - result: { - extrinsicId, - blockNumber: result.blockNumber, - futurepassEvent: formatEventData(futurepassEvent.event), - evmLogEvent: formatEventData(evmLogEvent.event), - aliceTransferEvent: formatEventData(aliceTransferEvent.event), - }, - }, - "dispatch result" - ); -}); - -export async function createEVMCall(caller: string, api: ApiPromise, logger: Logger) { - // setup the actual EVM transaction, as interface - const { provider, wallet } = await provideEthersProvider("porcini"); - const transferToken = getERC20Contract(SYLO_ASSET_ID).connect(wallet); - const transferAmount = utils.parseUnits("0.1", 18); // 0.1 SYLO - const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [ - ALICE, - transferAmount, - ]); - const transferEstimate = await transferToken.estimateGas.transfer(ALICE, transferAmount); - const { lastBaseFeePerGas: maxFeePerGas } = await provider.getFeeData(); - assert(maxFeePerGas); - - logger.info( - { - parameters: { - source: caller, - target: transferToken.address, - input: transferInput, - gasLimit: transferEstimate.toString(), - maxFeePerGas: maxFeePerGas.toString(), - }, - }, - `create an "emv.call"` - ); - - return api.tx.evm.call( - caller, - transferToken.address, - transferInput, - 0, - transferEstimate.toString(), - maxFeePerGas.toString(), - 0, - null, - [] - ); -}