mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #3879 from MetaMask/normalize-transactions
Normalize transactions
This commit is contained in:
commit
bcb5f14b06
@ -2,6 +2,7 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- Fix bug where checksum address are messing with balance issue [#3843](https://github.com/MetaMask/metamask-extension/issues/3843)
|
||||
- new ui: fix the confirm transaction screen
|
||||
|
||||
## 4.5.2 Wed Apr 04 2018
|
||||
|
@ -185,7 +185,8 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
|
||||
async addUnapprovedTransaction (txParams) {
|
||||
// validate
|
||||
await this.txGasUtil.validateTxParams(txParams)
|
||||
this._validateTxParams(txParams)
|
||||
this._normalizeTxParams(txParams)
|
||||
// construct txMeta
|
||||
let txMeta = this.txStateManager.generateTxMeta({txParams})
|
||||
this.addTx(txMeta)
|
||||
@ -215,7 +216,6 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
}
|
||||
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
|
||||
txParams.value = txParams.value || '0x0'
|
||||
if (txParams.to === null) delete txParams.to
|
||||
// set gasLimit
|
||||
return await this.txGasUtil.analyzeGasUsage(txMeta)
|
||||
}
|
||||
@ -314,6 +314,61 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
_normalizeTxParams (txParams) {
|
||||
delete txParams.chainId
|
||||
|
||||
if ( !txParams.to ) {
|
||||
delete txParams.to
|
||||
} else {
|
||||
txParams.to = ethUtil.addHexPrefix(txParams.to)
|
||||
}
|
||||
txParams.from = ethUtil.addHexPrefix(txParams.from).toLowerCase()
|
||||
|
||||
if (!txParams.data) {
|
||||
delete txParams.data
|
||||
} else {
|
||||
txParams.data = ethUtil.addHexPrefix(txParams.data)
|
||||
}
|
||||
|
||||
if (txParams.value) txParams.value = ethUtil.addHexPrefix(txParams.value)
|
||||
|
||||
if (txParams.gas) txParams.gas = ethUtil.addHexPrefix(txParams.gas)
|
||||
if (txParams.gasPrice) txParams.gas = ethUtil.addHexPrefix(txParams.gas)
|
||||
}
|
||||
|
||||
_validateTxParams (txParams) {
|
||||
this._validateFrom(txParams)
|
||||
this._validateRecipient(txParams)
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
if (value.includes('-')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
|
||||
}
|
||||
|
||||
if (value.includes('.')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_validateFrom (txParams) {
|
||||
if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
|
||||
if (!ethUtil.isValidAddress(txParams.from)) throw new Error('Invalid from address')
|
||||
}
|
||||
|
||||
_validateRecipient (txParams) {
|
||||
if (txParams.to === '0x' || txParams.to === null ) {
|
||||
if (txParams.data) {
|
||||
delete txParams.to
|
||||
} else {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
} else if ( txParams.to !== undefined && !ethUtil.isValidAddress(txParams.to) ) {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
return txParams
|
||||
}
|
||||
|
||||
_markNonceDuplicatesDropped (txId) {
|
||||
this.txStateManager.setTxStatusConfirmed(txId)
|
||||
// get the confirmed transactions nonce and from address
|
||||
|
@ -4,7 +4,7 @@ const {
|
||||
BnMultiplyByFraction,
|
||||
bnToHex,
|
||||
} = require('./util')
|
||||
const { addHexPrefix, isValidAddress } = require('ethereumjs-util')
|
||||
const { addHexPrefix } = require('ethereumjs-util')
|
||||
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
||||
|
||||
/*
|
||||
@ -100,37 +100,4 @@ module.exports = class TxGasUtil {
|
||||
// otherwise use blockGasLimit
|
||||
return bnToHex(upperGasLimitBn)
|
||||
}
|
||||
|
||||
async validateTxParams (txParams) {
|
||||
this.validateFrom(txParams)
|
||||
this.validateRecipient(txParams)
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
if (value.includes('-')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
|
||||
}
|
||||
|
||||
if (value.includes('.')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateFrom (txParams) {
|
||||
if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
|
||||
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
|
||||
}
|
||||
|
||||
validateRecipient (txParams) {
|
||||
if (txParams.to === '0x' || txParams.to === null ) {
|
||||
if (txParams.data) {
|
||||
delete txParams.to
|
||||
} else {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
} else if ( txParams.to !== undefined && !isValidAddress(txParams.to) ) {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
return txParams
|
||||
}
|
||||
}
|
45
app/scripts/migrations/024.js
Normal file
45
app/scripts/migrations/024.js
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
const version = 24
|
||||
|
||||
/*
|
||||
|
||||
This migration ensures that the from address in txParams is to lower case for
|
||||
all unapproved transactions
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
} catch (err) {
|
||||
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||
}
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
|
||||
if (
|
||||
txMeta.status === 'unapproved' &&
|
||||
txMeta.txParams &&
|
||||
txMeta.txParams.from
|
||||
) {
|
||||
txMeta.txParams.from = txMeta.txParams.from.toLowerCase()
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
return newState
|
||||
}
|
@ -34,4 +34,5 @@ module.exports = [
|
||||
require('./021'),
|
||||
require('./022'),
|
||||
require('./023'),
|
||||
require('./024'),
|
||||
]
|
||||
|
37
test/unit/migrations/024-test.js
Normal file
37
test/unit/migrations/024-test.js
Normal file
@ -0,0 +1,37 @@
|
||||
const assert = require('assert')
|
||||
const migration24 = require('../../../app/scripts/migrations/024')
|
||||
const properTime = (new Date()).getTime()
|
||||
const storage = {
|
||||
"meta": {},
|
||||
"data": {
|
||||
"TransactionController": {
|
||||
"transactions": [
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const transactions = []
|
||||
|
||||
|
||||
while (transactions.length <= 10) {
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'unapproved' })
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' })
|
||||
}
|
||||
|
||||
|
||||
storage.data.TransactionController.transactions = transactions
|
||||
|
||||
describe('storage is migrated successfully and the txParams.from are lowercase', () => {
|
||||
it('should lowercase the from for unapproved txs', (done) => {
|
||||
migration24.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
migratedTransactions.forEach((tx) => {
|
||||
if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675')
|
||||
else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675')
|
||||
})
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
@ -210,31 +210,98 @@ describe('Transaction Controller', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxParams', function () {
|
||||
it('does not throw for positive values', function (done) {
|
||||
describe('#_validateTxParams', function () {
|
||||
it('does not throw for positive values', function () {
|
||||
var sample = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
value: '0x01',
|
||||
}
|
||||
txController.txGasUtil.validateTxParams(sample).then(() => {
|
||||
done()
|
||||
}).catch(done)
|
||||
txController._validateTxParams(sample)
|
||||
})
|
||||
|
||||
it('returns error for negative values', function (done) {
|
||||
it('returns error for negative values', function () {
|
||||
var sample = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
value: '-0x01',
|
||||
}
|
||||
txController.txGasUtil.validateTxParams(sample)
|
||||
.then(() => done('expected to thrown on negativity values but didn\'t'))
|
||||
.catch((err) => {
|
||||
try {
|
||||
txController._validateTxParams(sample)
|
||||
} catch (err) {
|
||||
assert.ok(err, 'error')
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_normalizeTxParams', () => {
|
||||
it('should normalize txParams', () => {
|
||||
let txParams = {
|
||||
chainId: '0x1',
|
||||
from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402',
|
||||
to: null,
|
||||
data: '68656c6c6f20776f726c64',
|
||||
}
|
||||
|
||||
txController._normalizeTxParams(txParams)
|
||||
|
||||
assert(!txParams.chainId, 'their should be no chainId')
|
||||
assert(!txParams.to, 'their should be no to address if null')
|
||||
assert.equal(txParams.from.slice(0, 2), '0x', 'from should be hexPrefixd')
|
||||
assert.equal(txParams.data.slice(0, 2), '0x', 'data should be hexPrefixd')
|
||||
|
||||
txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402'
|
||||
|
||||
txController._normalizeTxParams(txParams)
|
||||
assert.equal(txParams.to.slice(0, 2), '0x', 'to should be hexPrefixd')
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_validateRecipient', () => {
|
||||
it('removes recipient for txParams with 0x when contract data is provided', function () {
|
||||
const zeroRecipientandDataTxParams = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
to: '0x',
|
||||
data: 'bytecode',
|
||||
}
|
||||
const sanitizedTxParams = txController._validateRecipient(zeroRecipientandDataTxParams)
|
||||
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
|
||||
})
|
||||
|
||||
it('should error when recipient is 0x', function () {
|
||||
const zeroRecipientTxParams = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
to: '0x',
|
||||
}
|
||||
assert.throws(() => { txController._validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('#_validateFrom', () => {
|
||||
it('should error when from is not a hex string', function () {
|
||||
|
||||
// where from is undefined
|
||||
const txParams = {}
|
||||
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is array
|
||||
txParams.from = []
|
||||
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is a object
|
||||
txParams.from = {}
|
||||
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is a invalid address
|
||||
txParams.from = 'im going to fail'
|
||||
assert.throws(() => { txController._validateFrom(txParams) }, Error, `Invalid from address`)
|
||||
|
||||
// should run
|
||||
txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d'
|
||||
txController._validateFrom(txParams)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addTx', function () {
|
||||
it('should emit updates', function (done) {
|
||||
const txMeta = {
|
||||
|
@ -11,46 +11,4 @@ describe('Tx Gas Util', function () {
|
||||
provider,
|
||||
})
|
||||
})
|
||||
|
||||
it('removes recipient for txParams with 0x when contract data is provided', function () {
|
||||
const zeroRecipientandDataTxParams = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
to: '0x',
|
||||
data: 'bytecode',
|
||||
}
|
||||
const sanitizedTxParams = txGasUtil.validateRecipient(zeroRecipientandDataTxParams)
|
||||
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
|
||||
})
|
||||
|
||||
it('should error when recipient is 0x', function () {
|
||||
const zeroRecipientTxParams = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
to: '0x',
|
||||
}
|
||||
assert.throws(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
|
||||
})
|
||||
|
||||
it('should error when from is not a hex string', function () {
|
||||
|
||||
// where from is undefined
|
||||
const txParams = {}
|
||||
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is array
|
||||
txParams.from = []
|
||||
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is a object
|
||||
txParams.from = {}
|
||||
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is a invalid address
|
||||
txParams.from = 'im going to fail'
|
||||
assert.throws(() => { txGasUtil.validateFrom(txParams) }, Error, `Invalid from address`)
|
||||
|
||||
// should run
|
||||
txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d'
|
||||
txGasUtil.validateFrom(txParams)
|
||||
})
|
||||
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user