Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Add example with feeProxy+futurepass+evm combination #36

Merged
merged 4 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading