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": {
"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": {
"message": "Contract Deployment"
},

@ -108,6 +108,13 @@ export function isValidDomainName(address) {
return match !== null;
}
export function isOriginContractAddress(to, sendTokenAddress) {
if (!to || !sendTokenAddress) {
return false;
}
return to.toLowerCase() === sendTokenAddress.toLowerCase();
}
export function isAllOneCase(address) {
if (!address) {
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 () {
it('should return a BN 0 if given nothing', function () {
const result = util.numericBalance();

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

@ -7,6 +7,7 @@ import {
INVALID_RECIPIENT_ADDRESS_ERROR,
KNOWN_RECIPIENT_ADDRESS_ERROR,
CONFUSING_ENS_ERROR,
CONTRACT_ADDRESS_ERROR,
} from '../../../send.constants';
const stubs = {
@ -41,6 +42,18 @@ describe('add-recipient utils', function () {
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 () {

@ -19,15 +19,17 @@ export default class SendContent extends Component {
contact: PropTypes.object,
isOwnedAccount: PropTypes.bool,
warning: PropTypes.string,
error: PropTypes.string,
};
updateGas = (updateData) => this.props.updateGas(updateData);
render() {
const { warning } = this.props;
const { warning, error } = this.props;
return (
<PageContainerContent>
<div className="send-v2__form">
{error && this.renderError()}
{warning && this.renderWarning()}
{this.maybeRenderAddContact()}
<SendAssetRow />
@ -74,4 +76,15 @@ export default class SendContent extends Component {
</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 SendFooter from './send-footer';
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 {
static propTypes = {
@ -53,6 +57,7 @@ export default class SendTransactionScreen extends Component {
scanQrCode: PropTypes.func.isRequired,
qrCodeDetected: PropTypes.func.isRequired,
qrCodeData: PropTypes.object,
sendTokenAddress: PropTypes.string,
};
static contextTypes = {
@ -93,6 +98,7 @@ export default class SendTransactionScreen extends Component {
qrCodeData,
qrCodeDetected,
} = this.props;
const { toError, toWarning } = this.state;
let updateGas = false;
const {
@ -187,6 +193,25 @@ export default class SendTransactionScreen extends Component {
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() {
@ -233,7 +258,7 @@ export default class SendTransactionScreen extends Component {
}
validate(query) {
const { tokens, sendToken, network } = this.props;
const { tokens, sendToken, network, sendTokenAddress } = this.props;
const { internalSearch } = this.state;
@ -242,7 +267,7 @@ export default class SendTransactionScreen extends Component {
return;
}
const toErrorObject = getToErrorObject(query, network);
const toErrorObject = getToErrorObject(query, sendTokenAddress, network);
const toWarningObject = getToWarningObject(query, tokens, sendToken);
this.setState({
@ -358,7 +383,7 @@ export default class SendTransactionScreen extends Component {
renderSendContent() {
const { history, showHexData } = this.props;
const { toWarning } = this.state;
const { toWarning, toError } = this.state;
return [
<SendContent
@ -368,6 +393,7 @@ export default class SendTransactionScreen extends Component {
}
showHexData={showHexData}
warning={toWarning}
error={toError}
/>,
<SendFooter key="send-footer" history={history} />,
];

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

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

Loading…
Cancel
Save