From d43f03036d80fd560314142ea355122f88ce590c Mon Sep 17 00:00:00 2001 From: Vadim Arasev Date: Wed, 19 Sep 2018 10:54:31 +0300 Subject: [PATCH] (Feature) Add support of VotingToManageEmissionFunds.cancelNewBallot --- src/components/BallotCard.jsx | 172 +++++++++++++++--- .../BallotEmissionFundsMetadata.jsx | 17 +- src/components/NewBallot.jsx | 14 +- .../VotingToManageEmissionFunds.contract.js | 13 +- src/index.js | 1 + src/messages.js | 4 + src/stores/ContractsStore.js | 9 + 7 files changed, 181 insertions(+), 49 deletions(-) diff --git a/src/components/BallotCard.jsx b/src/components/BallotCard.jsx index 9c4e1fd..1c6a639 100644 --- a/src/components/BallotCard.jsx +++ b/src/components/BallotCard.jsx @@ -19,6 +19,7 @@ const zeroTimeTo = '00:00' @inject('commonStore', 'contractsStore', 'routing', 'ballotsStore') @observer export class BallotCard extends React.Component { + @observable cancelDeadline = 0 @observable startTime @observable endTime @observable timeTo = {} @@ -34,6 +35,11 @@ export class BallotCard extends React.Component { displayValue: zeroTimeTo, title: 'To close' } + @observable + timeToCancel = { + val: 0, + displayValue: zeroTimeTo + } @observable creatorMiningKey @observable creator @observable progress @@ -42,34 +48,50 @@ export class BallotCard extends React.Component { @observable freezeVotes @observable sendVotes @observable isFinalized + @observable isCanceled = false @observable canBeFinalized @observable hasAlreadyVoted @observable memo @computed - get finalizeButtonDisplayName() { - const displayName = this.isFinalized ? 'Finalized' : 'Finalize ballot' - return displayName - } - - @computed - get finalizeButtonClass() { - const cls = this.isFinalized - ? 'btn btn-primary btn-finalize disabled text-capitalize' - : 'btn btn-primary btn-finalize text-capitalize' - return cls - } - - @computed - get finalizeDescription() { + get cancelOrFinalizeButtonDisplayName() { if (this.isFinalized) { + return 'Finalized' + } else if (this.isCanceled) { + return 'Canceled' + } else if (this.timeToCancel.val > 0) { + return 'Cancel ballot' + } else { + return 'Finalize ballot' + } + } + + @computed + get cancelOrFinalizeButtonClass() { + if (this.isFinalized) { + return 'btn btn-primary btn-finalize disabled text-capitalize' + } else if (this.isCanceled) { + return 'btn btn-primary disabled text-capitalize' + } else if (this.timeToCancel.val > 0) { + return 'btn btn-danger text-capitalize' + } else { + return 'btn btn-primary btn-finalize text-capitalize' + } + } + + @computed + get cancelOrFinalizeDescription() { + if (this.isFinalized || this.isCanceled) { return '' + } else if (this.timeToCancel.val > 0) { + return `You can cancel this ballot within ${this.timeToCancel.displayValue}` + } else { + let description = 'Finalization is available after ballot time is finished' + if (this.canBeFinalized !== null) { + description += ' or all validators are voted' + } + return description } - let description = 'Finalization is available after ballot time is finished' - if (this.canBeFinalized !== null) { - description += ' or all validators are voted' - } - return description } @computed @@ -165,10 +187,20 @@ export class BallotCard extends React.Component { @action('Calculate time to start/finish') calcTimeTo = () => { const _now = moment() + const cancel = moment.utc(this.cancelDeadline, USDateTimeFormat) const start = moment.utc(this.startTime, USDateTimeFormat) const finish = moment.utc(this.endTime, USDateTimeFormat) - let msStart = start.diff(_now) - let msFinish = finish.diff(_now) + const msCancel = cancel.diff(_now) + const msStart = start.diff(_now) + const msFinish = finish.diff(_now) + + if (msCancel > 0) { + this.timeToCancel.val = msCancel + this.timeToCancel.displayValue = this.formatMs(msCancel, ':mm:ss') + } else { + this.timeToCancel.val = 0 + this.timeToCancel.displayValue = zeroTimeTo + } if (msStart > 0) { this.timeToStart.val = msStart + 5000 @@ -275,6 +307,9 @@ export class BallotCard extends React.Component { this.progress = Number(ballotInfo.progress) } this.isFinalized = Boolean(ballotInfo.isFinalized) + if (ballotInfo.hasOwnProperty('isCanceled')) { + this.isCanceled = Boolean(ballotInfo.isCanceled) + } if (ballotInfo.hasOwnProperty('canBeFinalizedNow')) { this.canBeFinalized = Boolean(ballotInfo.canBeFinalizedNow) } else { @@ -293,6 +328,7 @@ export class BallotCard extends React.Component { ballotsStore.ballotCards[pos].props.votingState.progress = this.progress } ballotsStore.ballotCards[pos].props.votingState.isFinalized = this.isFinalized + ballotsStore.ballotCards[pos].props.votingState.isCanceled = this.isCanceled ballotsStore.ballotCards[pos].props.votingState.canBeFinalized = this.canBeFinalized ballotsStore.ballotCards[pos].props.votingState.hasAlreadyVoted = this.hasAlreadyVoted @@ -304,11 +340,69 @@ export class BallotCard extends React.Component { ) } - finalize = async e => { - if (this.isFinalized) { + cancelOrFinalize = e => { + if (this.isFinalized || this.isCanceled) { + return + } + if (this.timeToCancel.val > 0) { + this.cancel(e) + } else { + this.finalize(e) + } + } + + cancel = async e => { + console.log('cancel function called') + const { votingState, contractsStore, commonStore, ballotsStore, votingType, id, pos } = this.props + const { push } = this.props.routing + const contract = this.getContract(contractsStore, votingType) + let canCancel = true + + if (!this.timeToCancel.val) { + canCancel = false + } + if (votingState.creator.toLowerCase() !== contractsStore.miningKey.toLowerCase()) { + canCancel = false + } + commonStore.showLoading() + if (!votingState.creationTime) { + canCancel = false + } else { + const currentTime = Number(await contract.getTime()) + if (currentTime - votingState.creationTime > contractsStore.ballotCancelingThreshold) { + canCancel = false + } + } + + if (!canCancel) { + commonStore.hideLoading() + swal('Warning!', messages.INVALID_CANCEL_MSG, 'warning') return } + sendTransactionByVotingKey( + this.props, + contract.address, + contract.cancelBallot(id), + async tx => { + this.isFinalized = false + this.isCanceled = true + ballotsStore.ballotCards[pos].props.votingState.isFinalized = this.isFinalized + ballotsStore.ballotCards[pos].props.votingState.isCanceled = this.isCanceled + if (this.canBeFinalized !== null) { + this.canBeFinalized = false + ballotsStore.ballotCards[pos].props.votingState.canBeFinalized = this.canBeFinalized + } + swal('Congratulations!', messages.CANCELED_SUCCESS_MSG, 'success').then(result => { + push(`${commonStore.rootPath}`) + }) + }, + messages.CANCEL_BALLOT_FAILED_TX + ) + } + + finalize = async e => { + console.log('finalize function called') if (this.timeToStart.val > 0) { swal('Warning!', messages.ballotIsNotActiveMsg(this.timeTo.displayValue), 'warning') return @@ -349,7 +443,9 @@ export class BallotCard extends React.Component { }) if (events.length > 0) { this.isFinalized = true + this.isCanceled = false ballotsStore.ballotCards[pos].props.votingState.isFinalized = this.isFinalized + ballotsStore.ballotCards[pos].props.votingState.isCanceled = this.isCanceled if (this.canBeFinalized !== null) { this.canBeFinalized = false ballotsStore.ballotCards[pos].props.votingState.canBeFinalized = this.canBeFinalized @@ -419,8 +515,17 @@ export class BallotCard extends React.Component { constructor(props) { super(props) - const { votingState } = this.props + const { votingState, contractsStore } = this.props // getTimes + if ( + votingState.hasOwnProperty('creationTime') && + contractsStore.ballotCancelingThreshold > 0 && + votingState.creator === contractsStore.miningKey + ) { + this.cancelDeadline = moment + .utc((votingState.creationTime + contractsStore.ballotCancelingThreshold) * 1000) + .format(USDateTimeFormat) + } this.startTime = moment.utc(votingState.startTime * 1000).format(USDateTimeFormat) this.endTime = moment.utc(votingState.endTime * 1000).format(USDateTimeFormat) this.calcTimeTo() @@ -442,6 +547,10 @@ export class BallotCard extends React.Component { } // getIsFinalized this.isFinalized = votingState.isFinalized + // getIsCanceled + if (votingState.hasOwnProperty('isCanceled')) { + this.isCanceled = votingState.isCanceled + } // canBeFinalizedNow if (votingState.hasOwnProperty('canBeFinalizedNow')) { this.canBeFinalized = votingState.canBeFinalizedNow @@ -478,9 +587,12 @@ export class BallotCard extends React.Component { showCard = () => { let { commonStore } = this.props let checkToFinalizeFilter = commonStore.isToFinalizeFilter - ? !this.isFinalized && (this.timeToFinish.val === 0 || this.canBeFinalized) && this.timeToStart.val === 0 + ? !this.isFinalized && + !this.isCanceled && + (this.timeToFinish.val === 0 || this.canBeFinalized) && + this.timeToStart.val === 0 : true - let show = commonStore.isActiveFilter ? !this.isFinalized : checkToFinalizeFilter + let show = commonStore.isActiveFilter ? !this.isFinalized && !this.isCanceled : checkToFinalizeFilter return show } @@ -664,10 +776,10 @@ export class BallotCard extends React.Component {
- -

{this.finalizeDescription}

+

{this.cancelOrFinalizeDescription}

{showHasAlreadyVotedLabel}
diff --git a/src/components/BallotEmissionFundsMetadata.jsx b/src/components/BallotEmissionFundsMetadata.jsx index 7ffaeb2..8e81065 100644 --- a/src/components/BallotEmissionFundsMetadata.jsx +++ b/src/components/BallotEmissionFundsMetadata.jsx @@ -35,16 +35,14 @@ export class BallotEmissionFundsMetadata extends React.Component { this.endDateTime = '...loading date...' let emissionReleaseTime = Number(await votingToManageEmissionFunds.emissionReleaseTime()) - let emissionReleaseThreshold = 0 + const emissionReleaseThreshold = Number(await votingToManageEmissionFunds.emissionReleaseThreshold()) const currentTime = Number(await votingToManageEmissionFunds.getTime()) const distributionThreshold = Number(await votingToManageEmissionFunds.distributionThreshold()) - if (currentTime > emissionReleaseTime) { - emissionReleaseThreshold = Number(await votingToManageEmissionFunds.emissionReleaseThreshold()) - const diff = Math.floor((currentTime - emissionReleaseTime) / emissionReleaseThreshold) - if (diff > 0) { - emissionReleaseTime += emissionReleaseThreshold * diff - } - } + emissionReleaseTime = votingToManageEmissionFunds.refreshEmissionReleaseTime( + emissionReleaseTime, + emissionReleaseThreshold, + currentTime + ) const releasePlusDistribution = emissionReleaseTime + distributionThreshold @@ -52,9 +50,6 @@ export class BallotEmissionFundsMetadata extends React.Component { this.beginDateTime = moment.unix(emissionReleaseTime).format(dateTimeFormat) this.endDateTime = moment.unix(releasePlusDistribution).format(dateTimeFormat) } else { - if (emissionReleaseThreshold === 0) { - emissionReleaseThreshold = Number(await votingToManageEmissionFunds.emissionReleaseThreshold()) - } const futureEmissionReleaseTime = emissionReleaseTime + emissionReleaseThreshold this.beginDateTime = moment.unix(futureEmissionReleaseTime).format(dateTimeFormat) this.endDateTime = moment.unix(futureEmissionReleaseTime + distributionThreshold).format(dateTimeFormat) diff --git a/src/components/NewBallot.jsx b/src/components/NewBallot.jsx index 19d8f50..e4069f6 100644 --- a/src/components/NewBallot.jsx +++ b/src/components/NewBallot.jsx @@ -280,15 +280,15 @@ export class NewBallot extends React.Component { 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()) - if (currentTime > emissionReleaseTime) { - const emissionReleaseThreshold = Number(await votingContract.emissionReleaseThreshold()) - const diff = Math.floor((currentTime - emissionReleaseTime) / emissionReleaseThreshold) - if (diff > 0) { - emissionReleaseTime += emissionReleaseThreshold * diff - } - } + emissionReleaseTime = votingContract.refreshEmissionReleaseTime( + emissionReleaseTime, + emissionReleaseThreshold, + currentTime + ) if (currentTime < emissionReleaseTime) { commonStore.hideLoading() diff --git a/src/contracts/VotingToManageEmissionFunds.contract.js b/src/contracts/VotingToManageEmissionFunds.contract.js index 95382e7..de69b43 100644 --- a/src/contracts/VotingToManageEmissionFunds.contract.js +++ b/src/contracts/VotingToManageEmissionFunds.contract.js @@ -17,7 +17,7 @@ export default class VotingToManageEmissionFunds { } // setters - cancelNewBallot() { + cancelBallot(_id) { return this.instance.methods.cancelNewBallot().encodeABI() } @@ -54,6 +54,17 @@ export default class VotingToManageEmissionFunds { return this.instance.methods.emissionReleaseTime().call() } + refreshEmissionReleaseTime(emissionReleaseTime, emissionReleaseThreshold, currentTime) { + let emissionReleaseTimeRefreshed = emissionReleaseTime + if (currentTime > emissionReleaseTime) { + const diff = Math.floor((currentTime - emissionReleaseTime) / emissionReleaseThreshold) + if (diff > 0) { + emissionReleaseTimeRefreshed += emissionReleaseThreshold * diff + } + } + return emissionReleaseTimeRefreshed + } + getBallotInfo(_id, _votingKey) { return this.instance.methods.getBallotInfo(_id).call() } diff --git a/src/index.js b/src/index.js index 704618c..217eef3 100644 --- a/src/index.js +++ b/src/index.js @@ -76,6 +76,7 @@ class AppMainRouter extends Component { contractsStore.getKeysBallotThreshold() contractsStore.getProxyBallotThreshold() + contractsStore.getBallotCancelingThreshold() contractsStore.getBallotsLimits() console.log('votingKey', contractsStore.votingKey) console.log('miningKey', contractsStore.miningKey) diff --git a/src/messages.js b/src/messages.js index b856e06..eb1ab03 100644 --- a/src/messages.js +++ b/src/messages.js @@ -5,9 +5,11 @@ messages.invalidVotingKeyMsg = key => { messages.VOTED_SUCCESS_MSG = 'You successfully voted' messages.BALLOT_CREATED_SUCCESS_MSG = 'You successfully created a new ballot' messages.FINALIZED_SUCCESS_MSG = 'You successfully finalized' +messages.CANCELED_SUCCESS_MSG = 'You successfully canceled' messages.ALREADY_FINALIZED_MSG = 'This ballot is already finalized' messages.INVALID_VOTE_MSG = "You can't vote on this ballot" messages.INVALID_FINALIZE_MSG = "You can't finalize this ballot" +messages.INVALID_CANCEL_MSG = "You can't cancel this ballot" messages.AFFECTED_KEY_IS_NOT_ADDRESS_MSG = "Ballot affectedKey isn't address" messages.MINING_KEY_IS_NOT_ADDRESS_MSG = "Ballot miningKey isn't address" messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG = "Proposed address isn't address" @@ -40,6 +42,8 @@ messages.VOTE_FAILED_TX = `Your transaction was failed. Please make sure you hav Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.` messages.FINALIZE_FAILED_TX = `Your transaction was failed. Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.` +messages.CANCEL_BALLOT_FAILED_TX = `Your transaction was failed. Make sure you don't have Transaction Error. +Exception thrown in contract code message in Metamask before you sign it.` messages.DESCRIPTION_IS_EMPTY = 'Description cannot be empty' messages.wrongRepo = repo => { return `There is no contracts.json in configured repo ${repo}` diff --git a/src/stores/ContractsStore.js b/src/stores/ContractsStore.js index 71eba21..19a9c4f 100644 --- a/src/stores/ContractsStore.js +++ b/src/stores/ContractsStore.js @@ -40,6 +40,8 @@ class ContractsStore { @observable keysBallotThreshold @observable minThresholdBallotThreshold @observable proxyBallotThreshold + @observable emissionFundsBallotThreshold + @observable ballotCancelingThreshold @observable validatorLimits @observable validatorsMetadata @observable netId @@ -69,6 +71,13 @@ class ContractsStore { this.emissionFundsBallotThreshold = this.proxyBallotThreshold } + @action('Get ballot canceling threshold') + getBallotCancelingThreshold = async () => { + this.ballotCancelingThreshold = this.votingToManageEmissionFunds + ? Number(await this.votingToManageEmissionFunds.ballotCancelingThreshold()) + : 0 + } + @action('Set web3Instance') setWeb3Instance = web3Config => { this.web3Instance = web3Config.web3Instance