Merge pull request #5380 from whymarrh/send-with-hex-data

Allow sending transactions with hex data and no recipient
feature/default_network_editable
Whymarrh Whitby 6 years ago committed by GitHub
commit f2fc91b80b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ui/app/actions.js
  2. 12
      ui/app/components/send/send-content/send-content.component.js
  3. 8
      ui/app/components/send/send-content/send-hex-data-row/send-hex-data-row.component.js
  4. 5
      ui/app/components/send/send-content/send-to-row/send-to-row.component.js
  5. 2
      ui/app/components/send/send-content/send-to-row/send-to-row.container.js
  6. 6
      ui/app/components/send/send-content/send-to-row/send-to-row.utils.js
  7. 2
      ui/app/components/send/send-content/send-to-row/tests/send-to-row-container.test.js
  8. 6
      ui/app/components/send/send-content/send-to-row/tests/send-to-row-utils.test.js
  9. 4
      ui/app/components/send/send-footer/send-footer.component.js
  10. 3
      ui/app/components/send/send.component.js
  11. 3
      ui/app/components/send/send.container.js
  12. 32
      ui/app/components/send/send.utils.js
  13. 1
      ui/app/components/send/tests/send-component.test.js
  14. 5
      ui/app/components/send/tests/send-container.test.js
  15. 25
      ui/app/components/send/tests/send-utils.test.js

@ -917,6 +917,7 @@ function updateGasData ({
selectedToken, selectedToken,
to, to,
value, value,
data,
}) { }) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.gasLoadingStarted()) dispatch(actions.gasLoadingStarted())
@ -937,6 +938,7 @@ function updateGasData ({
to, to,
value, value,
estimateGasPrice, estimateGasPrice,
data,
}), }),
]) ])
}) })

@ -15,18 +15,24 @@ export default class SendContent extends Component {
showHexData: PropTypes.bool, showHexData: PropTypes.bool,
}; };
updateGas = (updateData) => this.props.updateGas(updateData)
render () { render () {
return ( return (
<PageContainerContent> <PageContainerContent>
<div className="send-v2__form"> <div className="send-v2__form">
<SendFromRow /> <SendFromRow />
<SendToRow <SendToRow
updateGas={(updateData) => this.props.updateGas(updateData)} updateGas={this.updateGas}
scanQrCode={ _ => this.props.scanQrCode()} scanQrCode={ _ => this.props.scanQrCode()}
/> />
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} /> <SendAmountRow updateGas={this.updateGas} />
<SendGasRow /> <SendGasRow />
{ this.props.showHexData ? <SendHexDataRow /> : null } {(this.props.showHexData && (
<SendHexDataRow
updateGas={this.updateGas}
/>
))}
</div> </div>
</PageContainerContent> </PageContainerContent>
) )

@ -7,6 +7,7 @@ export default class SendHexDataRow extends Component {
data: PropTypes.string, data: PropTypes.string,
inError: PropTypes.bool, inError: PropTypes.bool,
updateSendHexData: PropTypes.func.isRequired, updateSendHexData: PropTypes.func.isRequired,
updateGas: PropTypes.func.isRequired,
}; };
static contextTypes = { static contextTypes = {
@ -14,9 +15,10 @@ export default class SendHexDataRow extends Component {
}; };
onInput = (event) => { onInput = (event) => {
const {updateSendHexData} = this.props const {updateSendHexData, updateGas} = this.props
event.target.value = event.target.value.replace(/\n/g, '') const data = event.target.value.replace(/\n/g, '') || null
updateSendHexData(event.target.value || null) updateSendHexData(data)
updateGas({ data })
} }
render () { render () {

@ -8,6 +8,7 @@ export default class SendToRow extends Component {
static propTypes = { static propTypes = {
closeToDropdown: PropTypes.func, closeToDropdown: PropTypes.func,
hasHexData: PropTypes.bool.isRequired,
inError: PropTypes.bool, inError: PropTypes.bool,
network: PropTypes.string, network: PropTypes.string,
openToDropdown: PropTypes.func, openToDropdown: PropTypes.func,
@ -25,8 +26,8 @@ export default class SendToRow extends Component {
}; };
handleToChange (to, nickname = '', toError) { handleToChange (to, nickname = '', toError) {
const { updateSendTo, updateSendToError, updateGas } = this.props const { hasHexData, updateSendTo, updateSendToError, updateGas } = this.props
const toErrorObject = getToErrorObject(to, toError) const toErrorObject = getToErrorObject(to, toError, hasHexData)
updateSendTo(to, nickname) updateSendTo(to, nickname)
updateSendToError(toErrorObject) updateSendToError(toErrorObject)
if (toErrorObject.to === null) { if (toErrorObject.to === null) {

@ -3,6 +3,7 @@ import {
getCurrentNetwork, getCurrentNetwork,
getSendTo, getSendTo,
getSendToAccounts, getSendToAccounts,
getSendHexData,
} from '../../send.selectors.js' } from '../../send.selectors.js'
import { import {
getToDropdownOpen, getToDropdownOpen,
@ -22,6 +23,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
hasHexData: Boolean(getSendHexData(state)),
inError: sendToIsInError(state), inError: sendToIsInError(state),
network: getCurrentNetwork(state), network: getCurrentNetwork(state),
to: getSendTo(state), to: getSendTo(state),

@ -4,9 +4,11 @@ const {
} = require('../../send.constants') } = require('../../send.constants')
const { isValidAddress } = require('../../../../util') const { isValidAddress } = require('../../../../util')
function getToErrorObject (to, toError = null) { function getToErrorObject (to, toError = null, hasHexData = false) {
if (!to) { if (!to) {
toError = REQUIRED_ERROR if (!hasHexData) {
toError = REQUIRED_ERROR
}
} else if (!isValidAddress(to) && !toError) { } else if (!isValidAddress(to) && !toError) {
toError = INVALID_RECIPIENT_ADDRESS_ERROR toError = INVALID_RECIPIENT_ADDRESS_ERROR
} }

@ -24,6 +24,7 @@ proxyquire('../send-to-row.container.js', {
}, },
'../../send.selectors.js': { '../../send.selectors.js': {
getCurrentNetwork: (s) => `mockNetwork:${s}`, getCurrentNetwork: (s) => `mockNetwork:${s}`,
getSendHexData: (s) => s,
getSendTo: (s) => `mockTo:${s}`, getSendTo: (s) => `mockTo:${s}`,
getSendToAccounts: (s) => `mockToAccounts:${s}`, getSendToAccounts: (s) => `mockToAccounts:${s}`,
}, },
@ -41,6 +42,7 @@ describe('send-to-row container', () => {
it('should map the correct properties to props', () => { it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), { assert.deepEqual(mapStateToProps('mockState'), {
hasHexData: true,
inError: 'mockInError:mockState', inError: 'mockInError:mockState',
network: 'mockNetwork:mockState', network: 'mockNetwork:mockState',
to: 'mockTo: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', () => { it('should return an invalid recipient error if to is truthy but invalid', () => {
assert.deepEqual(getToErrorObject('mockInvalidTo'), { assert.deepEqual(getToErrorObject('mockInvalidTo'), {
to: INVALID_RECIPIENT_ADDRESS_ERROR, to: INVALID_RECIPIENT_ADDRESS_ERROR,

@ -86,9 +86,9 @@ export default class SendFooter extends Component {
} }
formShouldBeDisabled () { formShouldBeDisabled () {
const { inError, selectedToken, tokenBalance, gasTotal, to } = this.props const { data, inError, selectedToken, tokenBalance, gasTotal, to } = this.props
const missingTokenBalance = selectedToken && !tokenBalance const missingTokenBalance = selectedToken && !tokenBalance
return inError || !gasTotal || missingTokenBalance || !to return inError || !gasTotal || missingTokenBalance || !(data || to)
} }
render () { render () {

@ -62,7 +62,7 @@ export default class SendTransactionScreen extends PersistentForm {
} }
} }
updateGas ({ to: updatedToAddress, amount: value } = {}) { updateGas ({ to: updatedToAddress, amount: value, data } = {}) {
const { const {
amount, amount,
blockGasLimit, blockGasLimit,
@ -86,6 +86,7 @@ export default class SendTransactionScreen extends PersistentForm {
selectedToken, selectedToken,
to: getToAddressForGasUpdate(updatedToAddress, currentToAddress), to: getToAddressForGasUpdate(updatedToAddress, currentToAddress),
value: value || amount, value: value || amount,
data,
}) })
} }

@ -86,9 +86,10 @@ function mapDispatchToProps (dispatch) {
selectedToken, selectedToken,
to, to,
value, value,
data,
}) => { }) => {
!editingTransactionId !editingTransactionId
? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value })) ? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value, data }))
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice))) : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
}, },
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => { updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {

@ -200,16 +200,20 @@ function doesAmountErrorRequireUpdate ({
return amountErrorRequiresUpdate 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 } 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 recipient has no code, gas is 21k max:
if (!selectedToken) { if (!selectedToken && !data) {
const code = Boolean(to) && await global.eth.getCode(to) const code = Boolean(to) && await global.eth.getCode(to)
if (!code || code === '0x') { if (!code || code === '0x') {
return SIMPLE_GAS_COST return SIMPLE_GAS_COST
@ -218,7 +222,19 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to,
return BASE_TOKEN_GAS_COST 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 // if not, fall back to block gasLimit
paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, { paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, {

@ -289,6 +289,7 @@ describe('Send Component', function () {
selectedToken: 'mockSelectedToken', selectedToken: 'mockSelectedToken',
to: '', to: '',
value: 'mockAmount', value: 'mockAmount',
data: undefined,
} }
) )
}) })

@ -105,6 +105,7 @@ describe('send container', () => {
selectedToken: { address: '0x1' }, selectedToken: { address: '0x1' },
to: 'mockTo', to: 'mockTo',
value: 'mockValue', value: 'mockValue',
data: undefined,
} }
it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => { 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', () => { 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( mapDispatchToPropsObject.updateAndSetGasTotal(
Object.assign({}, mockProps, {editingTransactionId: false}) Object.assign({}, mockProps, {editingTransactionId: false})
) )
assert(dispatchSpy.calledOnce) assert(dispatchSpy.calledOnce)
assert.deepEqual( assert.deepEqual(
actionSpies.updateGasData.getCall(0).args[0], 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', selectedAddress: 'mockAddress',
to: '0xisContract', to: '0xisContract',
estimateGasMethod: sinon.stub().callsFake( estimateGasMethod: sinon.stub().callsFake(
(data, cb) => cb( ({to}, cb) => {
data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null, const err = typeof to === 'string' && to.match(/willFailBecauseOf:/)
{ toString: (n) => `0xabc${n}` } ? new Error(to.match(/:(.+)$/)[1])
) : null
const result = { toString: (n) => `0xabc${n}` }
return cb(err, result)
}
), ),
} }
const baseExpectedCall = { const baseExpectedCall = {
@ -364,6 +367,18 @@ describe('send utils', () => {
assert.equal(result, '0xabc16') 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 () => { it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => {
assert.equal(baseMockParams.estimateGasMethod.callCount, 0) assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' })) const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' }))
@ -407,7 +422,7 @@ describe('send utils', () => {
to: 'isContract willFailBecauseOf:some other error', to: 'isContract willFailBecauseOf:some other error',
})) }))
} catch (err) { } catch (err) {
assert.deepEqual(err, { message: 'some other error' }) assert.equal(err.message, 'some other error')
} }
}) })
}) })

Loading…
Cancel
Save