Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #219 from hermeznetwork/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
AlbertoElias authored Nov 25, 2021
2 parents 7221f92 + 992f002 commit 9361305
Show file tree
Hide file tree
Showing 9 changed files with 855 additions and 670 deletions.
1,200 changes: 582 additions & 618 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"base64url": "^3.0.1",
"circomlib": "0.5.2",
"core-js": "^3.8.1",
"ethers": "^5.1.4",
"ethers": "^5.5.1",
"ffjavascript": "^0.2.35",
"graceful-fs": "4.2.4",
"js-sha3": "^0.8.0",
Expand Down
58 changes: 58 additions & 0 deletions src/abis/ERC20ABI.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,5 +284,63 @@ export default [
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "PERMIT_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "nonces",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getChainId",
"outputs": [
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
122 changes: 120 additions & 2 deletions src/tokens.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { splitSignature, Interface } from 'ethers/lib/utils'
import { constants as ethersConstants } from 'ethers'

import ERC20ABI from './abis/ERC20ABI.js'
import { ContractNames, CONTRACT_ADDRESSES, APPROVE_AMOUNT } from './constants.js'
import { getContract } from './contracts.js'
import { SignerType } from './signers.js'
import { SignerType, getSigner } from './signers.js'
import { getProvider } from './providers.js'

/**
* Sends an approve transaction to an ERC 20 contract for a certain amount of tokens
Expand All @@ -24,6 +28,120 @@ async function approve (amount, accountAddress, contractAddress, signerData, pro
}
}

/**
* Checks where a token contract supports `permit`
* @param {ethers.Contract} tokenContractInstance - A Contract instance of an ERC 20 token
* @returns {Boolean} Whether the ERC 20 contract supports `permit`
*/
async function isPermitSupported (tokenContractInstance) {
try {
return !!(await tokenContractInstance.PERMIT_TYPEHASH())
} catch (e) {
return false
}
}

/**
* Generates a permit signature following EIP 712
* @param {ethers.Contract} tokenContractInstance - A Contract instance of an ERC 20 token
* @param {String} accountAddress - The Ethereum address of the account making the transfer
* @param {String} contractAddress - The Ethereum address of the contract being authorized
* @param {ethers.BigNumber} value - The amount being approved
* @param {ethers.BigNumber} nonce - The contract's nonce
* @param {ethers.BigNumber} deadline -
* @param {Object} signerData - Signer data used to build a Signer to send the transaction
* @param {String} providerUrl - Network url (i.e, http://localhost:8545). Optional
* @returns {Object} A signature object with r, s, v
*/
async function createPermitSignature (
tokenContractInstance,
accountAddress,
contractAddress,
value,
nonce,
deadline,
signerData,
providerUrl
) {
const chainId = (await tokenContractInstance.getChainId())
const name = await tokenContractInstance.name()

// The domain
const domain = {
name: name,
version: '1',
chainId: chainId,
verifyingContract: tokenContractInstance.address
}

// The named list of all type definitions
const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
}

// The data to sign
const values = {
owner: accountAddress,
spender: contractAddress,
value: value,
nonce: nonce,
deadline: deadline
}

const provider = getProvider(providerUrl)
const signer = getSigner(provider, signerData)
const rawSignature = await signer._signTypedData(domain, types, values)
return splitSignature(rawSignature)
}

/**
* Generates a permit data string to be passed to a smart contract function call
* @param {ethers.Contract} fromTokenContract - A Contract instance of an ERC 20 token
* @param {String} accountAddress - The Ethereum address of the transaction sender
* @param {Object} signerData - Signer data used to build a Signer to send the transaction
* @param {String} providerUrl - Network url (i.e, http://localhost:8545). Optional
* @returns {String} A hex string with the permit data
*/
async function permit (fromTokenContract, accountAddress, contractAddress, signerData, providerUrl) {
const nonce = await fromTokenContract.nonces(accountAddress)
const amount = ethersConstants.MaxUint256
const deadline = ethersConstants.MaxUint256
const { v, r, s } = await createPermitSignature(
fromTokenContract,
accountAddress,
contractAddress,
amount,
nonce,
deadline,
signerData,
providerUrl
)

const permitABI = [
'function permit(address,address,uint256,uint256,uint8,bytes32,bytes32)'
]
const permitInterface = new Interface(permitABI)
const dataPermit = permitInterface.encodeFunctionData('permit', [
accountAddress,
contractAddress,
amount,
deadline,
v,
r,
s
])

return dataPermit
}

export {
approve
approve,
isPermitSupported,
permit
}
80 changes: 54 additions & 26 deletions src/tx.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Scalar } from 'ffjavascript'
import { groth16 } from 'snarkjs'

import {
Expand All @@ -9,12 +8,24 @@ import {
} from './api.js'
import { HermezCompressedAmount } from './hermez-compressed-amount.js'
import { addPoolTransaction } from './tx-pool.js'
import { ContractNames, CONTRACT_ADDRESSES, GAS_LIMIT_LOW, GAS_MULTIPLIER, WITHDRAWAL_WASM_URL, WITHDRAWAL_ZKEY_URL, ETHER_ADDRESS } from './constants.js'
import { approve } from './tokens.js'
import {
ContractNames,
CONTRACT_ADDRESSES,
GAS_LIMIT_LOW,
WITHDRAWAL_WASM_URL,
WITHDRAWAL_ZKEY_URL,
ETHER_ADDRESS
} from './constants.js'
import {
approve,
isPermitSupported,
permit
} from './tokens.js'
import { getEthereumAddress, getAccountIndex } from './addresses.js'
import { getContract } from './contracts.js'
import { getProvider } from './providers.js'
import { generateL2Transaction } from './tx-utils.js'
import ERC20ABI from './abis/ERC20ABI.js'
import HermezABI from './abis/HermezABI.js'
import WithdrawalDelayerABI from './abis/WithdrawalDelayerABI.js'
import { SignerType } from './signers.js'
Expand All @@ -23,18 +34,18 @@ import { estimateDepositGasLimit, estimateWithdrawGasLimit } from './tx-fees.js'
import { generateAtomicGroup } from './atomic-utils.js'

/**
* Get current average gas price from the last ethereum blocks and multiply it
* @param {Number} multiplier - multiply the average gas price by this parameter
* Get max Fee per Gas from the last ethereum blocks
* @param {String} providerUrl - Network url (i.e, http://localhost:8545). Optional
* @returns {Promise} - promise will return the gas price obtained.
*/
async function getGasPrice (multiplier = GAS_MULTIPLIER, providerUrl) {
async function getGasPrice (providerUrl) {
const provider = getProvider(providerUrl)
const strAvgGas = await provider.getGasPrice()
const avgGas = Scalar.e(strAvgGas)
const res = (avgGas * Scalar.e(multiplier))
const retValue = res.toString()
return retValue
const { maxFeePerGas, maxPriorityFeePerGas } = await provider.getFeeData()

return {
maxFeePerGas: maxFeePerGas.toString(),
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
}
}

/**
Expand Down Expand Up @@ -68,22 +79,30 @@ const deposit = async (
const ethereumAddress = getEthereumAddress(hezEthereumAddress)
const txSignerData = signerData || { type: SignerType.JSON_RPC, addressOrIndex: ethereumAddress }
const hermezContract = getContract(CONTRACT_ADDRESSES[ContractNames.Hermez], HermezABI, txSignerData, providerUrl)
const fromTokenContract = getContract(token.ethereumAddress, ERC20ABI, txSignerData, providerUrl)

const accounts = await getAccounts(hezEthereumAddress, [token.id])
.catch(() => undefined)
const account = typeof accounts !== 'undefined' ? accounts.accounts[0] : null

const overrides = {
gasPrice: await getGasPrice(gasMultiplier, providerUrl)
gasPrice: await getGasPrice(providerUrl)
}

const usePermit = await isPermitSupported(fromTokenContract)
const permitSignature =
usePermit
? await permit(fromTokenContract, ethereumAddress, CONTRACT_ADDRESSES[ContractNames.Hermez], signerData, providerUrl)
: '0x'

const transactionParameters = [
account ? 0 : `0x${babyJubJub}`,
account ? getAccountIndex(account.accountIndex) : 0,
amount.value,
0,
token.id,
0,
'0x'
permitSignature
]

const decompressedAmount = HermezCompressedAmount.decompressAmount(amount)
Expand All @@ -100,7 +119,9 @@ const deposit = async (
return hermezContract.addL1Transaction(...transactionParameters, overrides)
}

await approve(decompressedAmount, ethereumAddress, token.ethereumAddress, signerData, providerUrl)
if (!usePermit) {
await approve(decompressedAmount, ethereumAddress, token.ethereumAddress, signerData, providerUrl)
}

return hermezContract.addL1Transaction(...transactionParameters, overrides)
}
Expand Down Expand Up @@ -137,10 +158,10 @@ const forceExit = async (
const ethereumAddress = getEthereumAddress(account.hezEthereumAddress)
const txSignerData = signerData || { type: SignerType.JSON_RPC, addressOrIndex: ethereumAddress }
const hermezContract = getContract(CONTRACT_ADDRESSES[ContractNames.Hermez], HermezABI, txSignerData, providerUrl)

const gasPrice = await getGasPrice(providerUrl)
const overrides = {
gasLimit: typeof gasLimit !== 'undefined' ? gasLimit : GAS_LIMIT_LOW,
gasPrice: await getGasPrice(gasMultiplier, providerUrl)
...gasPrice
}

const transactionParameters = [
Expand Down Expand Up @@ -193,12 +214,21 @@ const withdraw = async (
const ethereumAddress = getEthereumAddress(account.hezEthereumAddress)
const txSignerData = signerData || { type: SignerType.JSON_RPC, addressOrIndex: ethereumAddress }
const hermezContract = getContract(CONTRACT_ADDRESSES[ContractNames.Hermez], HermezABI, txSignerData, providerUrl)

const gasPrice = await getGasPrice(providerUrl)
const overrides = {
gasPrice: await getGasPrice(gasMultiplier, providerUrl),
gasLimit: typeof gasLimit === 'undefined'
? await estimateWithdrawGasLimit(token, merkleSiblings.length, amount, {}, txSignerData, providerUrl, isInstant)
: gasLimit
...gasPrice,
gasLimit:
typeof gasLimit === 'undefined'
? await estimateWithdrawGasLimit(
token,
merkleSiblings.length,
amount,
{},
txSignerData,
providerUrl,
isInstant
)
: gasLimit
}

const transactionParameters = [
Expand Down Expand Up @@ -243,9 +273,9 @@ const withdrawCircuit = async (
const zkInputs = await buildZkInputWithdraw(exitInfo)
const zkProofSnarkJs = await groth16.fullProve(zkInputs, wasmFileInput, zkeyFileInput)
const zkProofContract = await buildProofContract(zkProofSnarkJs.proof)

const gasPrice = await getGasPrice(providerUrl)
const overrides = {
gasPrice: gasMultiplier ? await getGasPrice(gasMultiplier, providerUrl) : undefined,
...gasPrice,
gasLimit
}

Expand Down Expand Up @@ -286,9 +316,7 @@ const delayedWithdraw = async (
const txSignerData = signerData || { type: SignerType.JSON_RPC, addressOrIndex: ethereumAddress }
const delayedWithdrawalContract = getContract(CONTRACT_ADDRESSES[ContractNames.WithdrawalDelayer], WithdrawalDelayerABI, txSignerData, providerUrl)

const overrides = {
gasPrice: await getGasPrice(gasMultiplier, providerUrl)
}
const overrides = await getGasPrice(providerUrl)

if (typeof gasLimit !== 'undefined') {
overrides.gasLimit = gasLimit
Expand Down
2 changes: 1 addition & 1 deletion tests/helpers/helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as CoordinatorAPI from '../../src/api.js'
import { getL1UserTxId, computeL2Transaction } from '../../src/tx-utils.js'
import { buildAtomicTransaction } from '../../src/atomic-utils.js'
import { generateAndSendAtomicGroup } from '../../src/txjs'
import { generateAndSendAtomicGroup } from '../../src/tx.js'

/**
* Wait timeout
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/tokens.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SignerType } from '../../src/signers.js'

// Requires `integration-testing` environment running

test('Check Allowance', async () => {
test('Check Allowance with approve', async () => {
const ERC20Address = '0xaded47e7b9275b17189f0b17bf6a4ce909047084'

// Initialize providers
Expand Down
Loading

0 comments on commit 9361305

Please sign in to comment.