mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #5380 from whymarrh/send-with-hex-data
Allow sending transactions with hex data and no recipient
This commit is contained in:
commit
f2fc91b80b
@ -917,6 +917,7 @@ function updateGasData ({
|
||||
selectedToken,
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
}) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.gasLoadingStarted())
|
||||
@ -937,6 +938,7 @@ function updateGasData ({
|
||||
to,
|
||||
value,
|
||||
estimateGasPrice,
|
||||
data,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
@ -15,18 +15,24 @@ export default class SendContent extends Component {
|
||||
showHexData: PropTypes.bool,
|
||||
};
|
||||
|
||||
updateGas = (updateData) => this.props.updateGas(updateData)
|
||||
|
||||
render () {
|
||||
return (
|
||||
<PageContainerContent>
|
||||
<div className="send-v2__form">
|
||||
<SendFromRow />
|
||||
<SendToRow
|
||||
updateGas={(updateData) => this.props.updateGas(updateData)}
|
||||
updateGas={this.updateGas}
|
||||
scanQrCode={ _ => this.props.scanQrCode()}
|
||||
/>
|
||||
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||
<SendAmountRow updateGas={this.updateGas} />
|
||||
<SendGasRow />
|
||||
{ this.props.showHexData ? <SendHexDataRow /> : null }
|
||||
{(this.props.showHexData && (
|
||||
<SendHexDataRow
|
||||
updateGas={this.updateGas}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PageContainerContent>
|
||||
)
|
||||
|
@ -7,6 +7,7 @@ export default class SendHexDataRow extends Component {
|
||||
data: PropTypes.string,
|
||||
inError: PropTypes.bool,
|
||||
updateSendHexData: PropTypes.func.isRequired,
|
||||
updateGas: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -14,9 +15,10 @@ export default class SendHexDataRow extends Component {
|
||||
};
|
||||
|
||||
onInput = (event) => {
|
||||
const {updateSendHexData} = this.props
|
||||
event.target.value = event.target.value.replace(/\n/g, '')
|
||||
updateSendHexData(event.target.value || null)
|
||||
const {updateSendHexData, updateGas} = this.props
|
||||
const data = event.target.value.replace(/\n/g, '') || null
|
||||
updateSendHexData(data)
|
||||
updateGas({ data })
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -8,6 +8,7 @@ export default class SendToRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
closeToDropdown: PropTypes.func,
|
||||
hasHexData: PropTypes.bool.isRequired,
|
||||
inError: PropTypes.bool,
|
||||
network: PropTypes.string,
|
||||
openToDropdown: PropTypes.func,
|
||||
@ -25,8 +26,8 @@ export default class SendToRow extends Component {
|
||||
};
|
||||
|
||||
handleToChange (to, nickname = '', toError) {
|
||||
const { updateSendTo, updateSendToError, updateGas } = this.props
|
||||
const toErrorObject = getToErrorObject(to, toError)
|
||||
const { hasHexData, updateSendTo, updateSendToError, updateGas } = this.props
|
||||
const toErrorObject = getToErrorObject(to, toError, hasHexData)
|
||||
updateSendTo(to, nickname)
|
||||
updateSendToError(toErrorObject)
|
||||
if (toErrorObject.to === null) {
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
getCurrentNetwork,
|
||||
getSendTo,
|
||||
getSendToAccounts,
|
||||
getSendHexData,
|
||||
} from '../../send.selectors.js'
|
||||
import {
|
||||
getToDropdownOpen,
|
||||
@ -22,6 +23,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
hasHexData: Boolean(getSendHexData(state)),
|
||||
inError: sendToIsInError(state),
|
||||
network: getCurrentNetwork(state),
|
||||
to: getSendTo(state),
|
||||
|
@ -4,9 +4,11 @@ const {
|
||||
} = require('../../send.constants')
|
||||
const { isValidAddress } = require('../../../../util')
|
||||
|
||||
function getToErrorObject (to, toError = null) {
|
||||
function getToErrorObject (to, toError = null, hasHexData = false) {
|
||||
if (!to) {
|
||||
toError = REQUIRED_ERROR
|
||||
if (!hasHexData) {
|
||||
toError = REQUIRED_ERROR
|
||||
}
|
||||
} else if (!isValidAddress(to) && !toError) {
|
||||
toError = INVALID_RECIPIENT_ADDRESS_ERROR
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ proxyquire('../send-to-row.container.js', {
|
||||
},
|
||||
'../../send.selectors.js': {
|
||||
getCurrentNetwork: (s) => `mockNetwork:${s}`,
|
||||
getSendHexData: (s) => s,
|
||||
getSendTo: (s) => `mockTo:${s}`,
|
||||
getSendToAccounts: (s) => `mockToAccounts:${s}`,
|
||||
},
|
||||
@ -41,6 +42,7 @@ describe('send-to-row container', () => {
|
||||
|
||||
it('should map the correct properties to props', () => {
|
||||
assert.deepEqual(mapStateToProps('mockState'), {
|
||||
hasHexData: true,
|
||||
inError: 'mockInError:mockState',
|
||||
network: 'mockNetwork:mockState',
|
||||
to: 'mockTo:mockState',
|
||||
|
@ -29,6 +29,12 @@ describe('send-to-row utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should return null if to is falsy and hexData is truthy', () => {
|
||||
assert.deepEqual(getToErrorObject(null, undefined, true), {
|
||||
to: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an invalid recipient error if to is truthy but invalid', () => {
|
||||
assert.deepEqual(getToErrorObject('mockInvalidTo'), {
|
||||
to: INVALID_RECIPIENT_ADDRESS_ERROR,
|
||||
|
@ -86,9 +86,9 @@ export default class SendFooter extends Component {
|
||||
}
|
||||
|
||||
formShouldBeDisabled () {
|
||||
const { inError, selectedToken, tokenBalance, gasTotal, to } = this.props
|
||||
const { data, inError, selectedToken, tokenBalance, gasTotal, to } = this.props
|
||||
const missingTokenBalance = selectedToken && !tokenBalance
|
||||
return inError || !gasTotal || missingTokenBalance || !to
|
||||
return inError || !gasTotal || missingTokenBalance || !(data || to)
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -62,7 +62,7 @@ export default class SendTransactionScreen extends PersistentForm {
|
||||
}
|
||||
}
|
||||
|
||||
updateGas ({ to: updatedToAddress, amount: value } = {}) {
|
||||
updateGas ({ to: updatedToAddress, amount: value, data } = {}) {
|
||||
const {
|
||||
amount,
|
||||
blockGasLimit,
|
||||
@ -86,6 +86,7 @@ export default class SendTransactionScreen extends PersistentForm {
|
||||
selectedToken,
|
||||
to: getToAddressForGasUpdate(updatedToAddress, currentToAddress),
|
||||
value: value || amount,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -86,9 +86,10 @@ function mapDispatchToProps (dispatch) {
|
||||
selectedToken,
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
}) => {
|
||||
!editingTransactionId
|
||||
? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value }))
|
||||
? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value, data }))
|
||||
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
|
||||
},
|
||||
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {
|
||||
|
@ -200,16 +200,20 @@ function doesAmountErrorRequireUpdate ({
|
||||
return amountErrorRequiresUpdate
|
||||
}
|
||||
|
||||
async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, value, gasPrice, estimateGasMethod }) {
|
||||
async function estimateGas ({
|
||||
selectedAddress,
|
||||
selectedToken,
|
||||
blockGasLimit,
|
||||
to,
|
||||
value,
|
||||
data,
|
||||
gasPrice,
|
||||
estimateGasMethod,
|
||||
}) {
|
||||
const paramsForGasEstimate = { from: selectedAddress, value, gasPrice }
|
||||
|
||||
if (selectedToken) {
|
||||
paramsForGasEstimate.value = '0x0'
|
||||
paramsForGasEstimate.data = generateTokenTransferData({ toAddress: to, amount: value, selectedToken })
|
||||
}
|
||||
|
||||
// if recipient has no code, gas is 21k max:
|
||||
if (!selectedToken) {
|
||||
if (!selectedToken && !data) {
|
||||
const code = Boolean(to) && await global.eth.getCode(to)
|
||||
if (!code || code === '0x') {
|
||||
return SIMPLE_GAS_COST
|
||||
@ -218,7 +222,19 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to,
|
||||
return BASE_TOKEN_GAS_COST
|
||||
}
|
||||
|
||||
paramsForGasEstimate.to = selectedToken ? selectedToken.address : to
|
||||
if (selectedToken) {
|
||||
paramsForGasEstimate.value = '0x0'
|
||||
paramsForGasEstimate.data = generateTokenTransferData({ toAddress: to, amount: value, selectedToken })
|
||||
paramsForGasEstimate.to = selectedToken.address
|
||||
} else {
|
||||
if (data) {
|
||||
paramsForGasEstimate.data = data
|
||||
}
|
||||
|
||||
if (to) {
|
||||
paramsForGasEstimate.to = to
|
||||
}
|
||||
}
|
||||
|
||||
// if not, fall back to block gasLimit
|
||||
paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, {
|
||||
|
@ -289,6 +289,7 @@ describe('Send Component', function () {
|
||||
selectedToken: 'mockSelectedToken',
|
||||
to: '',
|
||||
value: 'mockAmount',
|
||||
data: undefined,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -105,6 +105,7 @@ describe('send container', () => {
|
||||
selectedToken: { address: '0x1' },
|
||||
to: 'mockTo',
|
||||
value: 'mockValue',
|
||||
data: undefined,
|
||||
}
|
||||
|
||||
it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => {
|
||||
@ -117,14 +118,14 @@ describe('send container', () => {
|
||||
})
|
||||
|
||||
it('should dispatch an updateGasData action when editingTransactionId is falsy', () => {
|
||||
const { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value } = mockProps
|
||||
const { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value, data } = mockProps
|
||||
mapDispatchToPropsObject.updateAndSetGasTotal(
|
||||
Object.assign({}, mockProps, {editingTransactionId: false})
|
||||
)
|
||||
assert(dispatchSpy.calledOnce)
|
||||
assert.deepEqual(
|
||||
actionSpies.updateGasData.getCall(0).args[0],
|
||||
{ selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value }
|
||||
{ selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value, data }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -304,10 +304,13 @@ describe('send utils', () => {
|
||||
selectedAddress: 'mockAddress',
|
||||
to: '0xisContract',
|
||||
estimateGasMethod: sinon.stub().callsFake(
|
||||
(data, cb) => cb(
|
||||
data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null,
|
||||
{ toString: (n) => `0xabc${n}` }
|
||||
)
|
||||
({to}, cb) => {
|
||||
const err = typeof to === 'string' && to.match(/willFailBecauseOf:/)
|
||||
? new Error(to.match(/:(.+)$/)[1])
|
||||
: null
|
||||
const result = { toString: (n) => `0xabc${n}` }
|
||||
return cb(err, result)
|
||||
}
|
||||
),
|
||||
}
|
||||
const baseExpectedCall = {
|
||||
@ -364,6 +367,18 @@ describe('send utils', () => {
|
||||
assert.equal(result, '0xabc16')
|
||||
})
|
||||
|
||||
it('should call ethQuery.estimateGas without a recipient if the recipient is empty and data passed', async () => {
|
||||
const data = 'mockData'
|
||||
const to = ''
|
||||
const result = await estimateGas({...baseMockParams, data, to})
|
||||
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
|
||||
assert.deepEqual(
|
||||
baseMockParams.estimateGasMethod.getCall(0).args[0],
|
||||
{ gasPrice: undefined, value: undefined, data, from: baseExpectedCall.from, gas: baseExpectedCall.gas},
|
||||
)
|
||||
assert.equal(result, '0xabc16')
|
||||
})
|
||||
|
||||
it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => {
|
||||
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
|
||||
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' }))
|
||||
@ -407,7 +422,7 @@ describe('send utils', () => {
|
||||
to: 'isContract willFailBecauseOf:some other error',
|
||||
}))
|
||||
} catch (err) {
|
||||
assert.deepEqual(err, { message: 'some other error' })
|
||||
assert.equal(err.message, 'some other error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user