diff --git a/CHANGELOG.md b/CHANGELOG.md
index bcdfe7e04..d18f195c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
## Current Develop Branch
+## 7.7.4 Wed Jan 29 2020
+- [#7918](https://github.com/MetaMask/metamask-extension/pull/7918): Update data on Approve screen after updating custom spend limit
+- [#7919](https://github.com/MetaMask/metamask-extension/pull/7919): Allow editing max spend limit
+- [#7920](https://github.com/MetaMask/metamask-extension/pull/7920): Validate custom spend limit
+- [#7944](https://github.com/MetaMask/metamask-extension/pull/7944): Only resolve ENS on mainnet
+- [#7954](https://github.com/MetaMask/metamask-extension/pull/7954): Update ENS registry addresses
+
## 7.7.3 Fri Jan 24 2020
- [#7894](https://github.com/MetaMask/metamask-extension/pull/7894): Update GABA dependency version
- [#7901](https://github.com/MetaMask/metamask-extension/pull/7901): Use eth-contract-metadata@1.12.1
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index f947c9e5c..c9e1b02db 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -1261,6 +1261,12 @@
"message": "Spend limit requested by $1",
"description": "Origin of the site requesting the spend limit"
},
+ "spendLimitTooLarge": {
+ "message": "Spend limit too large"
+ },
+ "spendLimitInvalid": {
+ "message": "Spend limit invalid; must be a positive number"
+ },
"switchNetworks": {
"message": "Switch Networks"
},
diff --git a/app/manifest.json b/app/manifest.json
index b646d3568..2c6cbc644 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
- "version": "7.7.3",
+ "version": "7.7.4",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 2639d7703..31c8d1815 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -251,7 +251,10 @@ function setupController (initState, initLangCode) {
})
const provider = controller.provider
- setupEnsIpfsResolver({ provider })
+ setupEnsIpfsResolver({
+ getCurrentNetwork: controller.getCurrentNetwork,
+ provider,
+ })
// submit rpc requests to mesh-metrics
controller.networkController.on('rpc-req', (data) => {
diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js
index a0af263bc..09e7b5b32 100644
--- a/app/scripts/lib/ens-ipfs/resolver.js
+++ b/app/scripts/lib/ens-ipfs/resolver.js
@@ -52,19 +52,23 @@ function hexValueIsEmpty (value) {
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value)
}
+/**
+ * Returns the registry address for the given chain ID
+ * @param {number} chainId the chain ID
+ * @returns {string|null} the registry address if known, null otherwise
+ */
function getRegistryForChainId (chainId) {
switch (chainId) {
- // mainnet
case 1:
- return '0x314159265dd8dbb310642f98f50c066173c1259b'
- // ropsten
+ // falls through
case 3:
- return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
- // rinkeby
+ // falls through
case 4:
- return '0xe7410170f87102df0055eb195163a03b7f2bff4a'
- // goerli
+ // falls through
case 5:
- return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
+ // Mainnet, Ropsten, Rinkeby, and Goerli, respectively, use the same address
+ return '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'
+ default:
+ return null
}
}
diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js
index 8f19510c7..e4eddd494 100644
--- a/app/scripts/lib/ens-ipfs/setup.js
+++ b/app/scripts/lib/ens-ipfs/setup.js
@@ -6,7 +6,7 @@ const supportedTopLevelDomains = ['eth']
module.exports = setupEnsIpfsResolver
-function setupEnsIpfsResolver ({ provider }) {
+function setupEnsIpfsResolver ({ provider, getCurrentNetwork }) {
// install listener
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`)
@@ -23,7 +23,10 @@ function setupEnsIpfsResolver ({ provider }) {
async function webRequestDidFail (details) {
const { tabId, url } = details
// ignore requests that are not associated with tabs
- if (tabId === -1) return
+ // only attempt ENS resolution on mainnet
+ if (tabId === -1 || getCurrentNetwork() !== '1') {
+ return
+ }
// parse ens name
const urlData = urlUtil.parse(url)
const { hostname: name, path, search, hash: fragment } = urlData
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 9a43b8a80..15e1d6aa2 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -675,6 +675,10 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
+ getCurrentNetwork = () => {
+ return this.networkController.store.getState().network
+ }
+
/**
* Collects all the information that we want to share
* with the mobile client for syncing purposes
diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js
index 53ff473e4..f627ddaef 100644
--- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js
+++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js
@@ -1,12 +1,18 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
+import log from 'loglevel'
import Modal from '../../modal'
import Identicon from '../../../ui/identicon'
import TextField from '../../../ui/text-field'
+import { calcTokenAmount } from '../../../../helpers/utils/token-util'
import classnames from 'classnames'
+import BigNumber from 'bignumber.js'
+
+const MAX_UNSIGNED_256_INT = new BigNumber(2).pow(256).minus(1).toString(10)
export default class EditApprovalPermission extends PureComponent {
static propTypes = {
+ decimals: PropTypes.number,
hideModal: PropTypes.func.isRequired,
selectedIdentity: PropTypes.object,
tokenAmount: PropTypes.string,
@@ -14,7 +20,7 @@ export default class EditApprovalPermission extends PureComponent {
tokenSymbol: PropTypes.string,
tokenBalance: PropTypes.string,
setCustomAmount: PropTypes.func,
- origin: PropTypes.string,
+ origin: PropTypes.string.isRequired,
}
static contextTypes = {
@@ -26,7 +32,7 @@ export default class EditApprovalPermission extends PureComponent {
selectedOptionIsUnlimited: !this.props.customTokenAmount,
}
- renderModalContent () {
+ renderModalContent (error) {
const { t } = this.context
const {
hideModal,
@@ -61,7 +67,7 @@ export default class EditApprovalPermission extends PureComponent {
{ t('balance') }
- {`${tokenBalance} ${tokenSymbol}`}
+ {`${Number(tokenBalance).toPrecision(9)} ${tokenSymbol}`}
@@ -89,7 +95,7 @@ export default class EditApprovalPermission extends PureComponent {
'edit-approval-permission__edit-section__option-label--selected': selectedOptionIsUnlimited,
})}>
{
- tokenAmount < tokenBalance
+ (new BigNumber(tokenAmount)).lessThan(new BigNumber(tokenBalance))
? t('proposedApprovalLimit')
: t('unlimited')
}
@@ -98,7 +104,7 @@ export default class EditApprovalPermission extends PureComponent {
{ t('spendLimitRequestedBy', [origin]) }
- {`${tokenAmount} ${tokenSymbol}`}
+ {`${Number(tokenAmount)} ${tokenSymbol}`}
@@ -127,8 +133,7 @@ export default class EditApprovalPermission extends PureComponent {
{
this.setState({ customSpendLimit: event.target.value })
if (selectedOptionIsUnlimited) {
@@ -138,6 +143,7 @@ export default class EditApprovalPermission extends PureComponent {
fullWidth
margin="dense"
value={ this.state.customSpendLimit }
+ error={error}
/>
@@ -147,10 +153,44 @@ export default class EditApprovalPermission extends PureComponent {
)
}
+ validateSpendLimit () {
+ const { t } = this.context
+ const { decimals } = this.props
+ const { selectedOptionIsUnlimited, customSpendLimit } = this.state
+
+ if (selectedOptionIsUnlimited || !customSpendLimit) {
+ return
+ }
+
+ let customSpendLimitNumber
+ try {
+ customSpendLimitNumber = new BigNumber(customSpendLimit)
+ } catch (error) {
+ log.debug(`Error converting '${customSpendLimit}' to BigNumber:`, error)
+ return t('spendLimitInvalid')
+ }
+
+ if (customSpendLimitNumber.isNegative()) {
+ return t('spendLimitInvalid')
+ }
+
+ const maxTokenAmount = calcTokenAmount(MAX_UNSIGNED_256_INT, decimals)
+ if (customSpendLimitNumber.greaterThan(maxTokenAmount)) {
+ return t('spendLimitTooLarge')
+ }
+ }
+
render () {
const { t } = this.context
const { setCustomAmount, hideModal, customTokenAmount } = this.props
const { selectedOptionIsUnlimited, customSpendLimit } = this.state
+
+ const error = this.validateSpendLimit()
+ const disabled = Boolean(
+ (customSpendLimit === customTokenAmount && !selectedOptionIsUnlimited) ||
+ error
+ )
+
return (
{
@@ -161,9 +201,9 @@ export default class EditApprovalPermission extends PureComponent {
submitType="primary"
contentClass="edit-approval-permission-modal-content"
containerClass="edit-approval-permission-modal-container"
- submitDisabled={ (customSpendLimit === customTokenAmount) && !selectedOptionIsUnlimited }
+ submitDisabled={disabled}
>
- { this.renderModalContent() }
+ { this.renderModalContent(error) }
)
}
diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
index 38644541d..75608c16c 100644
--- a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
+++ b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
@@ -15,6 +15,7 @@ export default class ConfirmApproveContent extends Component {
static propTypes = {
amount: PropTypes.string,
txFeeTotal: PropTypes.string,
+ decimals: PropTypes.number,
tokenAmount: PropTypes.string,
customTokenAmount: PropTypes.string,
tokenSymbol: PropTypes.string,
@@ -100,7 +101,7 @@ export default class ConfirmApproveContent extends Component {
{ t('accessAndSpendNotice', [origin]) }
{ t('amountWithColon') }
-
{ `${customTokenAmount || tokenAmount} ${tokenSymbol}` }
+
{ `${Number(customTokenAmount || tokenAmount)} ${tokenSymbol}` }
{ t('toWithColon') }
@@ -124,6 +125,7 @@ export default class ConfirmApproveContent extends Component {
render () {
const { t } = this.context
const {
+ decimals,
siteImage,
tokenAmount,
customTokenAmount,
@@ -159,7 +161,15 @@ export default class ConfirmApproveContent extends Component {
>
showEditApprovalPermissionModal({ customTokenAmount, tokenAmount, tokenSymbol, setCustomAmount, tokenBalance, origin })}
+ onClick={() => showEditApprovalPermissionModal({
+ customTokenAmount,
+ decimals,
+ origin,
+ setCustomAmount,
+ tokenAmount,
+ tokenSymbol,
+ tokenBalance,
+ })}
>
{ t('editPermission') }
@@ -201,10 +211,12 @@ export default class ConfirmApproveContent extends Component {
showEdit: true,
onEditClick: () => showEditApprovalPermissionModal({
customTokenAmount,
+ decimals,
+ origin,
+ setCustomAmount,
tokenAmount,
tokenSymbol,
tokenBalance,
- setCustomAmount,
}),
})}
diff --git a/ui/app/pages/confirm-approve/confirm-approve.component.js b/ui/app/pages/confirm-approve/confirm-approve.component.js
index e8c44cd4f..786a854e7 100644
--- a/ui/app/pages/confirm-approve/confirm-approve.component.js
+++ b/ui/app/pages/confirm-approve/confirm-approve.component.js
@@ -15,7 +15,7 @@ export default class ConfirmApprove extends Component {
static propTypes = {
tokenAddress: PropTypes.string,
toAddress: PropTypes.string,
- tokenAmount: PropTypes.number,
+ tokenAmount: PropTypes.string,
tokenSymbol: PropTypes.string,
fiatTransactionTotal: PropTypes.string,
ethTransactionTotal: PropTypes.string,
@@ -33,7 +33,7 @@ export default class ConfirmApprove extends Component {
}
static defaultProps = {
- tokenAmount: 0,
+ tokenAmount: '0',
}
state = {
@@ -69,12 +69,16 @@ export default class ConfirmApprove extends Component {
} = this.props
const { customPermissionAmount } = this.state
- const tokensText = `${tokenAmount} ${tokenSymbol}`
+ const tokensText = `${Number(tokenAmount)} ${tokenSymbol}`
const tokenBalance = tokenTrackerBalance
- ? Number(calcTokenAmount(tokenTrackerBalance, decimals)).toPrecision(9)
+ ? calcTokenAmount(tokenTrackerBalance, decimals).toString(10)
: ''
+ const customData = customPermissionAmount
+ ? getCustomTxParamsData(data, { customPermissionAmount, decimals })
+ : null
+
return (
{
this.setState({ customPermissionAmount: newAmount })
}}
customTokenAmount={String(customPermissionAmount)}
- tokenAmount={String(tokenAmount)}
+ tokenAmount={tokenAmount}
origin={origin}
tokenSymbol={tokenSymbol}
tokenBalance={tokenBalance}
showCustomizeGasModal={() => showCustomizeGasModal(txData)}
showEditApprovalPermissionModal={showEditApprovalPermissionModal}
- data={data}
+ data={customData || data}
toAddress={toAddress}
currentCurrency={currentCurrency}
ethTransactionTotal={ethTransactionTotal}
fiatTransactionTotal={fiatTransactionTotal}
/>}
hideSenderToRecipient
- customTxParamsData={customPermissionAmount
- ? getCustomTxParamsData(data, { customPermissionAmount, tokenAmount, decimals })
- : null
- }
+ customTxParamsData={customData}
{...restProps}
/>
)
diff --git a/ui/app/pages/confirm-approve/confirm-approve.container.js b/ui/app/pages/confirm-approve/confirm-approve.container.js
index 43f5aab90..185c0f9c6 100644
--- a/ui/app/pages/confirm-approve/confirm-approve.container.js
+++ b/ui/app/pages/confirm-approve/confirm-approve.container.js
@@ -43,7 +43,7 @@ const mapStateToProps = (state, ownProps) => {
const tokenData = getTokenData(data)
const tokenValue = tokenData && getTokenValue(tokenData.params)
const toAddress = tokenData && getTokenToAddress(tokenData.params)
- const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toNumber()
+ const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toString(10)
const contractExchangeRate = contractExchangeRateSelector(state)
const { origin } = transaction
@@ -76,20 +76,22 @@ const mapDispatchToProps = (dispatch) => {
return {
showCustomizeGasModal: (txData) => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })),
showEditApprovalPermissionModal: ({
- tokenAmount,
customTokenAmount,
- tokenSymbol,
- tokenBalance,
- setCustomAmount,
+ decimals,
origin,
+ setCustomAmount,
+ tokenAmount,
+ tokenBalance,
+ tokenSymbol,
}) => dispatch(showModal({
name: 'EDIT_APPROVAL_PERMISSION',
- tokenAmount,
customTokenAmount,
- tokenSymbol,
- tokenBalance,
- setCustomAmount,
+ decimals,
origin,
+ setCustomAmount,
+ tokenAmount,
+ tokenBalance,
+ tokenSymbol,
})),
}
}
diff --git a/ui/app/pages/confirm-approve/confirm-approve.util.js b/ui/app/pages/confirm-approve/confirm-approve.util.js
index be77c65f9..0318c6bed 100644
--- a/ui/app/pages/confirm-approve/confirm-approve.util.js
+++ b/ui/app/pages/confirm-approve/confirm-approve.util.js
@@ -1,28 +1,33 @@
import { decimalToHex } from '../../helpers/utils/conversions.util'
import { calcTokenValue } from '../../helpers/utils/token-util.js'
+import { getTokenData } from '../../helpers/utils/transactions.util'
-export function getCustomTxParamsData (data, { customPermissionAmount, tokenAmount, decimals }) {
- if (customPermissionAmount) {
- const tokenValue = decimalToHex(calcTokenValue(tokenAmount, decimals))
+export function getCustomTxParamsData (data, { customPermissionAmount, decimals }) {
+ const tokenData = getTokenData(data)
- const re = new RegExp('(^.+)' + tokenValue + '$')
- const matches = re.exec(data)
-
- if (!matches || !matches[1]) {
- return data
- }
- let dataWithoutCurrentAmount = matches[1]
- const customPermissionValue = decimalToHex(calcTokenValue(Number(customPermissionAmount), decimals))
+ if (!tokenData) {
+ throw new Error('Invalid data')
+ } else if (tokenData.name !== 'approve') {
+ throw new Error(`Invalid data; should be 'approve' method, but instead is '${tokenData.name}'`)
+ }
+ let spender = tokenData.params[0].value
+ if (spender.startsWith('0x')) {
+ spender = spender.substring(2)
+ }
+ const [signature, tokenValue] = data.split(spender)
- const differenceInLengths = customPermissionValue.length - tokenValue.length
- const zeroModifier = dataWithoutCurrentAmount.length - differenceInLengths
- if (differenceInLengths > 0) {
- dataWithoutCurrentAmount = dataWithoutCurrentAmount.slice(0, zeroModifier)
- } else if (differenceInLengths < 0) {
- dataWithoutCurrentAmount = dataWithoutCurrentAmount.padEnd(zeroModifier, 0)
- }
+ if (!signature || !tokenValue) {
+ throw new Error('Invalid data')
+ } else if (tokenValue.length !== 64) {
+ throw new Error('Invalid token value; should be exactly 64 hex digits long (u256)')
+ }
- const customTxParamsData = dataWithoutCurrentAmount + customPermissionValue
- return customTxParamsData
+ let customPermissionValue = decimalToHex(calcTokenValue(customPermissionAmount, decimals))
+ if (customPermissionValue.length > 64) {
+ throw new Error('Custom value is larger than u256')
}
+
+ customPermissionValue = customPermissionValue.padStart(tokenValue.length, '0')
+ const customTxParamsData = `${signature}${spender}${customPermissionValue}`
+ return customTxParamsData
}
diff --git a/yarn.lock b/yarn.lock
index 74d5eb8f6..5f03bb146 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7576,7 +7576,7 @@ cross-spawn@^4:
lru-cache "^4.0.1"
which "^1.2.9"
-cross-spawn@^5.0.1, cross-spawn@^5.1.0:
+cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
@@ -10370,9 +10370,9 @@ ethereum-common@^0.0.18:
integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=
ethereum-ens-network-map@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.0.tgz#43cd7669ce950a789e151001118d4d65f210eeb7"
- integrity sha1-Q812ac6VCnieFRABEY1NZfIQ7rc=
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.2.tgz#4e27bad18dae7bd95d84edbcac2c9e739fc959b9"
+ integrity sha512-5qwJ5n3YhjSpE6O/WEBXCAb2nagUgyagJ6C0lGUBWC4LjKp/rRzD+pwtDJ6KCiITFEAoX4eIrWOjRy0Sylq5Hg==
ethereumjs-abi@0.6.5, ethereumjs-abi@^0.6.4, ethereumjs-abi@^0.6.5:
version "0.6.5"