Skip to content

Commit

Permalink
[Feat] Add example with feeProxy+futurepass+evm combination (#36)
Browse files Browse the repository at this point in the history
* Update `use-feeProxy` example to use a similar logging output

* Add `src/callProxyExtrinsicEVMCall.ts`

* Add comment headers to make the code a bit clearer

* Remove the irrelevant code
  • Loading branch information
ken-futureverse authored Oct 5, 2023
1 parent 17fb2e7 commit 6117915
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 53 deletions.
3 changes: 3 additions & 0 deletions examples/substrate/use-feeProxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
3 changes: 2 additions & 1 deletion examples/substrate/use-feeProxy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
32 changes: 31 additions & 1 deletion examples/substrate/use-feeProxy/src/callBatchAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,33 @@ 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());
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,
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,
Expand All @@ -47,13 +63,27 @@ 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: {
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, [
Expand Down
113 changes: 79 additions & 34 deletions examples/substrate/use-feeProxy/src/callEVMCall.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -15,53 +17,33 @@ 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,
[]
);
/**
* 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,
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();

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],
Expand All @@ -72,12 +54,28 @@ 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: {
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, [
const [proxyEvent, evmLogEvent, aliceTransferEvent] = filterExtrinsicEvents(result.events, [
"FeeProxy.CallWithFeePreferences",
"Evm.Log",
{ name: "Assets.Transferred", key: "to", data: { value: ALICE, type: "T::AccountId" } },
]);

Expand All @@ -87,9 +85,56 @@ withChainApi("porcini", async (api, caller, logger) => {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.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,
};
}
58 changes: 44 additions & 14 deletions examples/substrate/use-feeProxy/src/callProxyExtrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -13,9 +13,12 @@ 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) => {
/**
* 1. Create `futurepass.proxyExtrinsic` call that wraps around `system.remarkWithEvent` call
*/
const fpAccount = (await api.query.futurepass.holders(caller.address)).unwrap();
logger.info(
{
Expand All @@ -31,8 +34,20 @@ 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);

/**
* 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,
Expand All @@ -42,8 +57,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],
Expand All @@ -54,27 +67,44 @@ 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: {
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",
"Futurepass.ProxyExecuted",
"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"
);
});
Loading

0 comments on commit 6117915

Please sign in to comment.