Adding a warning when sending a token to its own contract address (#10546)

Fixes MetaMask/metamask-extension#9437
feature/default_network_editable
ryanml 4 years ago committed by GitHub
parent eef92d0d5a
commit 3c6cdef074
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/_locales/en/messages.json
  2. 7
      ui/app/helpers/utils/util.js
  3. 32
      ui/app/helpers/utils/util.test.js
  4. 6
      ui/app/pages/send/send-content/add-recipient/add-recipient.js
  5. 13
      ui/app/pages/send/send-content/add-recipient/tests/add-recipient-utils.test.js
  6. 15
      ui/app/pages/send/send-content/send-content.component.js
  7. 34
      ui/app/pages/send/send.component.js
  8. 2
      ui/app/pages/send/send.constants.js
  9. 2
      ui/app/pages/send/send.container.js

@ -405,6 +405,9 @@
"continueToWyre": { "continueToWyre": {
"message": "Continue to Wyre" "message": "Continue to Wyre"
}, },
"contractAddressError": {
"message": "You are sending tokens to the token's contract address. This may result in the loss of these tokens."
},
"contractDeployment": { "contractDeployment": {
"message": "Contract Deployment" "message": "Contract Deployment"
}, },

@ -108,6 +108,13 @@ export function isValidDomainName(address) {
return match !== null; return match !== null;
} }
export function isOriginContractAddress(to, sendTokenAddress) {
if (!to || !sendTokenAddress) {
return false;
}
return to.toLowerCase() === sendTokenAddress.toLowerCase();
}
export function isAllOneCase(address) { export function isAllOneCase(address) {
if (!address) { if (!address) {
return true; return true;

@ -152,6 +152,38 @@ describe('util', function () {
}); });
}); });
describe('isOriginContractAddress', function () {
it('should return true when the send address is the same as the selected tokens contract address', function () {
assert.equal(
util.isOriginContractAddress(
'0x8d6b81208414189a58339873ab429b6c47ab92d3',
'0x8d6b81208414189a58339873ab429b6c47ab92d3',
),
true,
);
});
it('should return true when the send address is the same as the selected tokens contract address, capitalized input', function () {
assert.equal(
util.isOriginContractAddress(
'0x8d6b81208414189a58339873ab429b6c47ab92d3',
'0X8D6B81208414189A58339873AB429B6C47AB92D3',
),
true,
);
});
it('should return false when the recipient address differs', function () {
assert.equal(
util.isOriginContractAddress(
'0x8d6b81208414189a58339873ab429b6c47ab92d3',
'0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B',
),
false,
);
});
});
describe('#numericBalance', function () { describe('#numericBalance', function () {
it('should return a BN 0 if given nothing', function () { it('should return a BN 0 if given nothing', function () {
const result = util.numericBalance(); const result = util.numericBalance();

@ -7,6 +7,7 @@ import {
KNOWN_RECIPIENT_ADDRESS_ERROR, KNOWN_RECIPIENT_ADDRESS_ERROR,
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR, INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
CONFUSING_ENS_ERROR, CONFUSING_ENS_ERROR,
CONTRACT_ADDRESS_ERROR,
} from '../../send.constants'; } from '../../send.constants';
import { import {
@ -14,9 +15,10 @@ import {
isEthNetwork, isEthNetwork,
checkExistingAddresses, checkExistingAddresses,
isValidDomainName, isValidDomainName,
isOriginContractAddress,
} from '../../../../helpers/utils/util'; } from '../../../../helpers/utils/util';
export function getToErrorObject(to, network) { export function getToErrorObject(to, sendTokenAddress, network) {
let toError = null; let toError = null;
if (!to) { if (!to) {
toError = REQUIRED_ERROR; toError = REQUIRED_ERROR;
@ -24,6 +26,8 @@ export function getToErrorObject(to, network) {
toError = isEthNetwork(network) toError = isEthNetwork(network)
? INVALID_RECIPIENT_ADDRESS_ERROR ? INVALID_RECIPIENT_ADDRESS_ERROR
: INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR; : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR;
} else if (isOriginContractAddress(to, sendTokenAddress)) {
toError = CONTRACT_ADDRESS_ERROR;
} }
return { to: toError }; return { to: toError };

@ -7,6 +7,7 @@ import {
INVALID_RECIPIENT_ADDRESS_ERROR, INVALID_RECIPIENT_ADDRESS_ERROR,
KNOWN_RECIPIENT_ADDRESS_ERROR, KNOWN_RECIPIENT_ADDRESS_ERROR,
CONFUSING_ENS_ERROR, CONFUSING_ENS_ERROR,
CONTRACT_ADDRESS_ERROR,
} from '../../../send.constants'; } from '../../../send.constants';
const stubs = { const stubs = {
@ -41,6 +42,18 @@ describe('add-recipient utils', function () {
to: null, to: null,
}); });
}); });
it('should return a contract address error if the recipient is the same as the tokens contract address', function () {
assert.deepStrictEqual(getToErrorObject('0xabc123', '0xabc123'), {
to: CONTRACT_ADDRESS_ERROR,
});
});
it('should return null if the recipient address is not the token contract address', function () {
assert.deepStrictEqual(getToErrorObject('0xabc123', '0xabc456'), {
to: null,
});
});
}); });
describe('getToWarningObject()', function () { describe('getToWarningObject()', function () {

@ -19,15 +19,17 @@ export default class SendContent extends Component {
contact: PropTypes.object, contact: PropTypes.object,
isOwnedAccount: PropTypes.bool, isOwnedAccount: PropTypes.bool,
warning: PropTypes.string, warning: PropTypes.string,
error: PropTypes.string,
}; };
updateGas = (updateData) => this.props.updateGas(updateData); updateGas = (updateData) => this.props.updateGas(updateData);
render() { render() {
const { warning } = this.props; const { warning, error } = this.props;
return ( return (
<PageContainerContent> <PageContainerContent>
<div className="send-v2__form"> <div className="send-v2__form">
{error && this.renderError()}
{warning && this.renderWarning()} {warning && this.renderWarning()}
{this.maybeRenderAddContact()} {this.maybeRenderAddContact()}
<SendAssetRow /> <SendAssetRow />
@ -74,4 +76,15 @@ export default class SendContent extends Component {
</Dialog> </Dialog>
); );
} }
renderError() {
const { t } = this.context;
const { error } = this.props;
return (
<Dialog type="error" className="send__error-dialog">
{t(error)}
</Dialog>
);
}
} }

@ -17,7 +17,11 @@ import AddRecipient from './send-content/add-recipient';
import SendContent from './send-content'; import SendContent from './send-content';
import SendFooter from './send-footer'; import SendFooter from './send-footer';
import EnsInput from './send-content/add-recipient/ens-input'; import EnsInput from './send-content/add-recipient/ens-input';
import { INVALID_RECIPIENT_ADDRESS_ERROR } from './send.constants'; import {
INVALID_RECIPIENT_ADDRESS_ERROR,
KNOWN_RECIPIENT_ADDRESS_ERROR,
CONTRACT_ADDRESS_ERROR,
} from './send.constants';
export default class SendTransactionScreen extends Component { export default class SendTransactionScreen extends Component {
static propTypes = { static propTypes = {
@ -53,6 +57,7 @@ export default class SendTransactionScreen extends Component {
scanQrCode: PropTypes.func.isRequired, scanQrCode: PropTypes.func.isRequired,
qrCodeDetected: PropTypes.func.isRequired, qrCodeDetected: PropTypes.func.isRequired,
qrCodeData: PropTypes.object, qrCodeData: PropTypes.object,
sendTokenAddress: PropTypes.string,
}; };
static contextTypes = { static contextTypes = {
@ -93,6 +98,7 @@ export default class SendTransactionScreen extends Component {
qrCodeData, qrCodeData,
qrCodeDetected, qrCodeDetected,
} = this.props; } = this.props;
const { toError, toWarning } = this.state;
let updateGas = false; let updateGas = false;
const { const {
@ -187,6 +193,25 @@ export default class SendTransactionScreen extends Component {
this.updateGas(); this.updateGas();
} }
} }
// If selecting ETH after selecting a token, clear token related messages.
if (prevSendToken && !sendToken) {
let error = toError;
let warning = toWarning;
if (toError === CONTRACT_ADDRESS_ERROR) {
error = null;
}
if (toWarning === KNOWN_RECIPIENT_ADDRESS_ERROR) {
warning = null;
}
this.setState({
toError: error,
toWarning: warning,
});
}
} }
componentDidMount() { componentDidMount() {
@ -233,7 +258,7 @@ export default class SendTransactionScreen extends Component {
} }
validate(query) { validate(query) {
const { tokens, sendToken, network } = this.props; const { tokens, sendToken, network, sendTokenAddress } = this.props;
const { internalSearch } = this.state; const { internalSearch } = this.state;
@ -242,7 +267,7 @@ export default class SendTransactionScreen extends Component {
return; return;
} }
const toErrorObject = getToErrorObject(query, network); const toErrorObject = getToErrorObject(query, sendTokenAddress, network);
const toWarningObject = getToWarningObject(query, tokens, sendToken); const toWarningObject = getToWarningObject(query, tokens, sendToken);
this.setState({ this.setState({
@ -358,7 +383,7 @@ export default class SendTransactionScreen extends Component {
renderSendContent() { renderSendContent() {
const { history, showHexData } = this.props; const { history, showHexData } = this.props;
const { toWarning } = this.state; const { toWarning, toError } = this.state;
return [ return [
<SendContent <SendContent
@ -368,6 +393,7 @@ export default class SendTransactionScreen extends Component {
} }
showHexData={showHexData} showHexData={showHexData}
warning={toWarning} warning={toWarning}
error={toError}
/>, />,
<SendFooter key="send-footer" history={history} />, <SendFooter key="send-footer" history={history} />,
]; ];

@ -35,6 +35,7 @@ const INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR =
'invalidAddressRecipientNotEthNetwork'; 'invalidAddressRecipientNotEthNetwork';
const REQUIRED_ERROR = 'required'; const REQUIRED_ERROR = 'required';
const KNOWN_RECIPIENT_ADDRESS_ERROR = 'knownAddressRecipient'; const KNOWN_RECIPIENT_ADDRESS_ERROR = 'knownAddressRecipient';
const CONTRACT_ADDRESS_ERROR = 'contractAddressError';
const CONFUSING_ENS_ERROR = 'confusingEnsDomain'; const CONFUSING_ENS_ERROR = 'confusingEnsDomain';
const SIMPLE_GAS_COST = '0x5208'; // Hex for 21000, cost of a simple send. const SIMPLE_GAS_COST = '0x5208'; // Hex for 21000, cost of a simple send.
@ -45,6 +46,7 @@ export {
INSUFFICIENT_TOKENS_ERROR, INSUFFICIENT_TOKENS_ERROR,
INVALID_RECIPIENT_ADDRESS_ERROR, INVALID_RECIPIENT_ADDRESS_ERROR,
KNOWN_RECIPIENT_ADDRESS_ERROR, KNOWN_RECIPIENT_ADDRESS_ERROR,
CONTRACT_ADDRESS_ERROR,
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR, INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
MIN_GAS_LIMIT_DEC, MIN_GAS_LIMIT_DEC,
MIN_GAS_LIMIT_HEX, MIN_GAS_LIMIT_HEX,

@ -22,6 +22,7 @@ import {
getQrCodeData, getQrCodeData,
getSelectedAddress, getSelectedAddress,
getAddressBook, getAddressBook,
getSendTokenAddress,
} from '../../selectors'; } from '../../selectors';
import { import {
@ -65,6 +66,7 @@ function mapStateToProps(state) {
tokens: getTokens(state), tokens: getTokens(state),
tokenBalance: getTokenBalance(state), tokenBalance: getTokenBalance(state),
tokenContract: getSendTokenContract(state), tokenContract: getSendTokenContract(state),
sendTokenAddress: getSendTokenAddress(state),
}; };
} }

Loading…
Cancel
Save