(Feature) Add voting to manage emission funds

This commit is contained in:
Vadim Arasev 2018-09-17 19:17:56 +03:00
parent 12719ebd8c
commit 5639487713
16 changed files with 645 additions and 154 deletions

File diff suppressed because one or more lines are too long

View File

@ -136,4 +136,12 @@ button {
padding-top: 12px;
margin: 0;
word-break: break-word;
a {
color: $primary-color;
.sokol & {
color: $primary-color-sokol;
}
}
}

View File

@ -8,6 +8,9 @@ import swal from 'sweetalert2'
const ACCEPT = 1
const REJECT = 2
const SEND = 1
const BURN = 2
const FREEZE = 3
const USDateTimeFormat = 'MM/DD/YYYY h:mm:ss A'
const maxDetailsLength = 500
@ -102,6 +105,60 @@ export class BallotCard extends React.Component {
return votesPercents
}
@computed
get votesBurnNumber() {
let votes = this.burnVotes
if (isNaN(votes)) votes = 0
return votes
}
@computed
get votesBurnPercents() {
if (this.totalVoters <= 0) {
return 0
}
let votesPercents = Math.round((this.votesBurnNumber / this.totalVoters) * 100)
if (isNaN(votesPercents)) votesPercents = 0
return votesPercents
}
@computed
get votesFreezeNumber() {
let votes = this.freezeVotes
if (isNaN(votes)) votes = 0
return votes
}
@computed
get votesFreezePercents() {
if (this.totalVoters <= 0) {
return 0
}
let votesPercents = Math.round((this.votesFreezeNumber / this.totalVoters) * 100)
if (isNaN(votesPercents)) votesPercents = 0
return votesPercents
}
@computed
get votesSendNumber() {
let votes = this.sendVotes
if (isNaN(votes)) votes = 0
return votes
}
@computed
get votesSendPercents() {
if (this.totalVoters <= 0) {
return 0
}
let votesPercents = Math.round((this.votesSendNumber / this.totalVoters) * 100)
if (isNaN(votesPercents)) votesPercents = 0
return votesPercents
}
@action('Calculate time to start/finish')
calcTimeTo = () => {
const _now = moment()
@ -202,8 +259,18 @@ export class BallotCard extends React.Component {
async tx => {
const ballotInfo = await contract.getBallotInfo(id, contractsStore.votingKey)
this.totalVoters = Number(ballotInfo.totalVoters)
this.progress = Number(ballotInfo.progress)
if (ballotInfo.hasOwnProperty('totalVoters')) {
this.totalVoters = Number(ballotInfo.totalVoters)
} else {
this.burnVotes = ballotInfo.burnVotes
this.freezeVotes = ballotInfo.freezeVotes
this.sendVotes = ballotInfo.sendVotes
this.totalVoters =
Number(ballotInfo.burnVotes) + Number(ballotInfo.freezeVotes) + Number(ballotInfo.sendVotes)
}
if (ballotInfo.hasOwnProperty('progress')) {
this.progress = Number(ballotInfo.progress)
}
this.isFinalized = Boolean(ballotInfo.isFinalized)
if (ballotInfo.hasOwnProperty('canBeFinalizedNow')) {
this.canBeFinalized = Boolean(ballotInfo.canBeFinalizedNow)
@ -212,8 +279,16 @@ export class BallotCard extends React.Component {
}
this.hasAlreadyVoted = true
ballotsStore.ballotCards[pos].props.votingState.totalVoters = this.totalVoters
ballotsStore.ballotCards[pos].props.votingState.progress = this.progress
if (ballotInfo.hasOwnProperty('totalVoters')) {
ballotsStore.ballotCards[pos].props.votingState.totalVoters = this.totalVoters
} else {
ballotsStore.ballotCards[pos].props.votingState.burnVotes = this.burnVotes
ballotsStore.ballotCards[pos].props.votingState.freezeVotes = this.freezeVotes
ballotsStore.ballotCards[pos].props.votingState.sendVotes = this.sendVotes
}
if (ballotInfo.hasOwnProperty('progress')) {
ballotsStore.ballotCards[pos].props.votingState.progress = this.progress
}
ballotsStore.ballotCards[pos].props.votingState.isFinalized = this.isFinalized
ballotsStore.ballotCards[pos].props.votingState.canBeFinalized = this.canBeFinalized
ballotsStore.ballotCards[pos].props.votingState.hasAlreadyVoted = this.hasAlreadyVoted
@ -315,6 +390,8 @@ export class BallotCard extends React.Component {
return contractsStore.votingToChangeMinThreshold
case 'votingToChangeProxy':
return contractsStore.votingToChangeProxy
case 'votingToManageEmissionFunds':
return contractsStore.votingToManageEmissionFunds
case 'validatorMetadata':
return contractsStore.validatorMetadata
default:
@ -330,6 +407,8 @@ export class BallotCard extends React.Component {
return contractsStore.minThresholdBallotThreshold
case 'votingToChangeProxy':
return contractsStore.proxyBallotThreshold
case 'votingToManageEmissionFunds':
return contractsStore.emissionFundsBallotThreshold
default:
return contractsStore.keysBallotThreshold
}
@ -346,9 +425,18 @@ export class BallotCard extends React.Component {
this.creator = votingState.creator
this.creatorMiningKey = votingState.creatorMiningKey
// getTotalVoters
this.totalVoters = Number(votingState.totalVoters)
if (votingState.hasOwnProperty('totalVoters')) {
this.totalVoters = Number(votingState.totalVoters)
} else {
this.burnVotes = Number(votingState.burnVotes)
this.freezeVotes = Number(votingState.freezeVotes)
this.sendVotes = Number(votingState.sendVotes)
this.totalVoters = this.burnVotes + this.freezeVotes + this.sendVotes
}
// getProgress
this.progress = Number(votingState.progress)
if (votingState.hasOwnProperty('progress')) {
this.progress = Number(votingState.progress)
}
// getIsFinalized
this.isFinalized = votingState.isFinalized
// canBeFinalizedNow
@ -397,6 +485,8 @@ export class BallotCard extends React.Component {
return 'Keys'
case 'votingToChangeProxy':
return 'Proxy'
case 'votingToManageEmissionFunds':
return 'EmissionFunds'
default:
return ''
}
@ -425,66 +515,25 @@ export class BallotCard extends React.Component {
) : (
''
)
return (
<div className={ballotClass}>
<div className="ballots-about">
<div className="ballots-about-i ballots-about-i_name">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Proposer</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p className="ballots-i--name">{this.creator}</p>
</div>
</div>
{children}
<div className="ballots-about-i ballots-about-i_time">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Ballot Time</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p className="ballots-i--created">{this.startTime}</p>
<p className="ballots-i--time">
{this.timeTo.displayValue}&nbsp;({this.timeTo.title})
</p>
</div>
</div>
</div>
{/* TODO: Send / Burn / Freeze */}
{/* <div className="ballots-i-scale">
<div className="ballots-i-scale-column ballots-i-scale-column-3">
<button
className="btn btn-success ballots-i--vote_btn xl m-r-20"
onClick={e => this.vote({ choice: ACCEPT })}
type="button"
>
Send
</button>
<div className="vote-scale--container">
<p className="vote-scale--votes">{this.votesForNumber} Votes</p>
<p className="vote-scale--percentage">{this.votesForPercents}%</p>
<div className={voteScaleClass}>
<div
className="vote-scale--fill vote-scale--fill_send"
style={{ width: `${this.votesForPercents}%` }}
/>
</div>
</div>
</div>
let votingScale
if (votingType === 'votingToManageEmissionFunds') {
votingScale = (
<div className="ballots-i-scale">
<div className="ballots-i-scale-column ballots-i-scale-column-3">
<button
type="button"
onClick={e => this.vote({ choice: REJECT })}
onClick={e => this.vote({ choice: BURN })}
className="btn btn-danger ballots-i--vote_btn xl m-r-20"
>
Burn
</button>
<div className="vote-scale--container">
<p className="vote-scale--votes">{this.votesAgainstNumber} Votes</p>
<p className="vote-scale--percentage">{this.votesAgainstPercents}%</p>
<p className="vote-scale--votes">{this.votesBurnNumber} Votes</p>
<p className="vote-scale--percentage">{this.votesBurnPercents}%</p>
<div className={voteScaleClass}>
<div
className="vote-scale--fill vote-scale--fill_burn"
style={{ width: `${this.votesAgainstPercents}%` }}
style={{ width: `${this.votesBurnPercents}%` }}
/>
</div>
</div>
@ -492,24 +541,45 @@ export class BallotCard extends React.Component {
<div className="ballots-i-scale-column ballots-i-scale-column-3">
<button
type="button"
onClick={e => this.vote({ choice: REJECT })}
onClick={e => this.vote({ choice: FREEZE })}
className="btn btn-freeze ballots-i--vote_btn xl m-r-20"
>
Freeze
</button>
<div className="vote-scale--container">
<p className="vote-scale--votes">{this.votesAgainstNumber} Votes</p>
<p className="vote-scale--percentage">{this.votesAgainstPercents}%</p>
<p className="vote-scale--votes">{this.votesFreezeNumber} Votes</p>
<p className="vote-scale--percentage">{this.votesFreezePercents}%</p>
<div className={voteScaleClass}>
<div
className="vote-scale--fill vote-scale--fill_freeze"
style={{ width: `${this.votesAgainstPercents}%` }}
style={{ width: `${this.votesFreezePercents}%` }}
/>
</div>
</div>
</div>
</div> */}
{/* No / yes */}
<div className="ballots-i-scale-column ballots-i-scale-column-3">
<button
className="btn btn-success ballots-i--vote_btn xl m-r-20"
onClick={e => this.vote({ choice: SEND })}
type="button"
>
Send
</button>
<div className="vote-scale--container">
<p className="vote-scale--votes">{this.votesSendNumber} Votes</p>
<p className="vote-scale--percentage">{this.votesSendPercents}%</p>
<div className={voteScaleClass}>
<div
className="vote-scale--fill vote-scale--fill_send"
style={{ width: `${this.votesSendPercents}%` }}
/>
</div>
</div>
</div>
</div>
)
} else {
votingScale = (
<div className="ballots-i-scale">
<div className="ballots-i-scale-column">
<button
@ -547,6 +617,33 @@ export class BallotCard extends React.Component {
</button>
</div>
</div>
)
}
return (
<div className={ballotClass}>
<div className="ballots-about">
<div className="ballots-about-i ballots-about-i_name">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Proposer</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p className="ballots-i--name">{this.creator}</p>
</div>
</div>
{children}
<div className="ballots-about-i ballots-about-i_time">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Ballot Time</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p className="ballots-i--created">{this.startTime}</p>
<p className="ballots-i--time">
{this.timeTo.displayValue}&nbsp;({this.timeTo.title})
</p>
</div>
</div>
</div>
{votingScale}
<div className="info-container">
<div className="info info-minimum">
Minimum {threshold} from {contractsStore.validatorsLength} validators are required to pass the proposal

View File

@ -0,0 +1,32 @@
import React from 'react'
import { inject, observer } from 'mobx-react'
import { BallotCard } from './BallotCard.jsx'
@inject('contractsStore')
@observer
export class BallotEmissionFundsCard extends React.Component {
render() {
let { id, votingState, pos, contractsStore } = this.props
const amount = contractsStore.web3Instance.fromWei(votingState.amount, 'ether')
return (
<BallotCard votingType="votingToManageEmissionFunds" votingState={votingState} id={id} pos={pos}>
<div className="ballots-about-i ballots-about-i_proposed_receiver">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Proposed Receiver</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p>{votingState.receiver}</p>
</div>
</div>
<div className="ballots-about-i ballots-about-i_funds_amount">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Funds Amount</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p>{amount} POA</p>
</div>
</div>
</BallotCard>
)
}
}

View File

@ -0,0 +1,120 @@
import React from 'react'
import { observable, action } from 'mobx'
import { inject, observer } from 'mobx-react'
import moment from 'moment'
@inject('ballotStore', 'contractsStore')
@observer
export class BallotEmissionFundsMetadata extends React.Component {
@observable emissionFundsBalance
@observable noActiveBallotExists
@observable beginDateTime
@observable endDateTime
@action('Get EmissionFunds balance')
getEmissionFundsBalance = async () => {
this.emissionFundsBalance = 'Loading...'
this.emissionFundsBalance = await this.props.contractsStore.emissionFunds.balance()
}
@action('Get VotingToManageEmissionFunds.noActiveBallotExists')
getNoActiveBallotExists = async () => {
this.noActiveBallotExists = await this.props.contractsStore.votingToManageEmissionFunds.noActiveBallotExists()
}
@action('Get beginDateTime and endDateTime')
getDateTimeLimits = async () => {
const { votingToManageEmissionFunds } = this.props.contractsStore
const dateTimeFormat = 'MM/DD/YYYY HH:mm'
this.beginDateTime = '...loading date...'
this.endDateTime = '...loading date...'
let emissionReleaseTime = Number(await votingToManageEmissionFunds.emissionReleaseTime())
let emissionReleaseThreshold = 0
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
}
}
const releasePlusDistribution = emissionReleaseTime + distributionThreshold
if (currentTime < releasePlusDistribution) {
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)
}
}
constructor(props) {
super(props)
this.getEmissionFundsBalance()
this.getNoActiveBallotExists()
this.getDateTimeLimits()
}
render() {
const { ballotStore, contractsStore } = this.props
let note, explorerLink
if (this.noActiveBallotExists === true) {
note = (
<p>
The ballot can be created starting from <b>{this.beginDateTime}</b> and will end on <b>{this.endDateTime}</b>.
</p>
)
} else if (this.noActiveBallotExists !== true) {
note = <p>To be able to create a new ballot, the previous ballot of this type must be finalized.</p>
}
if (contractsStore.netId === '77') {
explorerLink = `https://sokol-explorer.poa.network/account/${contractsStore.emissionFunds.address}`
} else {
explorerLink = `https://poaexplorer.com/address/${contractsStore.emissionFunds.address}`
}
return (
<div>
<div className="hidden">
<div className="left">
<div className="form-el">
<label htmlFor="receiver">Address of funds receiver</label>
<input
type="text"
id="receiver"
value={ballotStore.ballotEmissionFunds.receiver}
onChange={e => ballotStore.changeBallotMetadata(e, 'receiver', 'ballotEmissionFunds')}
/>
<p className="hint">
The address to which the funds will be sent if `Send` operation obtains the majority of votes.
</p>
</div>
</div>
<div className="right">
<div className="form-el">
<label htmlFor="amount">Current amount of funds</label>
<input type="text" id="amount" value={this.emissionFundsBalance} disabled="disabled" />
<p className="hint">
Current balance of&nbsp;
<a href={explorerLink} target="_blank">
EmissionFunds contract
</a>.
</p>
</div>
</div>
</div>
<hr />
{note}
<br />
</div>
)
}
}

View File

@ -63,23 +63,6 @@ export class BallotKeysCard extends React.Component {
</div>
</div>
{miningKeyDiv}
{/* TODO: New ballot type */}
{/* <div className="ballots-about-i ballots-about-i_proposed_receiver">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Proposed Receiver</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p>0x4432c441EE96ef387CEC496709967Be6E27f57C8</p>
</div>
</div>
<div className="ballots-about-i ballots-about-i_funds_amount">
<div className="ballots-about-td ballots-about-td-title">
<p className="ballots-about-i--title">Funds Amount</p>
</div>
<div className="ballots-about-td ballots-about-td-value">
<p>10000 POA</p>
</div>
</div> */}
</BallotCard>
)
}

View File

@ -116,6 +116,14 @@ export class Ballots extends React.Component {
) {
continue
}
} else if (contractType === ballotStore.BallotType.emissionFunds) {
if (
String(votingState.receiver)
.toLowerCase()
.includes(searchTerm)
) {
continue
}
}
ballotCards.splice(i--, 1)

View File

@ -7,6 +7,7 @@ import { KeysTypes } from './KeysTypes.jsx'
import { BallotKeysMetadata } from './BallotKeysMetadata.jsx'
import { BallotMinThresholdMetadata } from './BallotMinThresholdMetadata.jsx'
import { BallotProxyMetadata } from './BallotProxyMetadata.jsx'
import { BallotEmissionFundsMetadata } from './BallotEmissionFundsMetadata.jsx'
import { messages } from '../messages'
import { constants } from '../constants'
import { sendTransactionByVotingKey } from '../helpers'
@ -44,40 +45,42 @@ export class NewBallot extends React.Component {
return false
}
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')
let neededHours = Math.floor(neededMinutes / 60)
let duration = moment.unix(ballotStore.endTimeUnix).diff(moment.unix(startTime), 'hours')
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')
let neededHours = Math.floor(neededMinutes / 60)
let duration = moment.unix(ballotStore.endTimeUnix).diff(moment.unix(startTime), 'hours')
if (duration < 0) {
duration = 0
}
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
}
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()
let 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
const twoWeeks = moment
.utc()
.add(14, 'days')
.format()
let 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) {
@ -132,7 +135,7 @@ export class NewBallot extends React.Component {
}
}
let isAddress = contractsStore.web3Instance.isAddress(ballotStore.ballotProxy.proposedAddress)
const isAddress = contractsStore.web3Instance.isAddress(ballotStore.ballotProxy.proposedAddress)
if (!isAddress) {
swal('Warning!', messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG, 'warning')
@ -141,7 +144,28 @@ export class NewBallot extends React.Component {
}
}
if (!ballotStore.isBallotForKey && !ballotStore.isBallotForMinThreshold && !ballotStore.isBallotForProxy) {
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.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
@ -150,11 +174,11 @@ export class NewBallot extends React.Component {
return true
}
createBallotForKeys = startTime => {
createBallotForKeys = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime: startTime,
endTime: ballotStore.endTimeUnix,
endTime: endTime,
affectedKey: ballotStore.ballotKeys.affectedKey,
affectedKeyType: ballotStore.ballotKeys.keyType,
newVotingKey: ballotStore.ballotKeys.newVotingKey,
@ -176,22 +200,22 @@ export class NewBallot extends React.Component {
return data
}
createBallotForMinThreshold = startTime => {
createBallotForMinThreshold = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime: startTime,
endTime: ballotStore.endTimeUnix,
endTime: endTime,
proposedValue: ballotStore.ballotMinThreshold.proposedValue,
memo: ballotStore.memo
}
return contractsStore.votingToChangeMinThreshold.createBallot(inputToMethod)
}
createBallotForProxy = startTime => {
createBallotForProxy = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime: startTime,
endTime: ballotStore.endTimeUnix,
endTime: endTime,
proposedValue: ballotStore.ballotProxy.proposedAddress,
contractType: ballotStore.ballotProxy.contractType,
memo: ballotStore.memo
@ -199,6 +223,17 @@ export class NewBallot extends React.Component {
return contractsStore.votingToChangeProxy.createBallot(inputToMethod)
}
createBallotForEmissionFunds = (startTime, endTime) => {
const { ballotStore, contractsStore } = this.props
const inputToMethod = {
startTime: startTime,
endTime: 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
@ -240,6 +275,42 @@ export class NewBallot extends React.Component {
}
}
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 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
}
}
if (currentTime < emissionReleaseTime) {
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) {
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
@ -259,16 +330,19 @@ export class NewBallot extends React.Component {
contractType = 'votingToChangeProxy'
contractInstance = contractsStore.votingToChangeProxy.votingToChangeProxyInstance
break
case ballotStore.BallotType.emissionFunds:
methodToCreateBallot = this.createBallotForEmissionFunds
contractType = 'votingToManageEmissionFunds'
contractInstance = contractsStore.votingToManageEmissionFunds.instance
break
default:
break
}
const startTime = this.getStartTimeUnix()
sendTransactionByVotingKey(
this.props,
contractInstance.options.address,
methodToCreateBallot(startTime),
methodToCreateBallot(startTime, endTime),
async tx => {
const events = await contractInstance.getPastEvents('BallotCreated', {
fromBlock: tx.blockNumber,
@ -322,9 +396,23 @@ export class NewBallot extends React.Component {
metadata = <BallotProxyMetadata />
minThreshold = contractsStore.proxyBallotThreshold
break
case ballotStore.BallotType.emissionFunds:
metadata = <BallotEmissionFundsMetadata />
minThreshold = contractsStore.emissionFundsBallotThreshold
break
default:
break
}
const emissionFundsManagementBallot = contractsStore.votingToManageEmissionFunds ? (
<div
className={this.menuItemActive(ballotStore.BallotType.emissionFunds)}
onClick={e => ballotStore.changeBallotType(e, ballotStore.BallotType.emissionFunds)}
>
Emission Funds Ballot
</div>
) : (
''
)
return (
<section className="container new">
<form action="" className="new-form">
@ -348,12 +436,13 @@ export class NewBallot extends React.Component {
>
Modify Proxy Contract Ballot
</div>
{emissionFundsManagementBallot}
</div>
<div className="info">
<p className="info-title">Information of the ballot</p>
<p className="info-title">Limits of the ballot</p>
<div className="info-i">
Minimum {minThreshold} from {contractsStore.validatorsLength}
validators are required to pass the proposal<br />
Minimum {minThreshold} from {contractsStore.validatorsLength} validators are required to pass the&nbsp;
proposal<br />
</div>
<div className="info-i">
You can create {contractsStore.validatorLimits.keys} ballot(s) for keys<br />

View File

@ -6,11 +6,13 @@ constants.ABIsSources = {
KeysManager: 'KeysManager.abi.json',
PoaNetworkConsensus: 'PoaNetworkConsensus.abi.json',
BallotStorage: 'BallotsStorage.abi.json',
EmissionFunds: 'EmissionFunds.abi.json',
ProxyStorage: 'ProxyStorage.abi.json',
ValidatorMetadata: 'ValidatorMetadata.abi.json',
VotingToChangeKeys: 'VotingToChangeKeys.abi.json',
VotingToChangeMinThreshold: 'VotingToChangeMinThreshold.abi.json',
VotingToChangeProxyAddress: 'VotingToChangeProxyAddress.abi.json'
VotingToChangeProxyAddress: 'VotingToChangeProxyAddress.abi.json',
VotingToManageEmissionFunds: 'VotingToManageEmissionFunds.abi.json'
}
constants.NEW_MINING_KEY = {
label: 'New Mining Key',

View File

@ -0,0 +1,15 @@
import Web3 from 'web3'
import { networkAddresses } from './addresses'
export default class EmissionFunds {
async init({ web3, netId }) {
const { EMISSION_FUNDS_ADDRESS } = networkAddresses(netId)
console.log('EmissionFunds address', EMISSION_FUNDS_ADDRESS)
this.web3_10 = new Web3(web3.currentProvider)
this.address = EMISSION_FUNDS_ADDRESS
}
balance() {
return this.web3_10.eth.getBalance(this.address)
}
}

View File

@ -0,0 +1,84 @@
import Web3 from 'web3'
import { networkAddresses } from './addresses'
import helpers from './helpers'
export default class VotingToManageEmissionFunds {
async init({ web3, netId }) {
const { VOTING_TO_MANAGE_EMISSION_FUNDS_ADDRESS } = networkAddresses(netId)
console.log('VotingToManageEmissionFunds address', VOTING_TO_MANAGE_EMISSION_FUNDS_ADDRESS)
const web3_10 = new Web3(web3.currentProvider)
const branch = helpers.getBranch(netId)
const votingToManageEmissionFundsABI = await helpers.getABI(branch, 'VotingToManageEmissionFunds')
this.instance = new web3_10.eth.Contract(votingToManageEmissionFundsABI, VOTING_TO_MANAGE_EMISSION_FUNDS_ADDRESS)
this.address = VOTING_TO_MANAGE_EMISSION_FUNDS_ADDRESS
}
// setters
cancelNewBallot() {
return this.instance.methods.cancelNewBallot().encodeABI()
}
createBallot({ startTime, endTime, receiver, memo }) {
return this.instance.methods.createBallot(startTime, endTime, receiver, memo).encodeABI()
}
finalize(_id) {
return this.instance.methods.finalize(_id).encodeABI()
}
vote(_id, choice) {
return this.instance.methods.vote(_id, choice).encodeABI()
}
// getters
ballotCancelingThreshold() {
return this.instance.methods.ballotCancelingThreshold().call()
}
canBeFinalizedNow(_id) {
return this.instance.methods.canBeFinalizedNow(_id).call()
}
distributionThreshold() {
return this.instance.methods.distributionThreshold().call()
}
emissionReleaseThreshold() {
return this.instance.methods.emissionReleaseThreshold().call()
}
emissionReleaseTime() {
return this.instance.methods.emissionReleaseTime().call()
}
getBallotInfo(_id, _votingKey) {
return this.instance.methods.getBallotInfo(_id).call()
}
getTime() {
return this.instance.methods.getTime().call()
}
hasAlreadyVoted(_id, _votingKey) {
return this.instance.methods.hasAlreadyVoted(_id, _votingKey).call()
}
isActive(_id) {
return this.instance.methods.isActive(_id).call()
}
isValidVote(_id, _votingKey) {
return this.instance.methods.isValidVote(_id, _votingKey).call()
}
nextBallotId() {
return this.instance.methods.nextBallotId().call()
}
noActiveBallotExists() {
return this.instance.methods.noActiveBallotExists().call()
}
}

View File

@ -49,7 +49,7 @@ class AppMainRouter extends Component {
let setVotingToChangeProxy = contractsStore.setVotingToChangeProxy(web3Config)
let setValidatorMetadata = contractsStore.setValidatorMetadata(web3Config)
await Promise.all([
let promises = [
setPoaConsensus,
setBallotsStorage,
setKeysManager,
@ -58,7 +58,15 @@ class AppMainRouter extends Component {
setVotingToChangeMinThreshold,
setVotingToChangeProxy,
setValidatorMetadata
])
]
if (web3Config.netId === '77') {
// if we're in Sokol
promises.push(contractsStore.setEmissionFunds(web3Config))
promises.push(contractsStore.setVotingToManageEmissionFunds(web3Config))
}
await Promise.all(promises)
await contractsStore.setMiningKey(web3Config)
await contractsStore.setVotingKey(web3Config)
@ -67,7 +75,6 @@ class AppMainRouter extends Component {
await contractsStore.getAllBallots()
contractsStore.getKeysBallotThreshold()
contractsStore.getMinThresholdBallotThreshold()
contractsStore.getProxyBallotThreshold()
contractsStore.getBallotsLimits()
console.log('votingKey', contractsStore.votingKey)

View File

@ -10,7 +10,7 @@ messages.INVALID_VOTE_MSG = "You can't vote on this ballot"
messages.INVALID_FINALIZE_MSG = "You can't finalize 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 = "Ballot proposedAddress isn't address"
messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG = "Proposed address isn't address"
messages.END_TIME_SHOULD_BE_GREATER_THAN_NOW_MSG = 'Ballot end time should be greater than now'
messages.BALLOT_TYPE_IS_EMPTY_MSG = 'Ballot type is empty'
messages.NO_METAMASK_MSG = `You haven't chosen any account in MetaMask.
@ -30,6 +30,10 @@ messages.SHOULD_BE_MORE_THAN_MIN_DURATION = (minDuration, duration, neededHours,
messages.SHOULD_BE_LESS_OR_EQUAL_14_DAYS = duration => {
return `Ballot end time should not be more than 14 days from now in UTC time. Current duration is ${duration} hours.`
}
messages.EMISSION_RELEASE_TIME_IN_FUTURE = emissionReleaseTime => {
return `You cannot create ballot right now. You'll be able to do that after ${emissionReleaseTime} UTC.`
}
messages.PREVIOUS_BALLOT_NOT_FINALIZED = 'Previous ballot should be finalized first.'
messages.BALLOT_CREATE_FAILED_TX = `Your transaction was failed. Please make sure you set correct parameters for ballot creation.
Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.`
messages.VOTE_FAILED_TX = `Your transaction was failed. Please make sure you haven't already voted for this ballot.

View File

@ -6,7 +6,8 @@ class BallotStore {
BallotType = {
keys: 1,
minThreshold: 2,
proxy: 3
proxy: 3,
emissionFunds: 4
}
KeysBallotType = {
add: 1,
@ -34,6 +35,8 @@ class BallotStore {
@observable ballotKeys
@observable ballotMinThreshold
@observable ballotProxy
@observable ballotEmissionFunds
@observable memo
constructor() {
@ -59,6 +62,11 @@ class BallotStore {
proposedAddress: '',
contractType: ''
}
this.ballotEmissionFunds = {
receiver: ''
}
this.memo = ''
}
@ -83,6 +91,11 @@ class BallotStore {
return this.ballotType === this.BallotType.proxy
}
@computed
get isBallotForEmissionFunds() {
return this.ballotType === this.BallotType.emissionFunds
}
@computed
get isAddKeysBallotType() {
return this.ballotKeys.keysBallotType === this.KeysBallotType.add

View File

@ -1,21 +1,9 @@
import { observable } from 'mobx'
class BallotsStore {
@observable activeKeysBallotsLength
@observable activeMinThresholdBallotsLength
@observable activeProxyBallotsLength
@observable ballotCards
@observable activeMinThresholdBallotsIDs
@observable activeProxyBallotsIDs
constructor() {
this.activeKeysBallotsLength = 0
this.activeMinThresholdBallotsLength = 0
this.activeProxyBallotsLength = 0
this.activeMinThresholdBallotsIDs = []
this.activeProxyBallotsIDs = []
this.ballotCards = []
}
}

View File

@ -3,11 +3,13 @@ import React from 'react'
import PoaConsensus from '../contracts/PoaConsensus.contract'
import BallotsStorage from '../contracts/BallotsStorage.contract'
import EmissionFunds from '../contracts/EmissionFunds.contract'
import KeysManager from '../contracts/KeysManager.contract'
import ProxyStorage from '../contracts/ProxyStorage.contract'
import VotingToChangeKeys from '../contracts/VotingToChangeKeys.contract'
import VotingToChangeMinThreshold from '../contracts/VotingToChangeMinThreshold.contract'
import VotingToChangeProxy from '../contracts/VotingToChangeProxy.contract'
import VotingToManageEmissionFunds from '../contracts/VotingToManageEmissionFunds.contract'
import ValidatorMetadata from '../contracts/ValidatorMetadata.contract'
import ballotStore from './BallotStore'
import ballotsStore from './BallotsStore'
@ -15,6 +17,7 @@ import commonStore from './CommonStore'
import { BallotKeysCard } from '../components/BallotKeysCard.jsx'
import { BallotMinThresholdCard } from '../components/BallotMinThresholdCard.jsx'
import { BallotProxyCard } from '../components/BallotProxyCard.jsx'
import { BallotEmissionFundsCard } from '../components/BallotEmissionFundsCard.jsx'
import { constants } from '../constants'
import 'babel-polyfill'
@ -22,11 +25,13 @@ import 'babel-polyfill'
class ContractsStore {
@observable poaConsensus
@observable ballotsStorage
@observable emissionFunds
@observable keysManager
@observable proxyStorage
@observable votingToChangeKeys
@observable votingToChangeMinThreshold
@observable votingToChangeProxy
@observable votingToManageEmissionFunds
@observable validatorMetadata
@observable votingKey
@observable miningKey
@ -55,18 +60,13 @@ class ContractsStore {
@action('Get keys ballot threshold')
getKeysBallotThreshold = async () => {
this.keysBallotThreshold = await this.ballotsStorage.ballotsStorageInstance.methods.getBallotThreshold(1).call()
}
@action('Get min threshold ballot threshold')
async getMinThresholdBallotThreshold() {
this.minThresholdBallotThreshold = await this.ballotsStorage.ballotsStorageInstance.methods
.getBallotThreshold(1)
.call()
this.minThresholdBallotThreshold = this.keysBallotThreshold
}
@action('Get proxy ballot threshold')
getProxyBallotThreshold = async () => {
this.proxyBallotThreshold = await this.ballotsStorage.ballotsStorageInstance.methods.getProxyThreshold().call()
this.emissionFundsBallotThreshold = this.proxyBallotThreshold
}
@action('Set web3Instance')
@ -93,6 +93,15 @@ class ContractsStore {
})
}
@action('Set EmissionFunds contract')
setEmissionFunds = async web3Config => {
this.emissionFunds = new EmissionFunds()
await this.emissionFunds.init({
web3: web3Config.web3Instance,
netId: web3Config.netId
})
}
@action('Set KeysManager contract')
setKeysManager = async web3Config => {
this.keysManager = new KeysManager()
@ -138,6 +147,15 @@ class ContractsStore {
})
}
@action('Set VotingToManageEmissionFunds contract')
setVotingToManageEmissionFunds = async web3Config => {
this.votingToManageEmissionFunds = new VotingToManageEmissionFunds()
await this.votingToManageEmissionFunds.init({
web3: web3Config.web3Instance,
netId: web3Config.netId
})
}
@action('Set ValidatorMetadata contract')
setValidatorMetadata = async web3Config => {
this.validatorMetadata = new ValidatorMetadata()
@ -171,12 +189,19 @@ class ContractsStore {
getAllBallots = async () => {
let keysNextBallotId = 0,
minThresholdNextBallotId = 0,
proxyNextBallotId = 0
proxyNextBallotId = 0,
emissionFundsNextBallotId = 0
try {
;[keysNextBallotId, minThresholdNextBallotId, proxyNextBallotId] = await this.getAllBallotsNextIDs()
;[
keysNextBallotId,
minThresholdNextBallotId,
proxyNextBallotId,
emissionFundsNextBallotId
] = await this.getAllBallotsNextIDs()
keysNextBallotId = Number(keysNextBallotId)
minThresholdNextBallotId = Number(minThresholdNextBallotId)
proxyNextBallotId = Number(proxyNextBallotId)
emissionFundsNextBallotId = Number(emissionFundsNextBallotId)
} catch (e) {
console.log(e.message)
}
@ -184,10 +209,12 @@ class ContractsStore {
const allKeysPromise = this.getCards(keysNextBallotId, 'votingToChangeKeys')
const allMinThresholdPromise = this.getCards(minThresholdNextBallotId, 'votingToChangeMinThreshold')
const allProxyPromise = this.getCards(proxyNextBallotId, 'votingToChangeProxy')
const allEmissionFundsPromise = this.getCards(emissionFundsNextBallotId, 'votingToManageEmissionFunds')
await Promise.all([allKeysPromise, allMinThresholdPromise, allProxyPromise])
await Promise.all([allKeysPromise, allMinThresholdPromise, allProxyPromise, allEmissionFundsPromise])
const allBallotsIDsLength = keysNextBallotId + minThresholdNextBallotId + proxyNextBallotId
const allBallotsIDsLength =
keysNextBallotId + minThresholdNextBallotId + proxyNextBallotId + emissionFundsNextBallotId
if (allBallotsIDsLength === 0) {
commonStore.hideLoading()
@ -315,6 +342,17 @@ class ContractsStore {
/>
)
break
case 'votingToManageEmissionFunds':
card = (
<BallotEmissionFundsCard
id={id}
type={ballotStore.BallotType.emissionFunds}
key={ballotsStore.ballotCards.length}
pos={ballotsStore.ballotCards.length}
votingState={votingState}
/>
)
break
default:
break
}
@ -333,7 +371,10 @@ class ContractsStore {
const keysNextBallotId = this.votingToChangeKeys.nextBallotId()
const minThresholdNextBallotId = this.votingToChangeMinThreshold.nextBallotId()
const proxyNextBallotId = this.votingToChangeProxy.nextBallotId()
return Promise.all([keysNextBallotId, minThresholdNextBallotId, proxyNextBallotId])
const emissionFundsNextBallotId = this.votingToManageEmissionFunds
? this.votingToManageEmissionFunds.nextBallotId()
: 0
return Promise.all([keysNextBallotId, minThresholdNextBallotId, proxyNextBallotId, emissionFundsNextBallotId])
}
@action