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

[Spike] Update evm/use-feeProxy example #35

Merged
merged 9 commits into from
Oct 3, 2023
58 changes: 14 additions & 44 deletions examples/evm/use-feeProxy/README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
# Use FeeProxy
# FeeProxy Precompile

First run
[![Run in StackBlitz](https://img.shields.io/badge/Open_in_StackBlitz-1269D3?style=for-the-badge&logo=stackblitz&logoColor=white)](https://stackblitz.com/github/futureversecom/trn-examples?file=examples%2Fevm%2Fuse-feeProxy%2FREADME.md&title=FeeProxy%20Precompile%20Examples) [![Precompile Documentation](https://img.shields.io/badge/Pallet_Documentation-black?style=for-the-badge&logo=googledocs&logoColor=white)](https://docs-beta.therootnetwork.com/buidl/evm/precompile-feeProxy)

```
export CALLER_PRIVATE_KEY=0x000...
```

## Contract Read/Write
> [!IMPORTANT]
> Ensure the following ENV vars are available before running the examples
>
> - `CALLER_PRIVATE_KEY` - Private key of an account that submits the transaction. Follow this guide to [create and fund an account with some test tokens](../../GUIDES.md) on Porcini (testnet) if you don't have one yet.

Specify the payment asset id to get paymentPrecompileAddress

```js
const { erc20Precompile, wallet } = getERC20PrecompileForAssetId(
env.CALLER_PRIVATE_KEY,
paymentAsset
);
const feeToken = erc20Precompile;
```

Create FeeToken contract

```
const feeProxy = new Contract(FEE_PROXY_ADDRESS, FEE_PROXY_ABI, wallet);
```
## Examples

### `callWithFeePreferences(address asset, uint128 maxPayment, address target, bytes input)`
```bash
# change your working directory to this example first
cd examples/evm/use-feeProxy

- `asset` - precompile address for payment asset
- `maxPayment` - max payment user is willing to be pay in payment asset
- `target` - precompile address for payment asset
- `input` - transaction for which fee is to be paid in payment asset
# export all required environments
export CALLER_PRIVATE_KEY=

```js
const unsignedTx = {
type: 0,
from: wallet.address,
to: FEE_PROXY_ADDRESS,
nonce: nonce,
data: feeProxy.interface.encodeFunctionData("callWithFeePreferences", [
feeToken.address,
maxFeePaymentInToken,
feeToken.address,
transferInput,
]),
gasLimit: gasEstimate,
gasPrice: fees.gasPrice,
};
# `feeProxy.callWithFeePreferences` that wraps around `ERC20.transfer`
pnpm call:callERC20Transfer

await wallet.signTransaction(unsignedTx);
const tx = await wallet.sendTransaction(unsignedTx);
```
8 changes: 2 additions & 6 deletions examples/evm/use-feeProxy/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"scripts": {
"call": "ts-node --project ../../../tsconfig.json"
},
"eslintConfig": {
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
"call": "tsx --tsconfig ../../../tsconfig.json",
"call:callERC20Transfer": "pnpm call src/callERC20Transfer.ts"
}
}
76 changes: 76 additions & 0 deletions examples/evm/use-feeProxy/src/callERC20Transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ERC20_ABI } from "@therootnetwork/evm";
import { ALICE } from "@trne/utils/accounts";
import { filterTransactionEvents } from "@trne/utils/filterTransactionEvents";
import { getERC20Contract } from "@trne/utils/getERC20Contract";
import { getFeeProxyContract } from "@trne/utils/getFeeProxyContract";
import { getFeeProxyPricePair } from "@trne/utils/getFeeProxyPricePair";
import { ASTO_ASSET_ID, SYLO_ASSET_ID } from "@trne/utils/porcini-assets";
import { withEthersProvider } from "@trne/utils/withEthersProvider";
import { ContractReceipt, utils } from "ethers";

/**
* Use `feeProxy.callWithFeePreferences` to trigger a `transfer` call of SYLO token, and pay gas
* in ASTO token.
*
* Assumes the caller has some ASTO balance.
*/
withEthersProvider("porcini", async (provider, wallet, logger) => {
// contracts creation
const feeProxy = getFeeProxyContract().connect(wallet);
const sylo = getERC20Contract(SYLO_ASSET_ID).connect(wallet);
const asto = getERC20Contract(ASTO_ASSET_ID).connect(wallet);

// transfer call
const transferAmount = utils.parseUnits("1.0", 18); // 1 SYLO
const transferInput = new utils.Interface(ERC20_ABI).encodeFunctionData("transfer", [
ALICE,
transferAmount,
]);
const transferEstimate = await sylo.estimateGas.transfer(ALICE, transferAmount);

logger.info(
`prepare a transfer call with destination="${ALICE}", amount="${transferAmount}", and estimateGas="${transferEstimate}"`
);

// retrieve the maxPayment and maxFeePerGas parameters with slippage to be 0.05 (5%)
const { maxPayment, maxFeePerGas } = await getFeeProxyPricePair(
provider,
transferEstimate,
ASTO_ASSET_ID,
0.05
);

logger.info(
`dispatch a feeProxy call with maxPayment="${maxPayment}", and maxFeePerGas="${maxFeePerGas}"`
);

const tx = await feeProxy.callWithFeePreferences(
asto.address,
maxPayment,
sylo.address,
transferInput,
{
gasLimit: transferEstimate,
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: 0,
}
);

const receipt = (await tx.wait()) as unknown as ContractReceipt;

const [transferEvent] = filterTransactionEvents(ERC20_ABI, receipt.logs, ["Transfer"]);

logger.info(
{
result: {
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
transferEvent: {
name: transferEvent.name,
args: transferEvent.args,
},
},
},
"receive result"
);
});
64 changes: 0 additions & 64 deletions examples/evm/use-feeProxy/src/callWithFeePreferences.ts

This file was deleted.

3 changes: 3 additions & 0 deletions examples/substrate/use-feeProxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ pnpm call:callProxyExtrinsic
# `feeProxy.callWithFeePreferences` that wraps around `utility.batchAll`
pnpm call:callBatchAll

# `feeProxy.callWithFeePreferences` that wraps around `evm.call`
pnpm call:callEVMCall

```
5 changes: 3 additions & 2 deletions examples/substrate/use-feeProxy/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"scripts": {
"call": "ts-node --project ../../../tsconfig.json",
"call": "tsx --tsconfig ../../../tsconfig.json",
"call:callSystemRemark": "pnpm call src/callSystemRemark.ts",
"call:callProxyExtrinsic": "pnpm call src/callProxyExtrinsic.ts",
"call:callBatchAll": "pnpm call src/callBatchAll.ts"
"call:callBatchAll": "pnpm call src/callBatchAll.ts",
"call:callEVMCall": "pnpm call src/callEVMCall.ts"
}
}
46 changes: 29 additions & 17 deletions examples/substrate/use-feeProxy/src/callBatchAll.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { ALICE, BOB, CHARLIE } from "@trne/utils/accounts";
import { filterExtrinsicEvents } from "@trne/utils/filterExtrinsicEvents";
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";

const ASTO_ASSET_ID = 17_508;
const XRP_ASSET_ID = 2;

interface AmountsIn {
Ok: [number, number];
}
Expand All @@ -17,7 +15,7 @@ interface AmountsIn {
*
* Assumes the caller has some ASTO balance.
*/
withChainApi("porcini", async (api, caller) => {
withChainApi("porcini", async (api, caller, logger) => {
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());
Expand All @@ -29,26 +27,34 @@ withChainApi("porcini", async (api, caller) => {
transferToCharlieCall,
]);

const paymentInfo = await batchAllCall.paymentInfo(caller.address);
// 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,
batchAllCall
);

const paymentInfo = await feeProxyCallForEstimation.paymentInfo(caller.address);
const estimatedFee = paymentInfo.partialFee.toString();

// querying the dex for swap price, to determine the `maxPayment` you are willing to pay
// 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 prevent extrinsic failure
const maxPayment = Number(amountIn * 1.5).toFixed();

// allow a buffer to avoid slippage, 5%
const maxPayment = Number(amountIn * 1.05).toFixed();
const feeProxyCall = api.tx.feeProxy.callWithFeePreferences(
ASTO_ASSET_ID,
maxPayment,
batchAllCall
);

const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: console });
logger.info(`dispatch a feeProxy call with maxPayment="${maxPayment}"`);
const { result, extrinsicId } = await sendExtrinsic(feeProxyCall, caller, { log: logger });
const [proxyEvent, batchEvent, aliceTransferEvent, bobTransferEvent, charlieTransferEvent] =
filterExtrinsicEvents(result.events, [
"FeeProxy.CallWithFeePreferences",
Expand All @@ -58,12 +64,18 @@ withChainApi("porcini", async (api, caller) => {
{ name: "Assets.Transferred", key: "to", data: { value: CHARLIE, type: "T::AccountId" } },
]);

console.log("Extrinsic ID:", extrinsicId);
console.log("Extrinsic Result:", {
proxy: formatEventData(proxyEvent.event),
batchEvent: formatEventData(batchEvent.event),
aliceTransferEvent: formatEventData(aliceTransferEvent.event),
bobTransferEvent: formatEventData(bobTransferEvent.event),
charlieTransferEvent: formatEventData(charlieTransferEvent.event),
});
logger.info(
{
result: {
extrinsicId,
blockNumber: result.blockNumber,
proxyEvent: formatEventData(proxyEvent.event),
batchEvent: formatEventData(batchEvent.event),
aliceTransferEvent: formatEventData(aliceTransferEvent.event),
bobTransferEvent: formatEventData(bobTransferEvent.event),
charlieTransferEvent: formatEventData(charlieTransferEvent.event),
},
},
"receive result"
);
});
Loading
Loading