poa-dapps-voting/src/components/NewBallot/index.js

489 lines
18 KiB
JavaScript

import React from 'react'
import moment from 'moment'
import swal from 'sweetalert2'
import { BallotEmissionFundsMetadata } from '../BallotEmissionFundsMetadata'
import { BallotKeysMetadata } from '../BallotKeysMetadata'
import { BallotMinThresholdMetadata } from '../BallotMinThresholdMetadata'
import { BallotProxyMetadata } from '../BallotProxyMetadata'
import { ButtonAddBallot } from '../ButtonAddBallot'
import { FormTextarea } from '../FormTextarea'
import { KeysTypes } from '../KeysTypes'
import { NewBallotMenu } from '../NewBallotMenu'
import { NewBallotMenuInfo } from '../NewBallotMenuInfo'
import { Separator } from '../Separator'
import { Validator } from '../Validator'
import { constants } from '../../utils/constants'
import { getNetworkBranch } from '../../utils/utils'
import { inject, observer } from 'mobx-react'
import messages from '../../utils/messages'
import { sendTransactionByVotingKey } from '../../utils/helpers'
@inject('commonStore', 'ballotStore', 'validatorStore', 'contractsStore', 'routing', 'ballotsStore')
@observer
export class NewBallot extends React.Component {
constructor(props) {
super(props)
this.onClick = this.onClick.bind(this)
}
getVotingNetworkBranch = () => {
const { contractsStore } = this.props
return contractsStore.netId ? getNetworkBranch(contractsStore.netId) : null
}
getStartTimeUnix() {
return moment
.utc()
.add(constants.startTimeOffsetInMinutes, 'minutes')
.unix()
}
checkValidation() {
const { commonStore, contractsStore, ballotStore } = this.props
// Temporarily commented (until we implement https://github.com/poanetwork/poa-dapps-voting/issues/120)
// const { validatorStore } = this.props
// if (ballotStore.isNewValidatorPersonalData) {
// for (let validatorProp in validatorStore) {
// if (validatorStore[validatorProp].length === 0) {
// swal('Warning!', `Validator ${validatorProp} is empty`, 'warning')
// commonStore.hideLoading()
// return false
// }
// }
// }
if (!ballotStore.memo) {
swal('Warning!', messages.DESCRIPTION_IS_EMPTY, 'warning')
commonStore.hideLoading()
return false
}
if (!ballotStore.isBallotForEmissionFunds) {
const minBallotDurationInHours = constants.minBallotDurationInDays * 24
const startTime = this.getStartTimeUnix()
const minEndTime = moment
.utc()
.add(minBallotDurationInHours, 'hours')
.format()
let neededMinutes = moment(minEndTime).diff(moment(ballotStore.endTime), 'minutes')
const neededHours = Math.floor(neededMinutes / 60)
let duration = moment.unix(ballotStore.endTimeUnix).diff(moment.unix(startTime), 'hours')
if (duration < 0) {
duration = 0
}
if (neededMinutes > 0) {
neededMinutes = Math.abs(neededHours * 60 - neededMinutes)
swal(
'Warning!',
messages.SHOULD_BE_MORE_THAN_MIN_DURATION(minBallotDurationInHours, duration, neededHours, neededMinutes),
'warning'
)
commonStore.hideLoading()
return false
}
const twoWeeks = moment
.utc()
.add(14, 'days')
.format()
const exceededMinutes = moment(ballotStore.endTime).diff(moment(twoWeeks), 'minutes')
if (exceededMinutes > 0) {
swal('Warning!', messages.SHOULD_BE_LESS_OR_EQUAL_14_DAYS(duration), 'warning')
commonStore.hideLoading()
return false
}
}
if (ballotStore.isBallotForKey) {
for (let ballotKeysProp in ballotStore.ballotKeys) {
if (ballotKeysProp === 'newVotingKey' || ballotKeysProp === 'newPayoutKey') {
continue
}
if (!ballotStore.ballotKeys[ballotKeysProp]) {
swal('Warning!', `Ballot ${ballotKeysProp} is empty`, 'warning')
commonStore.hideLoading()
return false
}
if (ballotStore.ballotKeys[ballotKeysProp].length === 0) {
swal('Warning!', `Ballot ${ballotKeysProp} is empty`, 'warning')
commonStore.hideLoading()
return false
}
}
let isAffectedKeyAddress = contractsStore.web3Instance.utils.isAddress(ballotStore.ballotKeys.affectedKey)
if (!isAffectedKeyAddress) {
swal('Warning!', messages.AFFECTED_KEY_IS_NOT_ADDRESS_MSG, 'warning')
commonStore.hideLoading()
return false
}
let isMiningKeyAddress = contractsStore.web3Instance.utils.isAddress(ballotStore.ballotKeys.miningKey.value)
if (!isMiningKeyAddress) {
swal('Warning!', messages.MINING_KEY_IS_NOT_ADDRESS_MSG, 'warning')
commonStore.hideLoading()
return false
}
}
if (ballotStore.isBallotForMinThreshold) {
for (let ballotMinThresholdProp in ballotStore.ballotMinThreshold) {
if (ballotStore.ballotMinThreshold[ballotMinThresholdProp].length === 0) {
swal('Warning!', `Ballot ${ballotMinThresholdProp} is empty`, 'warning')
commonStore.hideLoading()
return false
}
}
}
if (ballotStore.isBallotForProxy) {
for (let ballotProxyProp in ballotStore.ballotProxy) {
if (ballotStore.ballotProxy[ballotProxyProp].length === 0) {
swal('Warning!', `Ballot ${ballotProxyProp} is empty`, 'warning')
commonStore.hideLoading()
return false
}
}
const isAddress = contractsStore.web3Instance.utils.isAddress(ballotStore.ballotProxy.proposedAddress)
if (!isAddress) {
swal('Warning!', messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG, 'warning')
commonStore.hideLoading()
return false
}
}
if (ballotStore.isBallotForEmissionFunds) {
if (ballotStore.ballotEmissionFunds.receiver.length === 0) {
swal('Warning!', `Address of funds receiver is empty`, 'warning')
commonStore.hideLoading()
return false
}
const isAddress = contractsStore.web3Instance.utils.isAddress(ballotStore.ballotEmissionFunds.receiver)
if (!isAddress) {
swal('Warning!', messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG, 'warning')
commonStore.hideLoading()
return false
}
}
if (
!ballotStore.isBallotForKey &&
!ballotStore.isBallotForMinThreshold &&
!ballotStore.isBallotForProxy &&
!ballotStore.isBallotForEmissionFunds
) {
swal('Warning!', messages.BALLOT_TYPE_IS_EMPTY_MSG, 'warning')
commonStore.hideLoading()
return false
}
return true
}
createBallotForKeys = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime,
endTime,
affectedKey: ballotStore.ballotKeys.affectedKey,
affectedKeyType: ballotStore.ballotKeys.keyType,
newVotingKey: ballotStore.ballotKeys.newVotingKey,
newPayoutKey: ballotStore.ballotKeys.newPayoutKey,
miningKey: ballotStore.ballotKeys.miningKey.value,
ballotType: ballotStore.ballotKeys.keysBallotType,
memo: ballotStore.memo
}
let data
if (
inputToMethod.ballotType === ballotStore.KeysBallotType.add &&
inputToMethod.affectedKeyType === ballotStore.KeyType.mining &&
(inputToMethod.newVotingKey || inputToMethod.newPayoutKey)
) {
data = contractsStore.votingToChangeKeys.createBallotToAddNewValidator(inputToMethod)
} else {
data = contractsStore.votingToChangeKeys.createBallot(inputToMethod)
}
return data
}
createBallotForMinThreshold = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime,
endTime,
proposedValue: ballotStore.ballotMinThreshold.proposedValue,
memo: ballotStore.memo
}
return contractsStore.votingToChangeMinThreshold.createBallot(inputToMethod)
}
createBallotForProxy = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime,
endTime,
proposedValue: ballotStore.ballotProxy.proposedAddress,
contractType: ballotStore.ballotProxy.contractType,
memo: ballotStore.memo
}
return contractsStore.votingToChangeProxy.createBallot(inputToMethod)
}
createBallotForEmissionFunds = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime,
endTime,
receiver: ballotStore.ballotEmissionFunds.receiver,
memo: ballotStore.memo
}
return contractsStore.votingToManageEmissionFunds.createBallot(inputToMethod)
}
onClick = async () => {
const { commonStore, contractsStore, ballotStore, ballotsStore } = this.props
const { push } = this.props.routing
if (!contractsStore.votingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.networkMatch) {
swal('Warning!', messages.networkMatchError(contractsStore.netId), 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return
}
commonStore.showLoading()
const isFormValid = this.checkValidation()
if (isFormValid) {
if (ballotStore.ballotType === ballotStore.BallotType.keys) {
const inputToAreBallotParamsValid = {
affectedKey: ballotStore.ballotKeys.affectedKey,
affectedKeyType: ballotStore.ballotKeys.keyType,
miningKey: ballotStore.ballotKeys.miningKey.value,
ballotType: ballotStore.ballotKeys.keysBallotType
}
let areBallotParamsValid
areBallotParamsValid = await contractsStore.ballotsStorage.areKeysBallotParamsValid(inputToAreBallotParamsValid)
if (areBallotParamsValid === null) {
areBallotParamsValid = await contractsStore.votingToChangeKeys.areBallotParamsValid(
inputToAreBallotParamsValid
)
}
if (ballotStore.ballotKeys.keysBallotType === ballotStore.KeysBallotType.add) {
if (ballotStore.ballotKeys.keyType !== ballotStore.KeyType.mining) {
if (!ballotStore.ballotKeys.miningKey.value) {
areBallotParamsValid = false
} else if (ballotStore.ballotKeys.miningKey.value === '0x0000000000000000000000000000000000000000') {
areBallotParamsValid = false
}
}
}
if (!areBallotParamsValid) {
commonStore.hideLoading()
return swal('Warning!', 'The ballot input params are invalid', 'warning')
}
}
let startTime = this.getStartTimeUnix()
let endTime = ballotStore.endTimeUnix
if (ballotStore.ballotType === ballotStore.BallotType.emissionFunds) {
const votingContract = contractsStore.votingToManageEmissionFunds
let emissionReleaseTime = Number(await votingContract.emissionReleaseTime())
const emissionReleaseThreshold = Number(await votingContract.emissionReleaseThreshold())
const currentTime = Number(await votingContract.getTime())
emissionReleaseTime = votingContract.refreshEmissionReleaseTime(
emissionReleaseTime,
emissionReleaseThreshold,
currentTime
)
if (currentTime < emissionReleaseTime) {
commonStore.hideLoading()
const emissionReleaseTimeString = moment
.unix(emissionReleaseTime)
.utc()
.format('MMM Do YYYY, h:mm:ss a')
swal('Warning!', messages.EMISSION_RELEASE_TIME_IN_FUTURE(emissionReleaseTimeString), 'warning')
return
}
const noActiveBallotExists = await votingContract.noActiveBallotExists()
if (!noActiveBallotExists) {
commonStore.hideLoading()
swal('Warning!', messages.PREVIOUS_BALLOT_NOT_FINALIZED, 'warning')
return
}
const distributionThreshold = Number(await votingContract.distributionThreshold())
startTime = currentTime + constants.startTimeOffsetInMinutes * 60
endTime = emissionReleaseTime + distributionThreshold
}
let methodToCreateBallot
let contractType
let contractInstance
switch (ballotStore.ballotType) {
case ballotStore.BallotType.keys:
methodToCreateBallot = this.createBallotForKeys
contractType = 'votingToChangeKeys'
contractInstance = contractsStore.votingToChangeKeys.instance
break
case ballotStore.BallotType.minThreshold:
methodToCreateBallot = this.createBallotForMinThreshold
contractType = 'votingToChangeMinThreshold'
contractInstance = contractsStore.votingToChangeMinThreshold.instance
break
case ballotStore.BallotType.proxy:
methodToCreateBallot = this.createBallotForProxy
contractType = 'votingToChangeProxy'
contractInstance = contractsStore.votingToChangeProxy.instance
break
case ballotStore.BallotType.emissionFunds:
methodToCreateBallot = this.createBallotForEmissionFunds
contractType = 'votingToManageEmissionFunds'
contractInstance = contractsStore.votingToManageEmissionFunds.instance
break
default:
break
}
sendTransactionByVotingKey(
this.props,
contractInstance.options.address,
methodToCreateBallot(startTime, endTime),
async tx => {
const events = await contractInstance.getPastEvents('BallotCreated', {
fromBlock: tx.blockNumber,
toBlock: tx.blockNumber
})
const newId = Number(events[0].returnValues.id)
const card = await contractsStore.getCard(newId, contractType)
ballotsStore.ballotCards.push(card)
swal('Congratulations!', messages.BALLOT_CREATED_SUCCESS_MSG, 'success').then(result => {
push(`${commonStore.rootPath}`)
window.scrollTo(0, 0)
})
},
messages.BALLOT_CREATE_FAILED_TX
)
}
}
componentDidMount() {
const { ballotStore } = this.props
ballotStore.changeBallotType(null, ballotStore.BallotType.keys)
}
getMenuItems() {
const { contractsStore, ballotStore } = this.props
let items = [
{
active: ballotStore.BallotType.keys === ballotStore.ballotType,
onClick: e => ballotStore.changeBallotType(e, ballotStore.BallotType.keys),
text: 'Validator Management Ballot'
},
{
active: ballotStore.BallotType.minThreshold === ballotStore.ballotType,
onClick: e => ballotStore.changeBallotType(e, ballotStore.BallotType.minThreshold),
text: 'Consensus Threshold Ballot'
},
{
active: ballotStore.BallotType.proxy === ballotStore.ballotType,
onClick: e => ballotStore.changeBallotType(e, ballotStore.BallotType.proxy),
text: 'Modify Proxy Contract Ballot'
}
]
if (contractsStore.votingToManageEmissionFunds) {
items.push({
active: ballotStore.BallotType.emissionFunds === ballotStore.ballotType,
onClick: e => ballotStore.changeBallotType(e, ballotStore.BallotType.emissionFunds),
text: 'Emission Funds Ballot'
})
}
return items
}
render() {
const { contractsStore, ballotStore } = this.props
const networkBranch = this.getVotingNetworkBranch()
let metadata
let minThreshold = 0
switch (ballotStore.ballotType) {
case ballotStore.BallotType.keys:
metadata = <BallotKeysMetadata networkBranch={networkBranch} />
minThreshold = contractsStore.keysBallotThreshold
break
case ballotStore.BallotType.minThreshold:
metadata = <BallotMinThresholdMetadata networkBranch={networkBranch} />
minThreshold = contractsStore.minThresholdBallotThreshold
break
case ballotStore.BallotType.proxy:
metadata = <BallotProxyMetadata networkBranch={networkBranch} />
minThreshold = contractsStore.proxyBallotThreshold
break
case ballotStore.BallotType.emissionFunds:
metadata = <BallotEmissionFundsMetadata networkBranch={networkBranch} />
minThreshold = contractsStore.emissionFundsBallotThreshold
break
default:
break
}
return (
<section className="new-NewBallot">
<form className="new-NewBallot_Form">
<div className="new-NewBallot_MenuContainer">
<NewBallotMenu menuItems={this.getMenuItems()} networkBranch={networkBranch} />
<NewBallotMenuInfo
keys={contractsStore.validatorLimits.keys}
minThreshold={minThreshold}
proxy={contractsStore.validatorLimits.proxy}
validatorLimitsMinThreshold={contractsStore.validatorLimits.minThreshold}
validatorsLength={contractsStore.validatorsLength}
/>
</div>
<div className="new-NewBallot_FormContent">
<FormTextarea
id="ballot-description"
networkBranch={networkBranch}
onChange={e => ballotStore.setMemo(e)}
title="Description of the ballot"
value={ballotStore.memo}
/>
<Separator />
{ballotStore.isBallotForKey ? <KeysTypes networkBranch={networkBranch} /> : null}
{ballotStore.isNewValidatorPersonalData ? <Validator networkBranch={networkBranch} /> : null}
{metadata}
<Separator />
<div className="new-NewBallot_ButtonContainer">
<ButtonAddBallot onClick={e => this.onClick(e)} networkBranch={networkBranch} />
</div>
</div>
</form>
</section>
)
}
}