2018-02-26 07:35:09 -08:00
|
|
|
|
# Implementation (2/2)
|
|
|
|
|
|
|
|
|
|
## Transactions
|
|
|
|
|
|
|
|
|
|
### Proposal Submission
|
|
|
|
|
|
|
|
|
|
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal`
|
|
|
|
|
transaction.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type TxGovSubmitProposal struct {
|
|
|
|
|
Title string // Title of the proposal
|
|
|
|
|
Description string // Description of the proposal
|
|
|
|
|
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
2018-02-26 08:28:57 -08:00
|
|
|
|
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
|
2018-02-26 07:35:09 -08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**State modifications:**
|
|
|
|
|
* Generate new `proposalID`
|
|
|
|
|
* Create new `Proposal`
|
|
|
|
|
* Initialise `Proposals` attributes
|
|
|
|
|
* Decrease balance of sender by `InitialDeposit`
|
|
|
|
|
* If `MinDeposit` is reached:
|
|
|
|
|
* Push `proposalID` in `ProposalProcessingQueueEnd`
|
|
|
|
|
* Store each validator's voting power in `ValidatorGovInfos`
|
|
|
|
|
|
|
|
|
|
A `TxGovSubmitProposal` transaction can be handled according to the following
|
|
|
|
|
pseudocode.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// PSEUDOCODE //
|
|
|
|
|
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
|
|
|
|
|
|
|
|
|
|
upon receiving txGovSubmitProposal from sender do
|
|
|
|
|
|
|
|
|
|
if !correctlyFormatted(txGovSubmitProposal) then
|
|
|
|
|
// check if proposal is correctly formatted. Includes fee payment.
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then
|
|
|
|
|
// InitialDeposit is negative or null OR sender has insufficient funds
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
sender.AtomBalance -= txGovSubmitProposal.InitialDeposit
|
|
|
|
|
|
|
|
|
|
proposalID = generate new proposalID
|
|
|
|
|
proposal = NewProposal()
|
|
|
|
|
|
|
|
|
|
proposal.Title = txGovSubmitProposal.Title
|
|
|
|
|
proposal.Description = txGovSubmitProposal.Description
|
|
|
|
|
proposal.Type = txGovSubmitProposal.Type
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.TotalDeposit = txGovSubmitProposal.InitialDeposit
|
2018-02-26 07:35:09 -08:00
|
|
|
|
proposal.SubmitBlock = CurrentBlock
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.Deposits.append({InitialDeposit, sender})
|
2018-03-02 03:08:18 -08:00
|
|
|
|
proposal.Submitter = sender
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.Votes.YesVotes = 0
|
|
|
|
|
proposal.Votes.NoVotes = 0
|
|
|
|
|
proposal.Votes.NoWithVetoVotes = 0
|
|
|
|
|
proposal.Votes.AbstainVotes = 0
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
activeProcedure = load(params, 'ActiveProcedure')
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then
|
|
|
|
|
// MinDeposit is not reached
|
|
|
|
|
|
|
|
|
|
proposal.VotingStartBlock = -1
|
|
|
|
|
proposal.InitTotalVotingPower = 0
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
// MinDeposit is reached
|
|
|
|
|
|
|
|
|
|
proposal.VotingStartBlock = CurrentBlock
|
|
|
|
|
proposal.InitTotalVotingPower = TotalVotingPower
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.InitProcedure = activeProcedure
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
for each validator in CurrentBondedValidators
|
|
|
|
|
// Store voting power of each bonded validator
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
validatorGovInfo = new ValidatorGovInfo
|
2018-02-26 07:35:09 -08:00
|
|
|
|
validatorGovInfo.InitVotingPower = validator.VotingPower
|
|
|
|
|
validatorGovInfo.Minus = 0
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
store(ValidatorGovInfos, <proposalID>:<validator.Address>, validatorGovInfo)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
ProposalProcessingQueue.push(proposalID)
|
|
|
|
|
|
|
|
|
|
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
|
|
|
|
|
return proposalID
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Deposit
|
|
|
|
|
|
|
|
|
|
Once a proposal is submitted, if
|
2018-03-01 08:21:35 -08:00
|
|
|
|
`Proposal.TotalDeposit < ActiveProcedure.MinDeposit`, Atom holders can send
|
2018-02-26 07:35:09 -08:00
|
|
|
|
`TxGovDeposit` transactions to increase the proposal's deposit.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type TxGovDeposit struct {
|
|
|
|
|
ProposalID int64 // ID of the proposal
|
|
|
|
|
Deposit int64 // Number of Atoms to add to the proposal's deposit
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**State modifications:**
|
|
|
|
|
* Decrease balance of sender by `deposit`
|
2018-03-01 08:21:35 -08:00
|
|
|
|
* Add `deposit` of sender in `proposal.Deposits`
|
|
|
|
|
* Increase `proposal.TotalDeposit` by sender's `deposit`
|
2018-02-26 07:35:09 -08:00
|
|
|
|
* If `MinDeposit` is reached:
|
|
|
|
|
* Push `proposalID` in `ProposalProcessingQueueEnd`
|
|
|
|
|
* Store each validator's voting power in `ValidatorGovInfos`
|
|
|
|
|
|
|
|
|
|
A `TxGovDeposit` transaction has to go through a number of checks to be valid.
|
|
|
|
|
These checks are outlined in the following pseudocode.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// PSEUDOCODE //
|
|
|
|
|
// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached
|
|
|
|
|
|
|
|
|
|
upon receiving txGovDeposit from sender do
|
|
|
|
|
// check if proposal is correctly formatted. Includes fee payment.
|
|
|
|
|
|
|
|
|
|
if !correctlyFormatted(txGovDeposit) then
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-02-26 08:28:57 -08:00
|
|
|
|
proposal = load(Proposals, txGovDeposit.ProposalID)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
if (proposal == nil) then
|
|
|
|
|
// There is no proposal for this proposalID
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
|
|
|
|
|
// deposit is negative or null OR sender has insufficient funds
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-03-01 08:21:35 -08:00
|
|
|
|
activeProcedure = load(params, 'ActiveProcedure')
|
|
|
|
|
|
|
|
|
|
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
|
2018-02-26 07:35:09 -08:00
|
|
|
|
// MinDeposit was reached
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
|
|
|
|
|
// Maximum deposit period reached
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
// sender can deposit
|
|
|
|
|
|
|
|
|
|
sender.AtomBalance -= txGovDeposit.Deposit
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.Deposits.append({txGovVote.Deposit, sender})
|
|
|
|
|
proposal.TotalDeposit += txGovDeposit.Deposit
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
|
2018-02-26 07:35:09 -08:00
|
|
|
|
// MinDeposit is reached, vote opens
|
|
|
|
|
|
|
|
|
|
proposal.VotingStartBlock = CurrentBlock
|
|
|
|
|
proposal.InitTotalVotingPower = TotalVotingPower
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.InitProcedure = activeProcedure
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
for each validator in CurrentBondedValidators
|
|
|
|
|
// Store voting power of each bonded validator
|
|
|
|
|
|
|
|
|
|
validatorGovInfo = NewValidatorGovInfo()
|
|
|
|
|
validatorGovInfo.InitVotingPower = validator.VotingPower
|
|
|
|
|
validatorGovInfo.Minus = 0
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
store(ValidatorGovInfos, <proposalID>:<validator.Address>, validatorGovInfo)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
|
2018-02-28 06:15:10 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
store(Proposals, txGovVote.ProposalID, proposal)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Vote
|
|
|
|
|
|
|
|
|
|
Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there,
|
|
|
|
|
bonded Atom holders are able to send `TxGovVote` transactions to cast their
|
|
|
|
|
vote on the proposal.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type TxGovVote struct {
|
|
|
|
|
ProposalID int64 // proposalID of the proposal
|
|
|
|
|
Option string // option from OptionSet chosen by the voter
|
2018-03-01 08:21:35 -08:00
|
|
|
|
ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to
|
2018-02-26 07:35:09 -08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**State modifications:**
|
|
|
|
|
* If sender is not a validator and validator has not voted, initialize or
|
|
|
|
|
increase minus of validator by sender's `voting power`
|
|
|
|
|
* If sender is not a validator and validator has voted, decrease
|
2018-03-01 08:21:35 -08:00
|
|
|
|
votes of `validatorOption` by sender's `voting power`
|
|
|
|
|
* If sender is not a validator, increase votes of `txGovVote.Option`
|
2018-02-26 07:35:09 -08:00
|
|
|
|
by sender's `voting power`
|
2018-03-01 08:21:35 -08:00
|
|
|
|
* If sender is a validator, increase votes of `txGovVote.Option` by
|
|
|
|
|
validator's `InitVotingPower - minus` (`minus` can be equal to 0)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
Votes need to be tied to a validator in order to compute validator's voting
|
|
|
|
|
power. If a delegator is bonded to multiple validators, it will have to send
|
|
|
|
|
one transaction per validator (the UI should facilitate this so that multiple
|
|
|
|
|
transactions can be sent in one "vote flow"). If the sender is the validator
|
2018-03-01 08:21:35 -08:00
|
|
|
|
itself, then it will input its own address as `ValidatorAddress`
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
Next is a pseudocode proposal of the way `TxGovVote` transactions are
|
2018-02-26 07:35:09 -08:00
|
|
|
|
handled:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// PSEUDOCODE //
|
|
|
|
|
// Check if TxGovVote is valid. If it is, count vote//
|
|
|
|
|
|
|
|
|
|
upon receiving txGovVote from sender do
|
|
|
|
|
// check if proposal is correctly formatted. Includes fee payment.
|
|
|
|
|
|
|
|
|
|
if !correctlyFormatted(txGovDeposit) then
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-02-26 08:28:57 -08:00
|
|
|
|
proposal = load(Proposals, txGovDeposit.ProposalID)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
if (proposal == nil) then
|
|
|
|
|
// There is no proposal for this proposalID
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-03-01 08:21:35 -08:00
|
|
|
|
validator = load(CurrentValidators, txGovVote.ValidatorAddress)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR
|
2018-02-26 07:35:09 -08:00
|
|
|
|
(validator == nil) then
|
|
|
|
|
|
|
|
|
|
// Throws if
|
|
|
|
|
// Option is not in Option Set of procedure that was active when vote opened OR if
|
2018-03-01 08:21:35 -08:00
|
|
|
|
// ValidatorAddress is not the address of a current validator
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-03-01 08:21:35 -08:00
|
|
|
|
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
if (option != nil)
|
2018-03-01 08:21:35 -08:00
|
|
|
|
// sender has already voted with the Atoms bonded to ValidatorAddress
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
if (proposal.VotingStartBlock < 0) OR
|
2018-03-01 08:21:35 -08:00
|
|
|
|
(CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR
|
|
|
|
|
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR
|
|
|
|
|
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR
|
|
|
|
|
(proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
// Throws if
|
|
|
|
|
// Vote has not started OR if
|
|
|
|
|
// Vote had ended OR if
|
2018-03-01 08:21:35 -08:00
|
|
|
|
// sender bonded Atoms to ValidatorAddress after start of vote OR if
|
|
|
|
|
// sender unbonded Atoms from ValidatorAddress after start of vote OR if
|
2018-02-26 09:15:48 -08:00
|
|
|
|
// special condition is met, i.e. proposal is accepted and closed
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-03-01 08:21:35 -08:00
|
|
|
|
validatorGovInfo = load(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
if (validatorGovInfo == nil)
|
|
|
|
|
// validator became validator after proposal entered voting period
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
// sender can vote, check if sender == validator and store sender's option in Options
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>, txGovVote.Option)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
if (sender != validator.address)
|
|
|
|
|
// Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
if sender does not have bonded Atoms to txGovVote.ValidatorAddress then
|
2018-02-26 07:35:09 -08:00
|
|
|
|
// check in Staking module
|
|
|
|
|
|
|
|
|
|
throw
|
|
|
|
|
|
|
|
|
|
else
|
2018-03-01 08:21:35 -08:00
|
|
|
|
validatorOption = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
if (validatorOption == nil)
|
|
|
|
|
// Validator has not voted already
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress)
|
|
|
|
|
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>, validatorGovInfo)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
// Validator has already voted
|
|
|
|
|
// Reduce votes of option chosen by validator by sender's bonded Amount
|
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
// increase votes of option chosen by sender by bonded Amount
|
2018-03-01 08:21:35 -08:00
|
|
|
|
|
|
|
|
|
senderOption = txGovVote.Option
|
|
|
|
|
propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
|
|
|
|
|
|
|
|
|
|
store(Proposals, txGovVote.ProposalID, proposal)
|
2018-02-27 07:04:44 -08:00
|
|
|
|
|
2018-02-26 07:35:09 -08:00
|
|
|
|
|
|
|
|
|
else
|
2018-03-01 08:21:35 -08:00
|
|
|
|
// sender is the address of the validator whose main Address is txGovVote.ValidatorAddress
|
2018-02-26 07:35:09 -08:00
|
|
|
|
// i.e. sender == validator
|
2018-02-27 07:04:44 -08:00
|
|
|
|
|
2018-03-01 08:21:35 -08:00
|
|
|
|
proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
|
|
|
|
|
|
|
|
|
|
store(Proposals, txGovVote.ProposalID, proposal)
|
|
|
|
|
|
|
|
|
|
|
2018-02-26 07:35:09 -08:00
|
|
|
|
```
|