Fix calculation of data property for gas estimation on token transfers.

feature/default_network_editable
Dan 7 years ago
parent 701611e317
commit 1b879f45bc
  1. 2
      ui/app/actions.js
  2. 3
      ui/app/components/send_/send.component.js
  3. 8
      ui/app/components/send_/send.container.js
  4. 23
      ui/app/components/send_/send.utils.js
  5. 2
      ui/app/components/send_/tests/send-component.test.js
  6. 7
      ui/app/components/send_/tests/send-container.test.js
  7. 41
      ui/app/components/send_/tests/send-utils.test.js

@ -732,7 +732,6 @@ function setGasTotal (gasTotal) {
function updateGasData ({ function updateGasData ({
blockGasLimit, blockGasLimit,
data,
recentBlocks, recentBlocks,
selectedAddress, selectedAddress,
selectedToken, selectedToken,
@ -746,7 +745,6 @@ function updateGasData ({
estimateGas({ estimateGas({
estimateGasMethod: background.estimateGas, estimateGasMethod: background.estimateGas,
blockGasLimit, blockGasLimit,
data,
selectedAddress, selectedAddress,
selectedToken, selectedToken,
to, to,

@ -20,7 +20,6 @@ export default class SendTransactionScreen extends PersistentForm {
]), ]),
blockGasLimit: PropTypes.string, blockGasLimit: PropTypes.string,
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
data: PropTypes.string,
editingTransactionId: PropTypes.string, editingTransactionId: PropTypes.string,
from: PropTypes.object, from: PropTypes.object,
gasLimit: PropTypes.string, gasLimit: PropTypes.string,
@ -43,7 +42,6 @@ export default class SendTransactionScreen extends PersistentForm {
const { const {
amount, amount,
blockGasLimit, blockGasLimit,
data,
editingTransactionId, editingTransactionId,
gasLimit, gasLimit,
gasPrice, gasPrice,
@ -55,7 +53,6 @@ export default class SendTransactionScreen extends PersistentForm {
updateAndSetGasTotal({ updateAndSetGasTotal({
blockGasLimit, blockGasLimit,
data,
editingTransactionId, editingTransactionId,
gasLimit, gasLimit,
gasPrice, gasPrice,

@ -31,7 +31,6 @@ import {
} from '../../ducks/send.duck' } from '../../ducks/send.duck'
import { import {
calcGasTotal, calcGasTotal,
generateTokenTransferData,
} from './send.utils.js' } from './send.utils.js'
module.exports = compose( module.exports = compose(
@ -40,15 +39,11 @@ module.exports = compose(
)(SendEther) )(SendEther)
function mapStateToProps (state) { function mapStateToProps (state) {
const selectedAddress = getSelectedAddress(state)
const selectedToken = getSelectedToken(state)
return { return {
amount: getSendAmount(state), amount: getSendAmount(state),
amountConversionRate: getAmountConversionRate(state), amountConversionRate: getAmountConversionRate(state),
blockGasLimit: getBlockGasLimit(state), blockGasLimit: getBlockGasLimit(state),
conversionRate: getConversionRate(state), conversionRate: getConversionRate(state),
data: generateTokenTransferData(selectedAddress, selectedToken),
editingTransactionId: getSendEditingTransactionId(state), editingTransactionId: getSendEditingTransactionId(state),
from: getSendFromObject(state), from: getSendFromObject(state),
gasLimit: getGasLimit(state), gasLimit: getGasLimit(state),
@ -69,7 +64,6 @@ function mapDispatchToProps (dispatch) {
return { return {
updateAndSetGasTotal: ({ updateAndSetGasTotal: ({
blockGasLimit, blockGasLimit,
data,
editingTransactionId, editingTransactionId,
gasLimit, gasLimit,
gasPrice, gasPrice,
@ -80,7 +74,7 @@ function mapDispatchToProps (dispatch) {
value, value,
}) => { }) => {
!editingTransactionId !editingTransactionId
? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data, blockGasLimit, to, value })) ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value }))
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
}, },
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {

@ -14,6 +14,7 @@ const {
NEGATIVE_ETH_ERROR, NEGATIVE_ETH_ERROR,
ONE_GWEI_IN_WEI_HEX, ONE_GWEI_IN_WEI_HEX,
SIMPLE_GAS_COST, SIMPLE_GAS_COST,
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
} = require('./send.constants') } = require('./send.constants')
const abi = require('ethereumjs-abi') const abi = require('ethereumjs-abi')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
@ -165,26 +166,23 @@ function doesAmountErrorRequireUpdate ({
return amountErrorRequiresUpdate return amountErrorRequiresUpdate
} }
async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimit, to, value, gasPrice, estimateGasMethod }) { async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, value, gasPrice, estimateGasMethod }) {
const { symbol } = selectedToken || {}
const paramsForGasEstimate = { from: selectedAddress, value, gasPrice } const paramsForGasEstimate = { from: selectedAddress, value, gasPrice }
if (symbol) { if (selectedToken) {
Object.assign(paramsForGasEstimate, { value: '0x0' }) paramsForGasEstimate.value = '0x0'
paramsForGasEstimate.data = generateTokenTransferData({ toAddress: to, amount: value, selectedToken })
} }
if (data) {
Object.assign(paramsForGasEstimate, { data })
}
// if recipient has no code, gas is 21k max: // if recipient has no code, gas is 21k max:
const hasRecipient = Boolean(to) const hasRecipient = Boolean(to)
let code let code
if (hasRecipient) code = await global.eth.getCode(to) if (hasRecipient) code = await global.eth.getCode(to)
if (hasRecipient && (!code || code === '0x')) { if (hasRecipient && (!code || code === '0x') && !selectedToken) {
return SIMPLE_GAS_COST return SIMPLE_GAS_COST
} }
paramsForGasEstimate.to = to paramsForGasEstimate.to = selectedToken ? selectedToken.address : to
// if not, fall back to block gasLimit // if not, fall back to block gasLimit
paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, { paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, {
@ -212,11 +210,10 @@ async function estimateGas ({ selectedAddress, selectedToken, data, blockGasLimi
}) })
} }
function generateTokenTransferData (selectedAddress, selectedToken) { function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) {
if (!selectedToken) return if (!selectedToken) return
console.log(`abi.rawEncode`, abi.rawEncode) return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
return Array.prototype.map.call( abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
x => ('00' + x.toString(16)).slice(-2) x => ('00' + x.toString(16)).slice(-2)
).join('') ).join('')
} }

@ -34,7 +34,6 @@ describe('Send Component', function () {
amountConversionRate={'mockAmountConversionRate'} amountConversionRate={'mockAmountConversionRate'}
blockGasLimit={'mockBlockGasLimit'} blockGasLimit={'mockBlockGasLimit'}
conversionRate={10} conversionRate={10}
data={'mockData'}
editingTransactionId={'mockEditingTransactionId'} editingTransactionId={'mockEditingTransactionId'}
from={ { address: 'mockAddress', balance: 'mockBalance' } } from={ { address: 'mockAddress', balance: 'mockBalance' } }
gasLimit={'mockGasLimit'} gasLimit={'mockGasLimit'}
@ -210,7 +209,6 @@ describe('Send Component', function () {
propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0], propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0],
{ {
blockGasLimit: 'mockBlockGasLimit', blockGasLimit: 'mockBlockGasLimit',
data: 'mockData',
editingTransactionId: 'mockEditingTransactionId', editingTransactionId: 'mockEditingTransactionId',
gasLimit: 'mockGasLimit', gasLimit: 'mockGasLimit',
gasPrice: 'mockGasPrice', gasPrice: 'mockGasPrice',

@ -47,7 +47,6 @@ proxyquire('../send.container.js', {
'../../ducks/send.duck': duckActionSpies, '../../ducks/send.duck': duckActionSpies,
'./send.utils.js': { './send.utils.js': {
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice, calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
generateTokenTransferData: (a, b) => `mockData:${a + b}`,
}, },
}) })
@ -61,7 +60,6 @@ describe('send container', () => {
amountConversionRate: 'mockAmountConversionRate:mockState', amountConversionRate: 'mockAmountConversionRate:mockState',
blockGasLimit: 'mockBlockGasLimit:mockState', blockGasLimit: 'mockBlockGasLimit:mockState',
conversionRate: 'mockConversionRate:mockState', conversionRate: 'mockConversionRate:mockState',
data: 'mockData:mockSelectedAddress:mockStatemockSelectedToken:mockState',
editingTransactionId: 'mockEditingTransactionId:mockState', editingTransactionId: 'mockEditingTransactionId:mockState',
from: 'mockFrom:mockState', from: 'mockFrom:mockState',
gasLimit: 'mockGasLimit:mockState', gasLimit: 'mockGasLimit:mockState',
@ -92,7 +90,6 @@ describe('send container', () => {
describe('updateAndSetGasTotal()', () => { describe('updateAndSetGasTotal()', () => {
const mockProps = { const mockProps = {
blockGasLimit: 'mockBlockGasLimit', blockGasLimit: 'mockBlockGasLimit',
data: '0x1',
editingTransactionId: '0x2', editingTransactionId: '0x2',
gasLimit: '0x3', gasLimit: '0x3',
gasPrice: '0x4', gasPrice: '0x4',
@ -113,14 +110,14 @@ describe('send container', () => {
}) })
it('should dispatch an updateGasData action when editingTransactionId is falsy', () => { it('should dispatch an updateGasData action when editingTransactionId is falsy', () => {
const { selectedAddress, selectedToken, data, recentBlocks, blockGasLimit, to, value } = mockProps const { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value } = mockProps
mapDispatchToPropsObject.updateAndSetGasTotal( mapDispatchToPropsObject.updateAndSetGasTotal(
Object.assign({}, mockProps, {editingTransactionId: false}) Object.assign({}, mockProps, {editingTransactionId: false})
) )
assert(dispatchSpy.calledOnce) assert(dispatchSpy.calledOnce)
assert.deepEqual( assert.deepEqual(
actionSpies.updateGasData.getCall(0).args[0], actionSpies.updateGasData.getCall(0).args[0],
{ selectedAddress, selectedToken, data, recentBlocks, blockGasLimit, to, value } { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value }
) )
}) })
}) })

@ -106,11 +106,23 @@ describe('send utils', () => {
describe('generateTokenTransferData()', () => { describe('generateTokenTransferData()', () => {
it('should return undefined if not passed a selected token', () => { it('should return undefined if not passed a selected token', () => {
assert.equal(generateTokenTransferData('mockAddress', false), undefined) assert.equal(generateTokenTransferData({ toAddress: 'mockAddress', amount: '0xa', selectedToken: false}), undefined)
})
it('should call abi.rawEncode with the correct params', () => {
stubs.rawEncode.resetHistory()
generateTokenTransferData({ toAddress: 'mockAddress', amount: 'ab', selectedToken: true})
assert.deepEqual(
stubs.rawEncode.getCall(0).args,
[['address', 'uint256'], ['mockAddress', '0xab']]
)
}) })
it('should return encoded token transfer data', () => { it('should return encoded token transfer data', () => {
assert.equal(generateTokenTransferData('mockAddress', true), '104c') assert.equal(
generateTokenTransferData({ toAddress: 'mockAddress', amount: '0xa', selectedToken: true}),
'0xa9059cbb104c'
)
}) })
}) })
@ -276,22 +288,17 @@ describe('send utils', () => {
assert.equal(result, 'mockToString:16') assert.equal(result, 'mockToString:16')
}) })
it('should call ethQuery.estimateGas with a value of 0x0 if the passed selectedToken has a symbol', async () => { it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => {
const result = await estimateGas(Object.assign({ selectedToken: { symbol: true } }, baseMockParams)) const result = await estimateGas(Object.assign({ data: 'mockData', selectedToken: { address: 'mockAddress' } }, baseMockParams))
assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual( assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0], baseMockParams.estimateGasMethod.getCall(0).args[0],
Object.assign({ gasPrice: undefined, value: '0x0' }, baseExpectedCall) Object.assign({}, baseExpectedCall, {
) gasPrice: undefined,
assert.equal(result, 'mockToString:16') value: '0x0',
data: '0xa9059cbb104c',
to: 'mockAddress',
}) })
it('should call ethQuery.estimateGas with data if data is passed', async () => {
const result = await estimateGas(Object.assign({ data: 'mockData' }, baseMockParams))
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0],
Object.assign({ gasPrice: undefined, value: undefined, data: 'mockData' }, baseExpectedCall)
) )
assert.equal(result, 'mockToString:16') assert.equal(result, 'mockToString:16')
}) })
@ -302,6 +309,12 @@ describe('send utils', () => {
assert.equal(result, SIMPLE_GAS_COST) assert.equal(result, SIMPLE_GAS_COST)
}) })
it(`should not return ${SIMPLE_GAS_COST} if passed a selectedToken`, async () => {
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123', selectedToken: { address: '' } }))
assert.notEqual(result, SIMPLE_GAS_COST)
})
it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => { it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => {
const result = await estimateGas(Object.assign({}, baseMockParams, { const result = await estimateGas(Object.assign({}, baseMockParams, {
to: 'isContract willFailBecauseOf:Transaction execution error.', to: 'isContract willFailBecauseOf:Transaction execution error.',

Loading…
Cancel
Save