Skip to content

Commit

Permalink
added README.md example of custom paymaster example & minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ViktorYastrebov committed Jan 10, 2023
1 parent b590cd9 commit 513e90d
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 19 deletions.
138 changes: 137 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ For user needs there are the following contracts:
* NonceHolder
* ERC20Contract & ERC20FunctionEncoder
* ContractDeployer
* PaymasterFlowEncoder


#### NonceHolder
Expand Down Expand Up @@ -234,7 +235,26 @@ Methods:
| compute_l2_create_address | Address, Nonce | Address | Accepts address of deployer and current deploing nonce and returns address of contract that is going to be deployed by `encode_create` method |
| compute_l2_create2_address | Address, bytecode, ctor bytecode, salt | Address | Accepts address of deployer, binary representation of contract, if needed it's constructor in binary format and salf. By default constructor can be b'0' value. Returns address of contract that is going to be deployed by `encode_create2` method |

#### PaymasterFlowEncoder

PaymasterFlowEncoder is utility contract for encoding Paymaster parameters.<br>
Construction contract needs only Web3 Module object. It can be Eth or ZkSync.<br>

Example:
```python
from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder
from zksync2.module.module_builder import ZkSyncBuilder

zksync_web3 = ZkSyncBuilder.build("ZKSYNC_NETWORK_URL")
paymaster_encoder = PaymasterFlowEncoder(zksync_web3)
```

This utility contract has 2 methods wrapped directly to python:

* encode_approval_based
* encode_general

For example and usage, please have a look into example [section](#examples)


### Examples
Expand Down Expand Up @@ -718,4 +738,120 @@ def deploy_contract_create2():
if __name__ == "__main__":
deploy_contract_create2()

```
```

#### Custom Paymaster
This example is based on the `custom_paymaster_binary` that accepts any coin with <br>
the 1:1 relation to native token( 18 decimals )


```python

from pathlib import Path
from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import HexStr
from eth_utils import remove_0x_prefix
from web3 import Web3
from zksync2.manage_contracts.gas_provider import StaticGasProvider
from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder
from zksync2.signer.eth_signer import PrivateKeyEthSigner
from zksync2.module.module_builder import ZkSyncBuilder
from zksync2.manage_contracts.erc20_contract import ERC20Contract
from zksync2.transaction.transaction712 import TxFunctionCall

from zksync2.core.types import EthBlockParams, PaymasterParams, Token


def read_hex_binary(name: str) -> bytes:
p = Path(f"./{name}")
with p.open(mode='r') as contact_file:
lines = contact_file.readlines()
data = "".join(lines)
return bytes.fromhex(data)


# Contract to mint custom token under testnet:
# https://goerli.explorer.zksync.io/address/0xFC174650BDEbE4D94736442307D4D7fdBe799EeC#contract

class PaymasterExample:
SERC20_TOKEN = Token(
Web3.toChecksumAddress("0x" + "0" * 40),
Web3.toChecksumAddress("0xFC174650BDEbE4D94736442307D4D7fdBe799EeC"),
"SERC20",
18)

def __init__(self):
self.web3 = ZkSyncBuilder.build("ZKSYNC_NETWORK_URL")
self.account: LocalAccount = Account.from_key("YOUR_PRIVATE_KEY")
self.chain_id = self.web3.zksync.chain_id
self.signer = PrivateKeyEthSigner(self.account, self.chain_id)
self.gas_provider = StaticGasProvider(Web3.toWei(1, "gwei"), 555000)

self.custom_paymaster_contract_bin = read_hex_binary("custom_paymaster_binary.hex")

@property
def paymaster_address(self) -> HexStr:
return self.web3.zksync.zks_get_testnet_paymaster_address()

def build_paymaster(self, trans: TxFunctionCall, fee: int) -> TxFunctionCall:
paymaster_encoder = PaymasterFlowEncoder(self.web3.zksync)
encoded_approval_base = paymaster_encoder.encode_approval_based(self.SERC20_TOKEN.l2_address,
fee,
b'')
encoded_approval_bin = bytes.fromhex(remove_0x_prefix(encoded_approval_base))
trans.tx["eip712Meta"].paymaster_params = PaymasterParams(paymaster=self.paymaster_address,
paymaster_input=encoded_approval_bin)
return trans

def run(self):
gas_price = self.web3.zksync.gas_price
paymaster_address = self.paymaster_address
nonce = self.web3.zksync.get_transaction_count(self.account.address, EthBlockParams.PENDING.value)
transaction = TxFunctionCall(self.chain_id,
nonce=nonce,
from_=self.account.address,
to=self.account.address,
gas_price=gas_price)

# INFO: encode paymaster params with dummy fee to estimate real one
unknown_fee = 0
transaction = self.build_paymaster(transaction, unknown_fee)

paymaster_est_gas = self.web3.zksync.eth_estimate_gas(transaction.tx)
preprocessed_fee = gas_price * paymaster_est_gas

print(f"Paymaster fee: {preprocessed_fee}")

erc20 = ERC20Contract(self.web3.zksync, contract_address=self.SERC20_TOKEN.l2_address,
account=self.account,
gas_provider=self.gas_provider)

allowance = erc20.allowance(self.account.address, paymaster_address)
if allowance < preprocessed_fee:
is_approved = erc20.approve_deposit(paymaster_address, preprocessed_fee)
print(f"pass deposite: {is_approved}")

# INFO: encode paymaster params with real fee
transaction = self.build_paymaster(transaction, preprocessed_fee)

balance_before = self.web3.zksync.get_balance(self.account.address, EthBlockParams.PENDING.value)
print(f"balance before : {balance_before}")

tx712 = transaction.tx712(paymaster_est_gas)
singed_message = self.signer.sign_typed_data(tx712.to_eip712_struct())
msg = tx712.encode(singed_message)
tx_hash = self.web3.zksync.send_raw_transaction(msg)
tx_receipt = self.web3.zksync.wait_for_transaction_receipt(tx_hash, timeout=240, poll_latency=0.5)
print(f"status: {tx_receipt['status']}")

balance_after = self.web3.zksync.get_balance(self.account.address, EthBlockParams.PENDING.value)
print(f"balance after: {balance_after}")


if __name__ == "__main__":
paymaster = PaymasterExample()
paymaster.run()

```

16 changes: 3 additions & 13 deletions tests/test_paymaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
from eth_typing import HexStr
from eth_utils import keccak, remove_0x_prefix
from web3 import Web3
from web3.middleware import geth_poa_middleware

import zksync2.transaction.transaction712
from zksync2.provider.eth_provider import EthereumProvider

from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder
from zksync2.manage_contracts.erc20_contract import ERC20Contract
from zksync2.manage_contracts.gas_provider import StaticGasProvider
Expand All @@ -21,12 +16,7 @@


# mint tx hash of Test coins:
# https://goerli.explorer.zksync.io/tx/0xaaafc0e4228336783ad4e996292123c6eebb36b5c1b257e5b628e7e0281cccab

# https://goerli.explorer.zksync.io/tx/0xea09a90d09aa3644791ef300b5b5bbf397723812542a5040ff50237472e549fa
# https://goerli.explorer.zksync.io/tx/0x86b1b6361cfe54dd54b2968fbfe97429d8876994a0240b27962ad9869acd512f
# https://goerli.explorer.zksync.io/tx/0x78cf6db673a941495c6887badb84b4150bc09c3962a76c0d5c239190151e1ec5
# SERC20
# https://goerli.explorer.zksync.io/address/0xFC174650BDEbE4D94736442307D4D7fdBe799EeC#contract

class PaymasterTests(TestCase):
ETH_TEST_URL = "https://rpc.ankr.com/eth_goerli"
Expand Down Expand Up @@ -139,7 +129,7 @@ def test_send_funds_for_fee(self):
self.assertEqual(1, tx_receipt["status"])

def build_paymaster(self, trans: TxFunctionCall, fee: int) -> TxFunctionCall:
paymaster_encoder = PaymasterFlowEncoder(self.web3)
paymaster_encoder = PaymasterFlowEncoder(self.web3.zksync)
encoded_approval_base = paymaster_encoder.encode_approval_based(self.SERC20_TOKEN.l2_address,
fee,
b'')
Expand All @@ -148,7 +138,7 @@ def build_paymaster(self, trans: TxFunctionCall, fee: int) -> TxFunctionCall:
paymaster_input=encoded_approval_bin)
return trans

@skip("Integration test, paymaster params test not implemented yet")
# @skip("Integration test, paymaster params test not implemented yet")
def test_send_funds_with_paymaster(self):
gas_price = self.web3.zksync.gas_price
paymaster_address = self.paymaster_address
Expand Down
2 changes: 1 addition & 1 deletion zksync2/manage_contracts/erc20_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(self, web3: Module,
def _nonce(self) -> int:
return self.module.get_transaction_count(self.account.address)

def approve_deposit(self, zksync_address: HexStr, max_erc20_approve_amount=MAX_ERC20_APPROVE_AMOUNT) -> TxReceipt:
def approve_deposit(self, zksync_address: HexStr, max_erc20_approve_amount=MAX_ERC20_APPROVE_AMOUNT) -> bool:
return self.contract.functions.approve(zksync_address, max_erc20_approve_amount).call(
{
"chainId": self.module.chain_id,
Expand Down
10 changes: 6 additions & 4 deletions zksync2/manage_contracts/paymaster_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
from web3 import Web3
from eth_typing import HexStr
import json

from web3.module import Module

from zksync2.manage_contracts import contract_abi

paymaster_flow_abi_cache = None
Expand All @@ -20,10 +23,9 @@ def _paymaster_flow_abi_default():

class PaymasterFlowEncoder:

def __init__(self, zksync: Web3):
self.web3 = zksync
self.contract = self.web3.zksync.contract(address=None,
abi=_paymaster_flow_abi_default())
def __init__(self, module: Module):
self.contract = module.contract(address=None,
abi=_paymaster_flow_abi_default())

def encode_approval_based(self, address: HexStr, min_allowance: int, inner_input: bytes) -> HexStr:
return self.contract.encodeABI(fn_name="approvalBased", args=[address, min_allowance, inner_input])
Expand Down

0 comments on commit 513e90d

Please sign in to comment.