Skip to content

Commit

Permalink
Raise hard transaction fee cap for non-ETH chains (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
mholtzman committed Oct 11, 2021
1 parent d5a7ba1 commit 3c44651
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ import { usesBaseFee } from '../../../../../../../../../main/transaction'

import BigNumber from 'bignumber.js'

function maxFee (tx = { chainId: '' }) {
const chainId = parseInt(tx.chainId)

// for ETH-based chains, the max fee should be 2 ETH
if ([1, 3, 4, 5, 6, 10, 42, 61, 62, 63, 69].includes(chainId)) {
return 2 * 1e18
}

// for Fantom, the max fee should be 250 FTM
if ([250, 4002].includes(chainId)) {
return 250 * 1e18
}

// for all other chains, default to 10 of the chain's currency
return 10 * 1e18
}

// <div className='txModuleTop'>
// <div className={'txModuleTopData txModuleTopDataExpanded'}>
Expand All @@ -20,8 +36,6 @@ import BigNumber from 'bignumber.js'
// </div>
// <div className='txModuleBody'>

const FEE_MAX_TOTAL_ETH_WEI = 2 * 1e18

class TxFeeOverlay extends React.Component {
constructor (props, context) {
super(props, context)
Expand Down Expand Up @@ -95,8 +109,10 @@ class TxFeeOverlay extends React.Component {
baseFee = this.limitRange(baseFee, 0, 9999)
const priorityFee = parseFloat(this.state.priorityFee)
const gasLimit = parseInt(this.state.gasLimit)
if (gweiToWei(baseFee + priorityFee) * gasLimit > FEE_MAX_TOTAL_ETH_WEI) {
baseFee = Math.floor(FEE_MAX_TOTAL_ETH_WEI / gasLimit / 1e9) - priorityFee
const maxTotalFee = maxFee(this.props.req.data)

if (gweiToWei(baseFee + priorityFee) * gasLimit > maxTotalFee) {
baseFee = Math.floor(maxTotalFee / gasLimit / 1e9) - priorityFee
}
link.rpc('setBaseFee', gweiToWeiHex(baseFee), this.props.req.handlerId, e => {
if (e) console.error(e)
Expand All @@ -114,8 +130,10 @@ class TxFeeOverlay extends React.Component {
priorityFee = this.limitRange(priorityFee, 0, 9999)
const baseFee = parseFloat(this.state.baseFee)
const gasLimit = parseInt(this.state.gasLimit)
if (gweiToWei(baseFee + priorityFee) * gasLimit > FEE_MAX_TOTAL_ETH_WEI) {
priorityFee = Math.floor(FEE_MAX_TOTAL_ETH_WEI / gasLimit / 1e9) - baseFee
const maxTotalFee = maxFee(this.props.req.data)

if (gweiToWei(baseFee + priorityFee) * gasLimit > maxTotalFee) {
priorityFee = Math.floor(maxTotalFee / gasLimit / 1e9) - baseFee
}
link.rpc('setPriorityFee', gweiToWeiHex(priorityFee), this.props.req.handlerId, e => {
if (e) console.error(e)
Expand All @@ -134,8 +152,10 @@ class TxFeeOverlay extends React.Component {
if (isNaN(gasPrice)) return
gasPrice = this.limitRange(gasPrice, 0, 9999)
const gasLimit = parseInt(this.state.gasLimit)
if (gweiToWei(gasPrice) * gasLimit > FEE_MAX_TOTAL_ETH_WEI) {
gasPrice = Math.floor(FEE_MAX_TOTAL_ETH_WEI / gasLimit / 1e9)
const maxTotalFee = maxFee(this.props.req.data)

if (gweiToWei(gasPrice) * gasLimit > maxTotalFee) {
gasPrice = Math.floor(maxTotalFee / gasLimit / 1e9)
}
link.rpc('setGasPrice', gweiToWeiHex(gasPrice), this.props.req.handlerId, e => {
if (e) console.error(e)
Expand All @@ -150,17 +170,21 @@ class TxFeeOverlay extends React.Component {
this.gasLimitSubmitTimeout = setTimeout(() => {
gasLimit = parseInt(gasLimit)
if (isNaN(gasLimit)) return
if (gasLimit > 12.5e6) gasLimit = 12.5e6
if (gasLimit > 12.5e6) gasLimit = 12.5e6

const maxTotalFee = maxFee(this.props.req.data)

if (usesBaseFee(this.props.req.data)) {
const baseFee = parseFloat(this.state.baseFee)
const priorityFee = parseFloat(this.state.priorityFee)
if (gweiToWei(baseFee + priorityFee) * gasLimit > FEE_MAX_TOTAL_ETH_WEI) {
gasLimit = Math.floor(FEE_MAX_TOTAL_ETH_WEI / gweiToWei(baseFee + priorityFee))

if (gweiToWei(baseFee + priorityFee) * gasLimit > maxTotalFee) {
gasLimit = Math.floor(maxTotalFee / gweiToWei(baseFee + priorityFee))
}
} else {
const gasPrice = parseFloat(this.state.gasPrice)
if (gweiToWei(gasPrice) * gasLimit > FEE_MAX_TOTAL_ETH_WEI) {
gasLimit = Math.floor(FEE_MAX_TOTAL_ETH_WEI / gweiToWei(gasPrice))
if (gweiToWei(gasPrice) * gasLimit > maxTotalFee) {
gasLimit = Math.floor(maxTotalFee / gweiToWei(gasPrice))
}
}
link.rpc('setGasLimit', '0x' + gasLimit.toString(16), this.props.req.handlerId, e => {
Expand Down
50 changes: 30 additions & 20 deletions main/accounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const fetch = require('node-fetch')
const { addHexPrefix } = require('ethereumjs-util')

// const bip39 = require('bip39')
const { usesBaseFee, signerCompatibility } = require('../transaction')
const { usesBaseFee, signerCompatibility, maxFee } = require('../transaction')

const crypt = require('../crypt')
const store = require('../store')
Expand All @@ -34,8 +34,6 @@ const notify = (title, body, action) => {
setTimeout(() => note.show(), 1000)
}

const FEE_MAX = 2 * 1e18

class Accounts extends EventEmitter {
constructor () {
super()
Expand Down Expand Up @@ -701,14 +699,17 @@ class Accounts extends EventEmitter {
// No change
if (newBaseFee === currentBaseFee) return cb()

const tx = currentAccount.requests[handlerId].data

// New max fee per gas
const newMaxFeePerGas = newBaseFee + maxPriorityFeePerGas
const maxTotalFee = maxFee(tx)

// Limit max fee
if (newMaxFeePerGas * gasLimit > FEE_MAX) {
currentAccount.requests[handlerId].data.maxFeePerGas = intToHex(Math.floor(FEE_MAX / gasLimit))
if (newMaxFeePerGas * gasLimit > maxTotalFee) {
tx.maxFeePerGas = intToHex(Math.floor(maxTotalFee / gasLimit))
} else {
currentAccount.requests[handlerId].data.maxFeePerGas = intToHex(newMaxFeePerGas)
tx.maxFeePerGas = intToHex(newMaxFeePerGas)
}

// Complete update
Expand All @@ -729,18 +730,21 @@ class Accounts extends EventEmitter {
// No change
if (newMaxPriorityFeePerGas === maxPriorityFeePerGas) return cb()

const tx = currentAccount.requests[handlerId].data

// New max fee per gas
const newMaxFeePerGas = currentBaseFee + newMaxPriorityFeePerGas
const maxTotalFee = maxFee(tx)

// Limit max fee
if (newMaxFeePerGas * gasLimit > FEE_MAX) {
const limitedMaxFeePerGas = Math.floor(FEE_MAX / gasLimit)
if (newMaxFeePerGas * gasLimit > maxTotalFee) {
const limitedMaxFeePerGas = Math.floor(maxTotalFee / gasLimit)
const limitedMaxPriorityFeePerGas = limitedMaxFeePerGas - currentBaseFee
currentAccount.requests[handlerId].data.maxPriorityFeePerGas = intToHex(limitedMaxPriorityFeePerGas)
currentAccount.requests[handlerId].data.maxFeePerGas = intToHex(limitedMaxFeePerGas)
tx.maxPriorityFeePerGas = intToHex(limitedMaxPriorityFeePerGas)
tx.maxFeePerGas = intToHex(limitedMaxFeePerGas)
} else {
currentAccount.requests[handlerId].data.maxFeePerGas = intToHex(newMaxFeePerGas)
currentAccount.requests[handlerId].data.maxPriorityFeePerGas = intToHex(newMaxPriorityFeePerGas)
tx.maxFeePerGas = intToHex(newMaxFeePerGas)
tx.maxPriorityFeePerGas = intToHex(newMaxPriorityFeePerGas)
}

const previousFee = {
Expand All @@ -764,13 +768,16 @@ class Accounts extends EventEmitter {
const newGasPrice = parseInt(this.limitedHexValue(price, 0, 9999 * 1e9), 'hex')

// No change
if (newGasPrice === gasPrice) return cb()
if (newGasPrice === gasPrice) return cb()

const tx = currentAccount.requests[handlerId].data
const maxTotalFee = maxFee(tx)

// Limit max fee
if (newGasPrice * gasLimit > FEE_MAX) {
currentAccount.requests[handlerId].data.gasPrice = intToHex(Math.floor(FEE_MAX / gasLimit))
if (newGasPrice * gasLimit > maxTotalFee) {
tx.gasPrice = intToHex(Math.floor(maxTotalFee / gasLimit))
} else {
currentAccount.requests[handlerId].data.gasPrice = intToHex(newGasPrice)
tx.gasPrice = intToHex(newGasPrice)
}

const previousFee = {
Expand All @@ -787,16 +794,19 @@ class Accounts extends EventEmitter {

setGasLimit (limit, handlerId, userUpdate, cb) {
try {
const { currentAccount, maxFeePerGas, maxPriorityFeePerGas, gasPrice, txType } = this.txFeeUpdate(limit, handlerId, userUpdate, '0x0')
const { currentAccount, maxFeePerGas, gasPrice, txType } = this.txFeeUpdate(limit, handlerId, userUpdate, '0x0')

// New values
const newGasLimit = parseInt(this.limitedHexValue(limit, 0, 12.5e6), 'hex')

const tx = currentAccount.requests[handlerId].data
const maxTotalFee = maxFee(tx)

const fee = txType === '0x2' ? parseInt(maxFeePerGas, 'hex') : parseInt(gasPrice, 'hex')
if (newGasLimit * fee > FEE_MAX) {
currentAccount.requests[handlerId].data.gasLimit = intToHex(Math.floor(FEE_MAX / fee))
if (newGasLimit * fee > maxTotalFee) {
tx.gasLimit = intToHex(Math.floor(maxTotalFee / fee))
} else {
currentAccount.requests[handlerId].data.gasLimit = intToHex(newGasLimit)
tx.gasLimit = intToHex(newGasLimit)
}

// Complete update
Expand Down
20 changes: 14 additions & 6 deletions main/provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const store = require('../store')
const chains = require('../chains')
const accounts = require('../accounts')

const { populate: populateTransaction, usesBaseFee } = require('../transaction')
const { populate: populateTransaction, usesBaseFee, maxFee } = require('../transaction')

const version = require('../../package.json').version

Expand Down Expand Up @@ -224,12 +224,11 @@ class Provider extends EventEmitter {
})
}

feeTotalOverMax (rawTx) {
const FEE_MAX = 2 * 1e18 // 2 Ether
feeTotalOverMax (rawTx, maxTotalFee) {
const maxFeePerGas = usesBaseFee(rawTx) ? parseInt(rawTx.maxFeePerGas, 'hex') : parseInt(rawTx.gasPrice, 'hex')
const gasLimit = parseInt(rawTx.gasLimit, 'hex')
const totalFee = maxFeePerGas * gasLimit
return totalFee > FEE_MAX
return totalFee > maxTotalFee
}

signAndSend (req, cb) {
Expand All @@ -238,10 +237,19 @@ class Provider extends EventEmitter {
if (this.handlers[req.handlerId]) this.handlers[req.handlerId](data)
delete this.handlers[req.handlerId]
}

const payload = req.payload
const maxTotalFee = maxFee(rawTx)

if (this.feeTotalOverMax(rawTx, maxTotalFee)) {
const chainId = parseInt(rawTx.chainId).toString()
const symbol = store(`main.networks.ethereum.${chainId}.symbol`)
const displayAmount = symbol
? ` (${Math.floor(maxTotalFee / 1e18)} ${symbol})`
: ''

const err = `Max fee is over hard limit${displayAmount}`

if (this.feeTotalOverMax(rawTx)) {
const err = 'Max fee is over hard limit (2 ETH)'
this.resError(err, payload, res)
cb(new Error(err))
} else {
Expand Down
18 changes: 18 additions & 0 deletions main/transaction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ function usesBaseFee (rawTx: RawTransaction) {
return typeSupportsBaseFee(rawTx.type)
}

function maxFee (rawTx: RawTransaction) {
const chainId = parseInt(rawTx.chainId)

// for ETH-based chains, the max fee should be 2 ETH
if ([1, 3, 4, 5, 6, 10, 42, 61, 62, 63, 69].includes(chainId)) {
return 2 * 1e18
}

// for Fantom, the max fee should be 250 FTM
if ([250, 4002].includes(chainId)) {
return 250 * 1e18
}

// for all other chains, default to 10 of the chain's currency
return 10 * 1e18
}

function populate (rawTx: RawTransaction, chainConfig: Common, gas: any): TransactionData {
const txData: TransactionData = { ...rawTx }

Expand Down Expand Up @@ -139,6 +156,7 @@ async function sign (rawTx: TransactionData, signingFn: (tx: TypedTransaction) =

export {
usesBaseFee,
maxFee,
populate,
sign,
signerCompatibility,
Expand Down
1 change: 1 addition & 0 deletions test/main/accounts/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ beforeEach(() => {
handlerId: 1,
type: 'transaction',
data: {
chainId: '0x1',
gasLimit: weiToHex(21000),
gasPrice: gweiToHex(30),
type: '0x2',
Expand Down
18 changes: 17 additions & 1 deletion test/main/provider/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,24 @@ describe('#signAndSend', () => {
data: tx
}
})

it('allows a Fantom transaction with fees over the mainnet hard limit', done => {
// 200 gwei * 10M gas = 2 FTM
tx.chainId = '0xfa'
tx.type = '0x0'
tx.gasPrice = utils.parseUnits('210', 'gwei').toHexString()
tx.gasLimit = addHexPrefix((1e7).toString(16))

mockAccounts.signTransaction = () => done()

signAndSend(err => {
done('unexpected error :' + err.message)
})
})

it('does not allow a pre-EIP-1559 transaction with fees that exceed the hard limit', done => {
it('does not allow a pre-EIP-1559 transaction with fees that exceeds the hard limit', done => {
// 200 gwei * 10M gas = 2 ETH
tx.chainId = '0x1'
tx.type = '0x0'
tx.gasPrice = utils.parseUnits('210', 'gwei').toHexString()
tx.gasLimit = addHexPrefix((1e7).toString(16))
Expand All @@ -454,6 +469,7 @@ describe('#signAndSend', () => {

it('does not allow a post-EIP-1559 transaction with fees that exceed the hard limit', done => {
// 200 gwei * 10M gas = 2 ETH
tx.chainId = '0x1'
tx.type = '0x2'
tx.maxFeePerGas = utils.parseUnits('210', 'gwei').toHexString()
tx.gasLimit = addHexPrefix((1e7).toString(16))
Expand Down
28 changes: 27 additions & 1 deletion test/main/transaction/index.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util'
import Common from '@ethereumjs/common'

import { usesBaseFee, londonToLegacy, signerCompatibility, populate, sign } from '../../../main/transaction'
import { usesBaseFee, maxFee, londonToLegacy, signerCompatibility, populate, sign } from '../../../main/transaction'

describe('#signerCompatibility', () => {
it('is always compatible with legacy transactions', () => {
Expand Down Expand Up @@ -223,6 +223,32 @@ describe('#usesBaseFee', () => {
})
})

describe('#maxFee', () => {
it('sets the max fee as 2 ETH on mainnet', () => {
const tx = {
chainId: addHexPrefix((1).toString(16))
}

expect(maxFee(tx)).toBe(2e18)
})

it('sets the max fee as 1000 FTM on Fantom', () => {
const tx = {
chainId: addHexPrefix((250).toString(16))
}

expect(maxFee(tx)).toBe(250e18)
})

it('sets the max fee as 10 on other chains', () => {
const tx = {
chainId: addHexPrefix((255).toString(16))
}

expect(maxFee(tx)).toBe(1e19)
})
})

describe('#populate', () => {
let gas
const rawTx = {
Expand Down

0 comments on commit 3c44651

Please sign in to comment.