mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Adding a warning when sending a token to its own contract address (#10546)
Fixes MetaMask/metamask-extension#9437
This commit is contained in:
parent
eef92d0d5a
commit
3c6cdef074
@ -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…
x
Reference in New Issue
Block a user