mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'master' of https://github.com/MetaMask/metamask-extension into cb-254
This commit is contained in:
commit
4c15c0d3ba
@ -20,6 +20,7 @@ const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
||||
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
||||
const EdgeEncryptor = require('./edge-encryptor')
|
||||
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
|
||||
const getObjStructure = require('./lib/getObjStructure')
|
||||
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
@ -77,6 +78,16 @@ async function loadStateFromPersistence () {
|
||||
diskStore.getState() ||
|
||||
migrator.generateInitialState(firstTimeState)
|
||||
|
||||
// report migration errors to sentry
|
||||
migrator.on('error', (err) => {
|
||||
// get vault structure without secrets
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
raven.captureException(err, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
})
|
||||
|
||||
// migrate data
|
||||
versionedData = await migrator.migrateData(versionedData)
|
||||
if (!versionedData) {
|
||||
@ -84,7 +95,14 @@ async function loadStateFromPersistence () {
|
||||
}
|
||||
|
||||
// write to disk
|
||||
if (localStore.isSupported) localStore.set(versionedData)
|
||||
if (localStore.isSupported) {
|
||||
localStore.set(versionedData)
|
||||
} else {
|
||||
// throw in setTimeout so as to not block boot
|
||||
setTimeout(() => {
|
||||
throw new Error('MetaMask - Localstore not supported')
|
||||
})
|
||||
}
|
||||
|
||||
// return just the data
|
||||
return versionedData.data
|
||||
|
@ -185,10 +185,10 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
|
||||
async addUnapprovedTransaction (txParams) {
|
||||
// validate
|
||||
this._validateTxParams(txParams)
|
||||
this._normalizeTxParams(txParams)
|
||||
const normalizedTxParams = this._normalizeTxParams(txParams)
|
||||
this._validateTxParams(normalizedTxParams)
|
||||
// construct txMeta
|
||||
let txMeta = this.txStateManager.generateTxMeta({txParams})
|
||||
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
|
||||
this.addTx(txMeta)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
// add default tx params
|
||||
@ -315,25 +315,24 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
//
|
||||
|
||||
_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)
|
||||
// functions that handle normalizing of that key in txParams
|
||||
const whiteList = {
|
||||
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
|
||||
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
|
||||
nonce: nonce => ethUtil.addHexPrefix(nonce),
|
||||
value: value => ethUtil.addHexPrefix(value),
|
||||
data: data => ethUtil.addHexPrefix(data),
|
||||
gas: gas => ethUtil.addHexPrefix(gas),
|
||||
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
|
||||
}
|
||||
|
||||
if (txParams.value) txParams.value = ethUtil.addHexPrefix(txParams.value)
|
||||
// apply only keys in the whiteList
|
||||
const normalizedTxParams = {}
|
||||
Object.keys(whiteList).forEach((key) => {
|
||||
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
|
||||
})
|
||||
|
||||
if (txParams.gas) txParams.gas = ethUtil.addHexPrefix(txParams.gas)
|
||||
if (txParams.gasPrice) txParams.gas = ethUtil.addHexPrefix(txParams.gas)
|
||||
return normalizedTxParams
|
||||
}
|
||||
|
||||
_validateTxParams (txParams) {
|
||||
|
@ -2,14 +2,16 @@ const extension = require('extensionizer')
|
||||
const promisify = require('pify')
|
||||
const allLocales = require('../../_locales/index.json')
|
||||
|
||||
const existingLocaleCodes = allLocales.map(locale => locale.code)
|
||||
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
|
||||
|
||||
async function getFirstPreferredLangCode () {
|
||||
const userPreferredLocaleCodes = await promisify(
|
||||
extension.i18n.getAcceptLanguages,
|
||||
{ errorFirst: false }
|
||||
)()
|
||||
const firstPreferredLangCode = userPreferredLocaleCodes.find(code => existingLocaleCodes.includes(code))
|
||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||
.map(code => code.toLowerCase())
|
||||
.find(code => existingLocaleCodes.includes(code))
|
||||
return firstPreferredLangCode || 'en'
|
||||
}
|
||||
|
||||
|
33
app/scripts/lib/getObjStructure.js
Normal file
33
app/scripts/lib/getObjStructure.js
Normal file
@ -0,0 +1,33 @@
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = getObjStructure
|
||||
|
||||
// This will create an object that represents the structure of the given object
|
||||
// it replaces all values with the result of their type
|
||||
|
||||
// {
|
||||
// "data": {
|
||||
// "CurrencyController": {
|
||||
// "conversionDate": "number",
|
||||
// "conversionRate": "number",
|
||||
// "currentCurrency": "string"
|
||||
// }
|
||||
// }
|
||||
|
||||
function getObjStructure(obj) {
|
||||
const structure = clone(obj)
|
||||
return deepMap(structure, (value) => {
|
||||
return value === null ? 'null' : typeof value
|
||||
})
|
||||
}
|
||||
|
||||
function deepMap(target = {}, visit) {
|
||||
Object.entries(target).forEach(([key, value]) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
target[key] = deepMap(value, visit)
|
||||
} else {
|
||||
target[key] = visit(value)
|
||||
}
|
||||
})
|
||||
return target
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
class Migrator {
|
||||
const EventEmitter = require('events')
|
||||
|
||||
class Migrator extends EventEmitter {
|
||||
|
||||
constructor (opts = {}) {
|
||||
super()
|
||||
const migrations = opts.migrations || []
|
||||
// sort migrations by version
|
||||
this.migrations = migrations.sort((a, b) => a.version - b.version)
|
||||
@ -12,13 +15,29 @@ class Migrator {
|
||||
|
||||
// run all pending migrations on meta in place
|
||||
async migrateData (versionedData = this.generateInitialState()) {
|
||||
// get all migrations that have not yet been run
|
||||
const pendingMigrations = this.migrations.filter(migrationIsPending)
|
||||
|
||||
// perform each migration
|
||||
for (const index in pendingMigrations) {
|
||||
const migration = pendingMigrations[index]
|
||||
versionedData = await migration.migrate(versionedData)
|
||||
if (!versionedData.data) throw new Error('Migrator - migration returned empty data')
|
||||
if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
|
||||
try {
|
||||
// attempt migration and validate
|
||||
const migratedData = await migration.migrate(versionedData)
|
||||
if (!migratedData.data) throw new Error('Migrator - migration returned empty data')
|
||||
if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
|
||||
// accept the migration as good
|
||||
versionedData = migratedData
|
||||
} catch (err) {
|
||||
// rewrite error message to add context without clobbering stack
|
||||
const originalErrorMessage = err.message
|
||||
err.message = `MetaMask Migration Error #${migration.version}: ${originalErrorMessage}`
|
||||
console.warn(err.stack)
|
||||
// emit error instead of throw so as to not break the run (gracefully fail)
|
||||
this.emit('error', err)
|
||||
// stop migrating and use state as is
|
||||
return versionedData
|
||||
}
|
||||
}
|
||||
|
||||
return versionedData
|
||||
|
@ -108,6 +108,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
||||
updateTx (txMeta, note) {
|
||||
// validate txParams
|
||||
if (txMeta.txParams) {
|
||||
if (typeof txMeta.txParams.data === 'undefined') {
|
||||
delete txMeta.txParams.data
|
||||
}
|
||||
|
||||
this.validateTxParams(txMeta.txParams)
|
||||
}
|
||||
|
||||
|
@ -13,24 +13,20 @@ const clone = require('clone')
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
migrate: async 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)
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
if (!newState.TransactionController) return newState
|
||||
const transactions = newState.TransactionController.transactions
|
||||
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
|
||||
if (
|
||||
txMeta.status === 'unapproved' &&
|
||||
|
61
app/scripts/migrations/025.js
Normal file
61
app/scripts/migrations/025.js
Normal file
@ -0,0 +1,61 @@
|
||||
// next version number
|
||||
const version = 25
|
||||
|
||||
/*
|
||||
|
||||
normalizes txParams on unconfirmed txs
|
||||
|
||||
*/
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
|
||||
if (newState.TransactionController) {
|
||||
if (newState.TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (txMeta.status !== 'unapproved') return txMeta
|
||||
txMeta.txParams = normalizeTxParams(txMeta.txParams)
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
function normalizeTxParams (txParams) {
|
||||
// functions that handle normalizing of that key in txParams
|
||||
const whiteList = {
|
||||
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
|
||||
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
|
||||
nonce: nonce => ethUtil.addHexPrefix(nonce),
|
||||
value: value => ethUtil.addHexPrefix(value),
|
||||
data: data => ethUtil.addHexPrefix(data),
|
||||
gas: gas => ethUtil.addHexPrefix(gas),
|
||||
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
|
||||
}
|
||||
|
||||
// apply only keys in the whiteList
|
||||
const normalizedTxParams = {}
|
||||
Object.keys(whiteList).forEach((key) => {
|
||||
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
|
||||
})
|
||||
|
||||
return normalizedTxParams
|
||||
}
|
@ -35,4 +35,5 @@ module.exports = [
|
||||
require('./022'),
|
||||
require('./023'),
|
||||
require('./024'),
|
||||
require('./025'),
|
||||
]
|
||||
|
29
app/scripts/migrations/template.js
Normal file
29
app/scripts/migrations/template.js
Normal file
@ -0,0 +1,29 @@
|
||||
// next version number
|
||||
const version = 0
|
||||
|
||||
/*
|
||||
|
||||
description of migration and what it does
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
// transform state here
|
||||
return newState
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
const assert = require('assert')
|
||||
const migration24 = require('../../../app/scripts/migrations/024')
|
||||
const firstTimeState = {
|
||||
meta: {},
|
||||
data: require('../../../app/scripts/first-time-state'),
|
||||
}
|
||||
const properTime = (new Date()).getTime()
|
||||
const storage = {
|
||||
"meta": {},
|
||||
@ -34,4 +38,12 @@ describe('storage is migrated successfully and the txParams.from are lowercase',
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should migrate first time state', (done) => {
|
||||
migration24.migrate(firstTimeState)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, 24)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
|
49
test/unit/migrations/025-test.js
Normal file
49
test/unit/migrations/025-test.js
Normal file
@ -0,0 +1,49 @@
|
||||
const assert = require('assert')
|
||||
const migration25 = require('../../../app/scripts/migrations/025')
|
||||
const firstTimeState = {
|
||||
meta: {},
|
||||
data: require('../../../app/scripts/first-time-state'),
|
||||
}
|
||||
|
||||
const storage = {
|
||||
"meta": {},
|
||||
"data": {
|
||||
"TransactionController": {
|
||||
"transactions": [
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const transactions = []
|
||||
|
||||
|
||||
while (transactions.length <= 10) {
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', random: 'stuff', chainId: 2 }, 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) => {
|
||||
migration25.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
migratedTransactions.forEach((tx) => {
|
||||
if (tx.status === 'unapproved') assert(!tx.txParams.random)
|
||||
if (tx.status === 'unapproved') assert(!tx.txParams.chainId)
|
||||
})
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should migrate first time state', (done) => {
|
||||
migration25.migrate(firstTimeState)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, 25)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
17
test/unit/migrations/template-test.js
Normal file
17
test/unit/migrations/template-test.js
Normal file
@ -0,0 +1,17 @@
|
||||
const assert = require('assert')
|
||||
const migrationTemplate = require('../../../app/scripts/migrations/template')
|
||||
const properTime = (new Date()).getTime()
|
||||
const storage = {
|
||||
meta: {},
|
||||
data: {},
|
||||
}
|
||||
|
||||
describe('storage is migrated successfully', () => {
|
||||
it('should work', (done) => {
|
||||
migrationTemplate.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, 0)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
@ -1,7 +1,8 @@
|
||||
const assert = require('assert')
|
||||
const clone = require('clone')
|
||||
const Migrator = require('../../app/scripts/lib/migrator/')
|
||||
const migrations = [
|
||||
const liveMigrations = require('../../app/scripts/migrations/')
|
||||
const stubMigrations = [
|
||||
{
|
||||
version: 1,
|
||||
migrate: (data) => {
|
||||
@ -29,13 +30,31 @@ const migrations = [
|
||||
},
|
||||
]
|
||||
const versionedData = {meta: {version: 0}, data: {hello: 'world'}}
|
||||
|
||||
const firstTimeState = {
|
||||
meta: { version: 0 },
|
||||
data: require('../../app/scripts/first-time-state'),
|
||||
}
|
||||
|
||||
describe('Migrator', () => {
|
||||
const migrator = new Migrator({ migrations })
|
||||
const migrator = new Migrator({ migrations: stubMigrations })
|
||||
it('migratedData version should be version 3', (done) => {
|
||||
migrator.migrateData(versionedData)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, migrations[2].version)
|
||||
assert.equal(migratedData.meta.version, stubMigrations[2].version)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should match the last version in live migrations', (done) => {
|
||||
const migrator = new Migrator({ migrations: liveMigrations })
|
||||
migrator.migrateData(firstTimeState)
|
||||
.then((migratedData) => {
|
||||
console.log(migratedData)
|
||||
const last = liveMigrations.length - 1
|
||||
assert.equal(migratedData.meta.version, liveMigrations[last].version)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -239,19 +239,20 @@ describe('Transaction Controller', function () {
|
||||
from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402',
|
||||
to: null,
|
||||
data: '68656c6c6f20776f726c64',
|
||||
random: 'hello world',
|
||||
}
|
||||
|
||||
txController._normalizeTxParams(txParams)
|
||||
let normalizedTxParams = 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')
|
||||
assert(!normalizedTxParams.chainId, 'their should be no chainId')
|
||||
assert(!normalizedTxParams.to, 'their should be no to address if null')
|
||||
assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hexPrefixd')
|
||||
assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hexPrefixd')
|
||||
assert(!('random' in normalizedTxParams), 'their should be no random key in normalizedTxParams')
|
||||
|
||||
txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402'
|
||||
|
||||
txController._normalizeTxParams(txParams)
|
||||
assert.equal(txParams.to.slice(0, 2), '0x', 'to should be hexPrefixd')
|
||||
normalizedTxParams = txController._normalizeTxParams(txParams)
|
||||
assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hexPrefixd')
|
||||
|
||||
})
|
||||
})
|
||||
|
@ -35,6 +35,7 @@ function mapDispatchToProps (dispatch) {
|
||||
|
||||
inherits(PrivateKeyImportView, Component)
|
||||
function PrivateKeyImportView () {
|
||||
this.createKeyringOnEnter = this.createKeyringOnEnter.bind(this)
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
@ -51,7 +52,7 @@ PrivateKeyImportView.prototype.render = function () {
|
||||
h('input.new-account-import-form__input-password', {
|
||||
type: 'password',
|
||||
id: 'private-key-box',
|
||||
onKeyPress: () => this.createKeyringOnEnter(),
|
||||
onKeyPress: e => this.createKeyringOnEnter(e),
|
||||
}),
|
||||
|
||||
]),
|
||||
|
@ -577,12 +577,17 @@ SendTransactionScreen.prototype.getEditedTx = function () {
|
||||
data,
|
||||
})
|
||||
} else {
|
||||
const data = unapprovedTxs[editingTransactionId].txParams.data
|
||||
const { data } = unapprovedTxs[editingTransactionId].txParams
|
||||
|
||||
Object.assign(editingTx.txParams, {
|
||||
value: ethUtil.addHexPrefix(amount),
|
||||
to: ethUtil.addHexPrefix(to),
|
||||
data,
|
||||
})
|
||||
|
||||
if (typeof editingTx.txParams.data === 'undefined') {
|
||||
delete editingTx.txParams.data
|
||||
}
|
||||
}
|
||||
|
||||
return editingTx
|
||||
|
Loading…
Reference in New Issue
Block a user