cronos-labs / cronos-zkevm

Apache License 2.0
8 stars 2 forks source link

[POC] Experiment paymaster contract (EIP 4337) #29

Open thomas-nguy opened 1 year ago

thomas-nguy commented 1 year ago

Zksync has implemented account abstraction (EIP 4337) https://eips.ethereum.org/EIPS/eip-4337 https://era.zksync.io/docs/reference/concepts/account-abstraction.html

We would like to experiment this feature by creating a paymaster that would accept a custom token to pay the gas fee on transactions

Ideally we should come up with a prototype contract and deliver a demo

crypto-matto commented 1 year ago

Followed this tutorial: Custom Paymaster Tutorial https://github.com/matter-labs/custom-paymaster-tutorial The code works smoothly.

Only BootLoader (a system contract) can call this method. It is a never deployed contract and cannot be called. It does, however, have a formal address that is used by msg.sender when calling other contracts.

Procedure

  1. Deploy an ERC20 Token Contract
  2. Deploy the PayMaster contract, specified with an approved ERC20 address
  3. Calls a mint() function with paymasterParams from any other empty balance wallet address
  4. ERC20 token will be transferred to the empty balance wallet address, without paying any gas fee from the wallet POV

Result

ETH balance of the empty wallet before mint: 0 ETH
ERC20 token balance of the empty wallet before mint: 0
Paymaster ETH balance is 0.06 ETH
Transaction fee estimation is :>>  0.0000572715 ETH
Minting 5 tokens for empty wallet via paymaster...
Paymaster ERC20 token balance is now 1
Paymaster ETH balance is now 0.05995183625 ETH
ETH balance of the empty wallet after mint: 0 ETH
ERC20 token balance of the wallet after mint: 4

The magic lies on passing a paymasterParams in customData when calling mint() of the ERC20 token:

  const paymasterParams = utils.getPaymasterParams(PAYMASTER_ADDRESS, {
    type: "ApprovalBased",
    token: TOKEN_ADDRESS,
    // set minimalAllowance as we defined in the paymaster contract
    minimalAllowance: ethers.BigNumber.from(1),
    // empty bytes as testnet paymaster does not use innerInput
    innerInput: new Uint8Array(),
  });
  ...
    await erc20.mint(emptyWallet.address, 5, {
      // paymaster info
      customData: {
        paymasterParams: paymasterParams,
        gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
      },
    })

There are 2 types:

The input data is not needed for this paymaster on testnet. It's uncertain what's its role on mainnet, or any other potential usage. But this could be the entry point of limiting the usage to specific applications.

thomas-nguy commented 1 year ago

For the next time, could we try to deploy a uniswap contract pool on testnet and try to deploy a paymaster that authorize a swap on a specific pool?

crypto-matto commented 1 year ago

Follow up

crypto-matto commented 1 year ago

For the next time, could we try to deploy a uniswap contract pool on testnet and try to deploy a paymaster that authorize a swap on a specific pool?

In regard to paying the fee for uniswap transaction, the test was unsuccessful on local.

Steps

  1. Deploy all necessary uniswap contracts
  2. Create a trading pair & add liquidity
    • e.g. Token A - Token B
  3. Deploy a paymaster contract, specified with Token A
  4. Call a swap tx from Token A to Token B, specified with paymasterParams

The swap transaction swapExactTokensForETH() always fail with a general error:

TransferHelper::transferFrom: transferFrom failed

Another attempt on NFT ERC721

While for another experiment on ERC721 contract, the paymaster contract works fine with transactions like mint() & setBaseURI(). The general usage is pretty similar as ERC20.

Steps

  1. Deploy ERC721 contract
  2. Deploy Paymaster contract. Compare to the Paymaster used with ERC20
    • This NFT Paymaster contract is using General type selector, instead of ApprovalBased
    • This Paymaster contract doesn't ask for any fee from the caller
  3. Calls any functions with paymasterParams from any other empty balance wallet address

Result

Empty wallet's address: 0x52F43E59A6e5c645a7a9C23a6c22a0f82378bed9
ETH balance of the empty wallet before mint: 0
Paymaster ETH balance is 0.00492076 ETH
ERC721 token balance of the recipient: 1
Transaction setBaseURI() fee estimation is :>>  0.0000468345 ETH
Initiate ERC721 token transaction from empty wallet via paymaster...
New baseURI is: https://ipfs.io/ipfs/QmPtDtJEJDzxthbKmdgvYcLa9oNUUUkh7vvz5imJFPQdKa
Paymaster ERC721 token balance is now 0
Empty Wallet ERC721 token balance is now 1
Paymaster ETH balance is now 0.004881412 ETH
ETH balance of the empty wallet after mint: 0

Experiment on Loop Contracts

Infinite Loop

pragma solidity ^0.8.0;

contract TestInfiniteLoop {
      function loop() public pure returns(string memory){
        uint i = 1;
        uint c = 0;
        while(i == 1){
            c = c + 1;
        }
        return "good";
    }
}

On normal remix, calling loop() will result in VM error: out of gas.

On zkSync, calling loop() with the aid of paymaster, will result in CALL_EXCEPTION error instead, with errorName: null. Paymaster balance is not decreased.

Finite Loop

For initiating a finite loop transaction, a bunch of execute_in_sandbox actions, predicting things like execution time. While the ETH balance in paymaster is definitely enough to pay for the gas:

When it’s a loop 5000+ transaction, in the end the send_raw_transaction_impl function will return failed to validate the transaction.

failed_validation

When the loop is set to 4000, the procedure is the same, but the transaction will go through.

success_tx
crypto-matto commented 1 year ago

Paymaster Properties

For points marked as *, we might need to clarify from zkSync team. I suppose this will work for all types of transactions, but uncertain at this point if there are some OPCODE doesn't support yet.

crypto-matto commented 1 year ago

Forked Repo on testing paymaster: https://github.com/crypto-matto/custom-paymaster-tutorial/tree/test-paymaster

Working branch: test-paymaster

Steps

Please refer to https://github.com/crypto-matto/custom-paymaster-tutorial/tree/test-paymaster#testing-with-local-zksync

XinyuCRO commented 1 year ago

Paying the fee for Uniswap transaction is doable, check out this PR: https://github.com/crypto-matto/custom-paymaster-tutorial/pull/1