From 28f5216cbe4107387be14c73d99ddd080802663c Mon Sep 17 00:00:00 2001 From: Petr Hanzl Date: Thu, 23 Jan 2025 10:34:49 +0100 Subject: [PATCH] Add a proper documentation to the contracts --- contracts/adapters/RelayProxy.sol | 21 +- contracts/adapters/SFCToGovernable.sol | 56 +++- contracts/common/Decimal.sol | 3 +- contracts/governance/Constants.sol | 9 + contracts/governance/Governance.sol | 277 ++++++++++++++++-- contracts/governance/GovernanceSettings.sol | 22 +- contracts/governance/LRC.sol | 23 +- contracts/governance/Proposal.sol | 1 + contracts/model/Governable.sol | 21 +- .../NetworkParameterProposalFactory.sol | 14 +- .../pfactory/PlainTextProposalFactory.sol | 13 +- .../SlashingRefundProposalFactory.sol | 13 +- .../proposal/NetworkParameterProposal.sol | 32 +- contracts/proposal/PlainTextProposal.sol | 4 +- contracts/proposal/SlashingRefundProposal.sol | 5 +- .../proposal/SoftwareUpgradeProposal.sol | 8 +- contracts/proposal/base/BaseProposal.sol | 20 +- .../proposal/base/CallExecutableProposal.sol | 1 + contracts/proposal/base/Cancelable.sol | 4 + .../base/DelegatecallExecutableProposal.sol | 2 +- contracts/proposal/base/IProposal.sol | 61 ++-- .../proposal/base/NonExecutableProposal.sol | 1 + .../proposal/test/ExecLoggingProposal.sol | 2 + contracts/proposal/test/ExplicitProposal.sol | 4 + contracts/test/FakeVoteRecounter.sol | 1 + contracts/test/UnitTestConstantsManager.sol | 1 + contracts/test/UnitTestGovernable.sol | 1 + contracts/test/UnitTestGovernance.sol | 1 + contracts/verifiers/IProposalVerifier.sol | 34 ++- contracts/verifiers/OwnableVerifier.sol | 2 +- contracts/verifiers/ProposalTemplates.sol | 148 ++++++---- contracts/version/Version.sol | 8 +- contracts/votebook/votebook.sol | 40 ++- 33 files changed, 654 insertions(+), 199 deletions(-) diff --git a/contracts/adapters/RelayProxy.sol b/contracts/adapters/RelayProxy.sol index 5c445a9..1057c4b 100644 --- a/contracts/adapters/RelayProxy.sol +++ b/contracts/adapters/RelayProxy.sol @@ -2,6 +2,7 @@ pragma solidity ^0.5.0; import "../ownership/Ownable.sol"; +/// @dev RelayProxy is a contract for relaying calls to another contract contract RelayProxy { address public __destination; address public __owner; @@ -11,35 +12,35 @@ contract RelayProxy { __destination = _destination; } + /// @dev Emitted when ownership of the contract is transferred. event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + /// @dev Emitted when ownership of the contract is transferred. event DestinationChanged(address indexed previousRelay, address indexed newRelay); - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ + /// @dev Transfers ownership of the contract to a new account. - Can only be called by the current owner. + /// @param newOwner The address to transfer ownership to. function __transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Relay: new owner is the zero address"); emit OwnershipTransferred(__owner, newOwner); __owner = newOwner; } + /// @dev Sets the destination of the relay. + /// @param newDestination The address to relay calls to. function __setDestination(address newDestination) public onlyOwner { require(newDestination != address(0), "new owner address is the zero address"); emit OwnershipTransferred(__destination, newDestination); __destination = newDestination; } - /** - * @dev Returns true if the caller is the current owner. - */ + + /// @dev Returns true if the caller is the current owner. function isOwner() internal view returns (bool) { return msg.sender == __owner; } - /** - * @dev Throws if called by any account other than the owner. - */ + + /// @dev Throws if called by any account other than the owner. modifier onlyOwner() { require(isOwner(), "Relay: caller is not the owner"); _; diff --git a/contracts/adapters/SFCToGovernable.sol b/contracts/adapters/SFCToGovernable.sol index 1099a44..95357ac 100644 --- a/contracts/adapters/SFCToGovernable.sol +++ b/contracts/adapters/SFCToGovernable.sol @@ -2,41 +2,67 @@ pragma solidity ^0.5.0; import "../model/Governable.sol"; +// todo maybe move to governable +/// @dev SFC is an interface which implements some functions from the SFC required for the Governance contract. interface SFC { - function getStake(address _from, uint256 _toValidatorID) external view returns (uint256); + /// @dev Get the current stake of a delegator for a specific validator. + /// @param delegator The address of the delegator. + /// @param toValidatorID The ID of the validator to whom the stake is delegated. + /// @return The amount of stake delegated by given delegator to the specified validator. + function getStake(address delegator, uint256 toValidatorID) external view returns (uint256); - function getValidator(uint256 _validatorID) external view returns (uint256 status, uint256 deactivatedTime, uint256 deactivatedEpoch, - uint256 receivedStake, uint256 createdEpoch, uint256 createdTime, address auth); + /// @dev Get information about validator for the given ID. + /// @param validatorID The ID of the validator. + /// @return status The information about the validator. + function getValidator(uint256 validatorID) external view returns ( + uint256 status, + uint256 receivedStake, + address auth, + uint256 createdEpoch, + uint256 createdTime, + uint256 deactivatedTime, + uint256 deactivatedEpoch + ); - function getValidatorID(address _addr) external view returns (uint256); + /// @dev Get the current stake of a delegator for a specific validator. + /// @param validator The address of the validator. + /// @return The ID of the validator. + function getValidatorID(address validator) external view returns (uint256); - function totalActiveStake() external view returns (uint256); + /// @dev Get the sum of all active delegated stakes. + function getTotalActiveStake() external view returns (uint256); } contract SFCToGovernable is Governable { SFC internal sfc = SFC(address(0xFC00FACE00000000000000000000000000000000)); - // Gets the total weight of voters + /// @dev Retrieves the total active stake across all validators. + /// @return The sum of all active delegated stakes. function getTotalWeight() external view returns (uint256) { - return sfc.totalActiveStake(); + return sfc.getTotalActiveStake(); } - // Gets the received delegated weight - function getReceivedWeight(address addr) external view returns (uint256) { - uint256 validatorID = sfc.getValidatorID(addr); + /// @dev Retrieves the total delegated stake received by a specific validator. + /// @param validator The address of the validator whose received stake is being queried. + /// @return The total amount of stake delegated to the specified validator. + function getReceivedWeight(address validator) external view returns (uint256) { + uint256 validatorID = sfc.getValidatorID(validator); if (validatorID == 0) { return 0; } - (uint256 status, , , uint256 receivedStake, , ,) = sfc.getValidator(validatorID); + (uint256 status, uint256 receivedStake, , , , ,) = sfc.getValidator(validatorID); if (status != 0) { return 0; } return receivedStake; } - // Gets the voting weight which is delegated from the specified address to the specified address - function getWeight(address from, address to) external view returns (uint256) { - uint256 toValidatorID = sfc.getValidatorID(to); + /// @dev Retrieves the voting weight of a given delegator for a specified validator. + /// @param delegator The address of the delegator whose voting weight is being queried. + /// @param validator The address of the validator to whom the stake is delegated. + /// @return The voting weight (stake) of the delegator for the specified validator. + function getWeight(address delegator, address validator) external view returns (uint256) { + uint256 toValidatorID = sfc.getValidatorID(validator); if (toValidatorID == 0) { return 0; } @@ -44,6 +70,6 @@ contract SFCToGovernable is Governable { if (status != 0) { return 0; } - return sfc.getStake(from, toValidatorID); + return sfc.getStake(delegator, toValidatorID); } } diff --git a/contracts/common/Decimal.sol b/contracts/common/Decimal.sol index 62436f0..a1c9d35 100644 --- a/contracts/common/Decimal.sol +++ b/contracts/common/Decimal.sol @@ -1,7 +1,8 @@ pragma solidity ^0.5.0; +/// @dev Decimal is a library for handling decimal numbers library Decimal { - // unit is used for decimals, e.g. 0.123456 + /// @dev unit is used for decimals, e.g. 0.123456 function unit() internal pure returns (uint256) { return 1e18; } diff --git a/contracts/governance/Constants.sol b/contracts/governance/Constants.sol index f34c82d..33fcea1 100644 --- a/contracts/governance/Constants.sol +++ b/contracts/governance/Constants.sol @@ -3,6 +3,7 @@ pragma solidity ^0.5.0; import "../common/SafeMath.sol"; import "../common/Decimal.sol"; +/// @dev StatusConstants is a contract for managing status constants contract StatusConstants { enum Status { INITIAL, @@ -45,13 +46,21 @@ contract StatusConstants { uint256 constant TASK_VOTING = 1; } +/// @dev Constants is a contract for managing constants contract Constants is StatusConstants { using SafeMath for uint256; + /// @dev calculates the minimum number of votes required for a proposal + /// @param totalWeight The total weight of the voters + /// @param minVotesRatio The minimum ratio of votes required + /// @return The minimum number of votes required function minVotesAbsolute(uint256 totalWeight, uint256 minVotesRatio) public pure returns (uint256) { return totalWeight * minVotesRatio / Decimal.unit(); } + /// @dev converts bytes32 to string + /// @param _bytes32 The bytes32 to convert + /// @return The converted string function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) { bytes memory bytesArray = new bytes(32); for (uint256 i; i < 32; i++) { diff --git a/contracts/governance/Governance.sol b/contracts/governance/Governance.sol index 286c5cf..167d169 100644 --- a/contracts/governance/Governance.sol +++ b/contracts/governance/Governance.sol @@ -12,24 +12,30 @@ import "./GovernanceSettings.sol"; import "./LRC.sol"; import "../version/Version.sol"; import "../votebook/votebook.sol"; +import "hardhat/console.sol"; contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Version { using SafeMath for uint256; using LRC for LRC.Option; struct Vote { - uint256 weight; - uint256[] choices; + uint256 weight; // Weight of the vote + uint256[] choices; // Votes choices } struct ProposalState { Proposal.Parameters params; - // voting state - mapping(uint256 => LRC.Option) options; + mapping(uint256 => LRC.Option) options; // Voting state OptionID => LRC.Option uint256 winnerOptionID; - uint256 votes; // total weight of votes - + uint256 votes; // Sum of total weight of votes + + // Refer to Constants.sol + // 0 == STATUS_INITIAL + // 1 = STATUS_RESOLVED + // 1 << 1 == STATUS_FAILED + // 1 << 2 == STATUS_CANCELED + // 1 << 3 == STATUS_EXECUTION_EXPIRED uint256 status; } @@ -39,29 +45,78 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi uint256 proposalID; } - Governable governableContract; + Governable governableContract; // SFC to Governable adapter refer to SFCToGovernable.sol IProposalVerifier proposalVerifier; uint256 public lastProposalID; - Task[] tasks; - mapping(uint256 => ProposalState) proposals; - mapping(address => mapping(uint256 => uint256)) public overriddenWeight; // voter address, proposalID -> weight - mapping(address => mapping(address => mapping(uint256 => Vote))) _votes; // voter, delegationReceiver, proposalID -> Vote + Task[] tasks; // Tasks of all current proposals + + mapping(uint256 => ProposalState) proposals; // ProposalID => ProposalState + // voter address => proposalID => weight + mapping(address => mapping(uint256 => uint256)) public overriddenWeight; + // voter => delegationReceiver => proposalID => Vote + mapping(address => mapping(address => mapping(uint256 => Vote))) _votes; VotesBookKeeper votebook; + /// @dev Emitted when a new proposal is created. + /// @param proposalID ID of newly created proposal. event ProposalCreated(uint256 proposalID); + + /// @dev Emitted when a proposal is resolved. + /// @param proposalID ID of newly created proposal. event ProposalResolved(uint256 proposalID); + + /// @dev Emitted when a proposal is rejected. + /// @param proposalID ID of newly created proposal. event ProposalRejected(uint256 proposalID); + + /// @dev Emitted when a proposal is canceled. + /// @param proposalID ID of newly created proposal. event ProposalCanceled(uint256 proposalID); + + /// @dev Emitted when a proposal has expired. + /// @param proposalID ID of newly created proposal. event ProposalExecutionExpired(uint256 proposalID); + + /// @dev Emitted when a task (or tasks) is handled. + /// @param startIdx Index of the first task handled. + /// @param endIdx Index of the last task handled. + /// @param handled Number of tasks handled. event TasksHandled(uint256 startIdx, uint256 endIdx, uint256 handled); + + /// @dev Emitted when a task (or tasks) is erased. + /// @param quantity Number of tasks erased. event TasksErased(uint256 quantity); + + /// @dev Emitted when a weight of a voted has been override. + /// @param voter Address of the voter. + /// @param diff Weight difference. event VoteWeightOverridden(address voter, uint256 diff); + + /// @dev Emitted when a weight of a voted has been un-override. + /// @param voter Address of the voter. + /// @param diff Weight difference. event VoteWeightUnOverridden(address voter, uint256 diff); + + /// @dev Emitted when a vote is cast. + /// @param voter Address of the voter. + /// @param delegatedTo The address which the voter has delegated their stake to. + /// @param proposalID ID of the proposal. + /// @param choices Choices of the vote. + /// @param weight Weight of the vote. event Voted(address voter, address delegatedTo, uint256 proposalID, uint256[] choices, uint256 weight); + + /// @dev Emitted when a vote is canceled. + /// @param voter Address of the voter. + /// @param delegatedTo The address which the voter has delegated their stake to. + /// @param proposalID ID of the proposal. event VoteCanceled(address voter, address delegatedTo, uint256 proposalID); + /// @dev Initialize the contract. + /// @param _governableContract The address of the governable contract. + /// @param _proposalVerifier The address of the proposal verifier. + /// @param _votebook The address of the votebook contract. function initialize(address _governableContract, address _proposalVerifier, address _votebook) public initializer { ReentrancyGuard.initialize(); governableContract = Governable(_governableContract); @@ -69,36 +124,100 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi votebook = VotesBookKeeper(_votebook); } - function proposalParams(uint256 proposalID) public view returns (uint256 pType, Proposal.ExecType executable, uint256 minVotes, uint256 minAgreement, uint256[] memory opinionScales, bytes32[] memory options, address proposalContract, uint256 votingStartTime, uint256 votingMinEndTime, uint256 votingMaxEndTime) { + /// @dev Get the proposal params of a given proposal. + /// @param proposalID The ID of the proposal. + /// @return pType The type of the proposal. + /// @return executable The execution type of the proposal. + /// @return minVotes The minimum number of votes required. + /// @return minAgreement The minimum agreement required. + /// @return opinionScales The opinion scales. + /// @return options The options. + /// @return proposalContract The address of the proposal contract. + /// @return votingStartTime The start time of the voting. + /// @return votingMinEndTime The minimum end time of the voting. + /// @return votingMaxEndTime The maximum end time of the voting. + function proposalParams(uint256 proposalID) + public view returns ( + uint256 pType, + Proposal.ExecType executable, + uint256 minVotes, + uint256 minAgreement, + uint256[] memory opinionScales, + bytes32[] memory options, + address proposalContract, + uint256 votingStartTime, + uint256 votingMinEndTime, + uint256 votingMaxEndTime + ) + { Proposal.Parameters memory p = proposals[proposalID].params; - return (p.pType, p.executable, p.minVotes, p.minAgreement, p.opinionScales, p.options, p.proposalContract, p.deadlines.votingStartTime, p.deadlines.votingMinEndTime, p.deadlines.votingMaxEndTime); - } - + return ( + p.pType, + p.executable, + p.minVotes, + p.minAgreement, + p.opinionScales, + p.options, + p.proposalContract, + p.deadlines.votingStartTime, + p.deadlines.votingMinEndTime, + p.deadlines.votingMaxEndTime + ); + } + + /// @dev Get the state of a specific option in a proposal. + /// @param proposalID The ID of the proposal. + /// @param optionID The ID of the option. + /// @return votes Sum of total weight of votes + /// @return agreementRatio The agreement ratio for the option. + /// @return agreement The agreement value for the option. function proposalOptionState(uint256 proposalID, uint256 optionID) public view returns (uint256 votes, uint256 agreementRatio, uint256 agreement) { ProposalState storage prop = proposals[proposalID]; LRC.Option storage opt = prop.options[optionID]; return (opt.votes, LRC.agreementRatio(opt), opt.agreement); } + /// @dev Get the state of a proposal. + /// @param proposalID The ID of the proposal. + /// @return winnerOptionID The ID of the winning option. + /// @return votes Sum of total weight of votes + /// @return status The status of the proposal. function proposalState(uint256 proposalID) public view returns (uint256 winnerOptionID, uint256 votes, uint256 status) { ProposalState memory p = proposals[proposalID]; return (p.winnerOptionID, p.votes, p.status); } + /// @dev Get the vote details of a specific voter to a specific proposal. + /// @param from The address of the voter. + /// @param delegatedTo The address which the voter has delegated their stake to. + /// @param proposalID The ID of the proposal. + /// @return weight The weight of the vote. + /// @return choices The choices of the vote. function getVote(address from, address delegatedTo, uint256 proposalID) public view returns (uint256 weight, uint256[] memory choices) { Vote memory v = _votes[from][delegatedTo][proposalID]; return (v.weight, v.choices); } + /// @dev Get the total number of tasks. + /// @return The total number of tasks. function tasksCount() public view returns (uint256) { return (tasks.length); } + /// @dev Get the details of a specific task. + /// @param i The index of the task. + /// @return active Whether the task is active. + /// @return assignment The assignment type of the task. + /// @return proposalID The ID of the proposal associated with the task. function getTask(uint256 i) public view returns (bool active, uint256 assignment, uint256 proposalID) { Task memory t = tasks[i]; return (t.active, t.assignment, t.proposalID); } + /// @dev Cast a vote for a proposal. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. + /// @param choices The choices of the vote. function vote(address delegatedTo, uint256 proposalID, uint256[] calldata choices) nonReentrant external { if (delegatedTo == address(0)) { delegatedTo = msg.sender; @@ -108,7 +227,7 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi require(prop.params.proposalContract != address(0), "proposal with a given ID doesnt exist"); require(isInitialStatus(prop.status), "proposal isn't active"); - require(block.timestamp >= prop.params.deadlines.votingStartTime, "proposal voting has't begun"); + require(block.timestamp >= prop.params.deadlines.votingStartTime, "proposal voting hasn't begun"); require(_votes[msg.sender][delegatedTo][proposalID].weight == 0, "vote already exists"); require(choices.length == prop.params.options.length, "wrong number of choices"); @@ -116,6 +235,11 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi require(weight != 0, "zero weight"); } + /// @dev Create a new proposal. + /// The proposal must pay the proposal fee. + /// The proposal must pass the verification. + /// There must be at least one option. + /// @param proposalContract The address of the proposal contract. function createProposal(address proposalContract) nonReentrant external payable { require(msg.value == proposalFee(), "paid proposal fee is wrong"); @@ -129,6 +253,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi emit ProposalCreated(lastProposalID); } + /// @dev Internal function to create a new proposal. + + /// @param proposalID The ID of the proposal. + /// @param proposalContract The address of the proposal contract. function _createProposal(uint256 proposalID, address proposalContract) internal { require(proposalContract != address(0), "empty proposal address"); IProposal p = IProposal(proposalContract); @@ -151,6 +279,7 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi ok = proposalVerifier.verifyProposalContract(pType, proposalContract); require(ok, "proposal contract failed verification"); // save the parameters + // todo seems like we should check whether we are overwriting already existing proposal ProposalState storage prop = proposals[proposalID]; prop.params.pType = pType; prop.params.executable = executable; @@ -164,8 +293,9 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi prop.params.options = options; } - // cancelProposal cancels the proposal if no one managed to vote yet - // must be sent from the proposal contract + /// @dev Cancel a proposal if no votes have been cast. + /// Only the proposal contract can cancel the proposal. + /// @param proposalID The ID of the proposal. function cancelProposal(uint256 proposalID) nonReentrant external { ProposalState storage prop = proposals[proposalID]; require(prop.params.proposalContract != address(0), "proposal with a given ID doesnt exist"); @@ -177,7 +307,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi emit ProposalCanceled(proposalID); } - // handleTasks triggers proposal deadlines processing for a specified range of tasks + /// @dev Handle a specified range of tasks. + /// Emits TasksHandled event. + /// @param startIdx The starting index of the tasks. + /// @param quantity The number of tasks to handle. function handleTasks(uint256 startIdx, uint256 quantity) nonReentrant external { uint256 handled = 0; uint256 i; @@ -194,7 +327,9 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi msg.sender.transfer(handled.mul(taskHandlingReward())); } - // tasksCleanup erases inactive (handled) tasks backwards until an active task is met + /// @dev Clean up inactive tasks. + /// Emits TasksErased event. + /// @param quantity The number of tasks to clean up. function tasksCleanup(uint256 quantity) nonReentrant external { uint256 erased; for (erased = 0; tasks.length > 0 && erased < quantity; erased++) { @@ -211,7 +346,9 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi msg.sender.transfer(erased.mul(taskErasingReward())); } - // handleTask calls handleTaskAssignments and marks task as inactive if it was handled + /// @dev Handle a single specific task. + /// @param taskIdx The index of the task. + /// @return handled Whether the task was handled. function handleTask(uint256 taskIdx) internal returns (bool handled) { require(taskIdx < tasks.length, "incorrect task index"); Task storage task = tasks[taskIdx]; @@ -225,7 +362,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi return handled; } - // handleTaskAssignments iterates through assignment types and calls a specific handler + /// @dev Handle task for a proposal by its assignment. + /// @param proposalID The ID of the proposal. + /// @param assignment The assignment type. + /// @return handled Whether the task was handled. function handleTaskAssignments(uint256 proposalID, uint256 assignment) internal returns (bool handled) { ProposalState storage prop = proposals[proposalID]; if (!isInitialStatus(prop.status)) { @@ -238,7 +378,11 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi return false; } - // handleVotingTask handles only TASK_VOTING + /// @dev Handle voting tasks for a proposal with TASK_VOTING assignment. + /// Emits ProposalResolved or ProposalRejected event depending of the fate of the task. + /// @param proposalID The ID of the proposal. + /// @param prop The state of the proposal. + /// @return handled Whether the task was handled. function handleVotingTask(uint256 proposalID, ProposalState storage prop) internal returns (bool handled) { uint256 minVotesAbs = minVotesAbsolute(governableContract.getTotalWeight(), prop.params.minVotes); bool must = block.timestamp >= prop.params.deadlines.votingMaxEndTime; @@ -267,6 +411,11 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi return true; } + /// @dev Execute a proposal. + /// @param prop The state of the proposal. + /// @param winnerOptionID The ID of the winning option. + /// @return success Whether the execution was successful. + /// @return expired Whether the execution period has expired. function executeProposal(ProposalState storage prop, uint256 winnerOptionID) internal returns (bool, bool) { bool executable = prop.params.executable == Proposal.ExecType.CALL || prop.params.executable == Proposal.ExecType.DELEGATECALL; if (!executable) { @@ -290,9 +439,25 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi return (success, false); } + /// @dev Calculates votes options and finds the winner. + /// @param proposalID The ID of the proposal. + /// @return proposalResolved Whether the proposal is resolved. + /// @return winnerID The ID of the winning option. + /// @return votes The total number of votes. + function calculateVotingTally(uint256 proposalID) external view returns (bool proposalResolved, uint256 winnerID, uint256 votes) { + ProposalState storage prop = proposals[proposalID]; + (proposalResolved, winnerID) = _calculateVotingTally(prop); + return (proposalResolved, winnerID, prop.votes); + } + + /// @dev Calculates votes options and finds the winner. + /// @param prop The state of the proposal. + /// @return proposalResolved Whether the proposal is resolved. + /// @return winnerID The ID of the winning option. function _calculateVotingTally(ProposalState storage prop) internal view returns (bool, uint256) { uint256 minVotesAbs = minVotesAbsolute(governableContract.getTotalWeight(), prop.params.minVotes); uint256 mostAgreement = 0; + // todo rewrite with bool condition instead of winnerID overflow uint256 winnerID = prop.params.options.length; if (prop.votes < minVotesAbs) { return (false, winnerID); @@ -315,13 +480,9 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi return (winnerID != prop.params.options.length, winnerID); } - // calculateVotingTally calculates the voting tally and returns {is finished, won option ID, total weight of votes} - function calculateVotingTally(uint256 proposalID) external view returns (bool proposalResolved, uint256 winnerID, uint256 votes) { - ProposalState storage prop = proposals[proposalID]; - (proposalResolved, winnerID) = _calculateVotingTally(prop); - return (proposalResolved, winnerID, prop.votes); - } - + /// @dev Cancel a vote for a proposal. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. function cancelVote(address delegatedTo, uint256 proposalID) nonReentrant external { if (delegatedTo == address(0)) { delegatedTo = msg.sender; @@ -332,6 +493,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi _cancelVote(msg.sender, delegatedTo, proposalID); } + /// @dev Cancel a vote for a proposal. + /// Emits VoteCanceled event. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. function _cancelVote(address voter, address delegatedTo, uint256 proposalID) internal { Vote storage v = _votes[voter][delegatedTo][proposalID]; if (v.weight == 0) { @@ -352,21 +517,34 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi emit VoteCanceled(voter, delegatedTo, proposalID); } + /// @dev Cast a vote for a proposal. + /// Emits Voted event. + /// @param proposalID The ID of the proposal. + /// @param voter The address of the voter. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param choices The choices of the vote. + /// @param weight The weight of the vote. function makeVote(uint256 proposalID, address voter, address delegatedTo, uint256[] memory choices, uint256 weight) internal { _votes[voter][delegatedTo][proposalID] = Vote(weight, choices); addChoicesToProp(proposalID, choices, weight); - if (address(votebook) != address(0)) { votebook.onVoted(voter, delegatedTo, proposalID); } - emit Voted(voter, delegatedTo, proposalID, choices, weight); } + /// @dev Process a new vote for a proposal. + /// @param proposalID The ID of the proposal. + /// @param voterAddr The address of the voter. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param choices The choices of the vote. + /// @return The weight of the vote. function _processNewVote(uint256 proposalID, address voterAddr, address delegatedTo, uint256[] memory choices) internal returns (uint256) { if (delegatedTo == voterAddr) { // voter isn't a delegator + // todo voter is validator ? uint256 weight = governableContract.getReceivedWeight(voterAddr); + // todo overridden uint256 overridden = overriddenWeight[voterAddr][proposalID]; if (weight > overridden) { weight -= overridden; @@ -397,6 +575,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi } } + /// @dev Recount a votes weight for a proposal. + /// @param voterAddr The address of the voter. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. function recountVote(address voterAddr, address delegatedTo, uint256 proposalID) nonReentrant external { Vote storage v = _votes[voterAddr][delegatedTo][proposalID]; Vote storage vSuper = _votes[delegatedTo][delegatedTo][proposalID]; @@ -411,6 +593,11 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi require(beforeSelf != afterSelf || beforeSuper != afterSuper, "nothing changed"); } + /// @dev Recount a votes weight for a proposal. + /// @param voterAddr The address of the voter. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. + /// @return The weight of the vote. function _recountVote(address voterAddr, address delegatedTo, uint256 proposalID) internal returns (uint256) { uint256[] memory origChoices = _votes[voterAddr][delegatedTo][proposalID].choices; // cancel previous vote @@ -419,6 +606,11 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi return _processNewVote(proposalID, voterAddr, delegatedTo, origChoices); } + /// @dev Override the delegation weight for a proposal. + /// Emits VoteWeightOverridden event. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. + /// @param weight The weight of the vote. function overrideDelegationWeight(address delegatedTo, uint256 proposalID, uint256 weight) internal { uint256 overridden = overriddenWeight[delegatedTo][proposalID]; overridden = overridden.add(weight); @@ -431,6 +623,11 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi emit VoteWeightOverridden(delegatedTo, weight); } + /// @dev Un-override the delegation weight for a proposal. + /// Emits VoteWeightUnOverridden event. + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal. + /// @param weight The weight of the vote. function unOverrideDelegationWeight(address delegatedTo, uint256 proposalID, uint256 weight) internal { uint256 overridden = overriddenWeight[delegatedTo][proposalID]; overridden = overridden.sub(weight); @@ -443,6 +640,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi emit VoteWeightUnOverridden(delegatedTo, weight); } + /// @dev Add choices to a proposal. + /// @param proposalID The ID of the proposal. + /// @param choices The choices to be added. + /// @param weight The weight of the vote. function addChoicesToProp(uint256 proposalID, uint256[] memory choices, uint256 weight) internal { ProposalState storage prop = proposals[proposalID]; @@ -453,6 +654,10 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi } } + /// @dev Remove choices from a proposal. + /// @param proposalID The ID of the proposal. + /// @param choices The choices to be removed. + /// @param weight The weight of the vote. function removeChoicesFromProp(uint256 proposalID, uint256[] memory choices, uint256 weight) internal { ProposalState storage prop = proposals[proposalID]; @@ -463,14 +668,20 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi } } + /// @dev Add a task for a proposal. + /// @param proposalID The ID of the proposal. function addTasks(uint256 proposalID) internal { tasks.push(Task(true, TASK_VOTING, proposalID)); } + /// @dev Burn a specified amount of tokens. + /// @param amount The amount of tokens to burn. function burn(uint256 amount) internal { address(0).transfer(amount); } + /// @dev Sanitize the winner ID of a resolved proposal. + /// @param proposalID The ID of the proposal. function sanitizeWinnerID(uint256 proposalID) external { ProposalState storage prop = proposals[proposalID]; require(prop.status == STATUS_RESOLVED, "proposal isn't resolved"); @@ -479,6 +690,8 @@ contract Governance is Initializable, ReentrancyGuard, GovernanceSettings, Versi (, prop.winnerOptionID) = _calculateVotingTally(prop); } + /// @dev Upgrade the votebook contract. + /// @param _votebook The address of the new votebook contract. function upgrade(address _votebook) external { require(address(votebook) == address(0), "already set"); votebook = VotesBookKeeper(_votebook); diff --git a/contracts/governance/GovernanceSettings.sol b/contracts/governance/GovernanceSettings.sol index 62fd7f3..afa6924 100644 --- a/contracts/governance/GovernanceSettings.sol +++ b/contracts/governance/GovernanceSettings.sol @@ -6,9 +6,7 @@ import "../model/Governable.sol"; import "../proposal/SoftwareUpgradeProposal.sol"; import "./Constants.sol"; -/** - * @dev Various constants for governance governance settings - */ +/// @dev Various constants for governance governance settings contract GovernanceSettings is Constants { uint256 constant _proposalFee = _proposalBurntFee + _taskHandlingReward + _taskErasingReward; uint256 constant _proposalBurntFee = 50 * 1e18; @@ -17,32 +15,38 @@ contract GovernanceSettings is Constants { uint256 constant _maxOptions = 10; uint256 constant _maxExecutionPeriod = 3 days; - // @dev proposalFee is the fee for a proposal + /// @dev proposalFee is the fee for a proposal + /// @return proposal fee function proposalFee() public pure returns (uint256) { return _proposalFee; } - // @dev proposalBurntFee is the burnt part of fee for a proposal + /// @dev proposalBurntFee is the burnt part of fee for a proposal + /// @return proposal burn fee function proposalBurntFee() public pure returns (uint256) { return _proposalBurntFee; } - // @dev taskHandlingReward is a reward for handling each task + /// @dev taskHandlingReward is a reward for handling each task + /// @return task handling reward function taskHandlingReward() public pure returns (uint256) { return _taskHandlingReward; } - // @dev taskErasingReward is a reward for erasing each task + /// @dev taskErasingReward is a reward for erasing each task + /// @return task erasing reward function taskErasingReward() public pure returns (uint256) { return _taskErasingReward; } - // @dev maxOptions maximum number of options to choose + /// @dev maxOptions maximum number of options to choose + /// @return maximum number of options function maxOptions() public pure returns (uint256) { return _maxOptions; } - // maxExecutionPeriod is maximum time for which proposal is executable after maximum voting end date + /// maxExecutionPeriod is maximum time for which proposal is executable after maximum voting end date + /// @return maximum execution period function maxExecutionPeriod() public pure returns (uint256) { return _maxExecutionPeriod; } diff --git a/contracts/governance/LRC.sol b/contracts/governance/LRC.sol index 84400c3..072c9be 100644 --- a/contracts/governance/LRC.sol +++ b/contracts/governance/LRC.sol @@ -3,18 +3,20 @@ pragma solidity ^0.5.0; import "../common/Decimal.sol"; import "../common/SafeMath.sol"; -/** - * @dev LRC implements the "least resistant consensus" paper. More detailed description can be found in Fantom's docs. - */ + +/// @dev LRC implements the "least resistant consensus" paper. More detailed description can be found in Sonic's docs. library LRC { using SafeMath for uint256; + // Option represents a single option in the proposal struct Option { uint256 votes; uint256 agreement; } - // agreementRatio is a ratio of option agreement (higher -> option is less supported) + /// @dev ratio of option agreement (higher -> option is less supported) + /// @param self The option for which the agreement will be calculated + /// @return agreement ratio function agreementRatio(Option storage self) internal view returns (uint256) { if (self.votes == 0) { // avoid division by zero @@ -23,10 +25,18 @@ library LRC { return self.agreement.mul(Decimal.unit()).div(self.votes); } + /// @dev maxAgreementScale returns the maximum agreement scale + /// @param opinionScales The opinion scales + /// @return max agreement scale function maxAgreementScale(uint256[] storage opinionScales) internal view returns (uint256) { return opinionScales[opinionScales.length - 1]; } + /// @dev addVote adds a vote to the option + /// @param self The option to which the vote will be added + /// @param opinionID The voted opinion ID + /// @param weight The weight of the vote + /// @param opinionScales The opinion scales function addVote(Option storage self, uint256 opinionID, uint256 weight, uint256[] storage opinionScales) internal { require(opinionID < opinionScales.length, "wrong opinion ID"); @@ -36,6 +46,11 @@ library LRC { self.agreement = self.agreement.add(weight.mul(scale).div(maxAgreementScale(opinionScales))); } + /// @dev removeVote removes a vote from the option + /// @param self The option from which the vote will be removed + /// @param opinionID The voted opinion ID + /// @param weight The weight of the vote + /// @param opinionScales The opinion scales function removeVote(Option storage self, uint256 opinionID, uint256 weight, uint256[] storage opinionScales) internal { require(opinionID < opinionScales.length, "wrong opinion ID"); diff --git a/contracts/governance/Proposal.sol b/contracts/governance/Proposal.sol index c618385..2129882 100644 --- a/contracts/governance/Proposal.sol +++ b/contracts/governance/Proposal.sol @@ -2,6 +2,7 @@ pragma solidity ^0.5.0; import "../proposal/base/IProposal.sol"; +/// @dev Proposal is a library for handling proposals library Proposal { struct Timeline { uint256 votingStartTime; // date when the voting starts diff --git a/contracts/model/Governable.sol b/contracts/model/Governable.sol index 67049ef..a0e03fc 100644 --- a/contracts/model/Governable.sol +++ b/contracts/model/Governable.sol @@ -1,15 +1,20 @@ pragma solidity ^0.5.0; -/** - * @dev Governable defines the main interface for all governable items - */ + +/// @dev Governable defines the main interface for all governable items interface Governable { - // Gets the total weight of voters + /// @dev Retrieves the total active stake across all validators. + /// @return The sum of all active delegated stakes. function getTotalWeight() external view returns (uint256); - // Gets the received delegated weight - function getReceivedWeight(address addr) external view returns (uint256); + /// @dev Retrieves the total delegated stake received by a specific validator. + /// @param validator The address of the validator whose received stake is being queried. + /// @return The total amount of stake delegated to the specified validator. + function getReceivedWeight(address validator) external view returns (uint256); - // Gets the voting weight which is delegated from the specified address to the specified address - function getWeight(address from, address to) external view returns (uint256); + /// @dev Retrieves the voting weight of a given delegator for a specified validator. + /// @param delegator The address of the delegator whose voting weight is being queried. + /// @param validator The address of the validator to whom the stake is delegated. + /// @return The voting weight (stake) of the delegator for the specified validator. + function getWeight(address delegator, address validator) external view returns (uint256); } diff --git a/contracts/pfactory/NetworkParameterProposalFactory.sol b/contracts/pfactory/NetworkParameterProposalFactory.sol index 07230de..00bc95b 100644 --- a/contracts/pfactory/NetworkParameterProposalFactory.sol +++ b/contracts/pfactory/NetworkParameterProposalFactory.sol @@ -5,17 +5,27 @@ import "../governance/Governance.sol"; import "../proposal/NetworkParameterProposal.sol"; import "../verifiers/ScopedVerifier.sol"; +/// @dev NetworkParameterProposalFactory is a factory contract to create NetworkParameterProposal contract NetworkParameterProposalFactory is ScopedVerifier { using SafeMath for uint256; Governance internal gov; - address internal constsAddress; - address public lastNetworkProposal; + address internal constsAddress; // address of the Constants contract + address public lastNetworkProposal; // address of the last created NetworkParameterProposal constructor(address _governance, address _constsAddress) public { gov = Governance(_governance); constsAddress = _constsAddress; } + /// @dev create creates a new NetworkParameterProposal + /// @param __description The description of the proposal + /// @param __methodID The method ID of the proposal + /// @param __optionVals The option values of the proposal + /// @param __minVotes The minimum number of votes required + /// @param __minAgreement The minimum agreement required + /// @param __start The start time + /// @param __minEnd The minimum end time + /// @param __maxEnd The maximum end time function create( string memory __description, uint8 __methodID, diff --git a/contracts/pfactory/PlainTextProposalFactory.sol b/contracts/pfactory/PlainTextProposalFactory.sol index b891c6b..6377805 100644 --- a/contracts/pfactory/PlainTextProposalFactory.sol +++ b/contracts/pfactory/PlainTextProposalFactory.sol @@ -4,12 +4,22 @@ import "../governance/Governance.sol"; import "../proposal/PlainTextProposal.sol"; import "../verifiers/ScopedVerifier.sol"; +/// @dev PlainTextProposalFactory is a factory contract to create PlainTextProposal contract PlainTextProposalFactory is ScopedVerifier { Governance internal gov; constructor(address _govAddress) public { gov = Governance(_govAddress); } + /// @dev create creates a new PlainTextProposal + /// @param __name The name of the proposal + /// @param __description The description of the proposal + /// @param __options The options of the proposal + /// @param __minVotes The minimum number of votes required + /// @param __minAgreement The minimum agreement required + /// @param __start The start time + /// @param __minEnd The minimum end time + /// @param __maxEnd The maximum end time function create(string calldata __name, string calldata __description, bytes32[] calldata __options, uint256 __minVotes, uint256 __minAgreement, uint256 __start, uint256 __minEnd, uint256 __maxEnd) payable external { // use memory to avoid stack overflow @@ -19,10 +29,7 @@ contract PlainTextProposalFactory is ScopedVerifier { params[2] = __start; params[3] = __minEnd; params[4] = __maxEnd; - _create(__name, __description, __options, params); - } - function _create(string memory __name, string memory __description, bytes32[] memory __options, uint256[] memory params) internal { PlainTextProposal proposal = new PlainTextProposal(__name, __description, __options, params[0], params[1], params[2], params[3], params[4], address(0)); proposal.transferOwnership(msg.sender); diff --git a/contracts/pfactory/SlashingRefundProposalFactory.sol b/contracts/pfactory/SlashingRefundProposalFactory.sol index 9528310..173a1b3 100644 --- a/contracts/pfactory/SlashingRefundProposalFactory.sol +++ b/contracts/pfactory/SlashingRefundProposalFactory.sol @@ -4,6 +4,7 @@ import "../governance/Governance.sol"; import "../proposal/SlashingRefundProposal.sol"; import "../verifiers/ScopedVerifier.sol"; +/// @dev SlashingRefundProposalFactory is a factory contract to create SlashingRefundProposal contract SlashingRefundProposalFactory is ScopedVerifier { Governance internal gov; address internal sfcAddress; @@ -12,6 +13,14 @@ contract SlashingRefundProposalFactory is ScopedVerifier { sfcAddress = _sfcAddress; } + /// @dev create creates a new SlashingRefundProposal + /// @param __validatorID The ID of the validator + /// @param __description The description of the proposal + /// @param __minVotes The minimum number of votes required + /// @param __minAgreement The minimum agreement required + /// @param __start The start time + /// @param __minEnd The minimum end time + /// @param __maxEnd The maximum end time function create(uint256 __validatorID, string calldata __description, uint256 __minVotes, uint256 __minAgreement, uint256 __start, uint256 __minEnd, uint256 __maxEnd) payable external { // use memory to avoid stack overflow @@ -21,10 +30,6 @@ contract SlashingRefundProposalFactory is ScopedVerifier { params[2] = __start; params[3] = __minEnd; params[4] = __maxEnd; - _create(__validatorID, __description, params); - } - - function _create(uint256 __validatorID, string memory __description, uint256[] memory params) internal { require(SFC(sfcAddress).isSlashed(__validatorID), "validator isn't slashed"); SlashingRefundProposal proposal = new SlashingRefundProposal(__validatorID, __description, params[0], params[1], params[2], params[3], params[4], sfcAddress, address(0)); diff --git a/contracts/proposal/NetworkParameterProposal.sol b/contracts/proposal/NetworkParameterProposal.sol index 45b8949..aa50b24 100644 --- a/contracts/proposal/NetworkParameterProposal.sol +++ b/contracts/proposal/NetworkParameterProposal.sol @@ -35,9 +35,7 @@ interface ConstsI { function updateGasPriceBalancingCounterweight(uint256 v) external; // 15 } -/** - * @dev NetworkParameterProposal proposal - */ +/// @dev A proposal to update network parameters contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable { using SafeMath for uint256; Proposal.ExecType _exec; @@ -126,6 +124,10 @@ contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable event NetworkParameterUpgradeIsDone(uint256 newValue); + /// @dev Execute the proposal + /// @dev Depending on the methodID, the corresponding network parameter will be updated + /// @param selfAddr The address of the proposal + /// @param winnerOptionID The winning option ID function execute_delegatecall(address selfAddr, uint256 winnerOptionID) external { NetworkParameterProposal self = NetworkParameterProposal(selfAddr); uint256 __methodID = self.methodID(); @@ -174,6 +176,9 @@ contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable return decimals; } + /// @dev Convert a uint256 to a bytes + /// @param num The number to be converted + /// @return The converted bytes function uint256ToB(uint256 num) internal pure returns (bytes memory) { if (num == 0) { return bytes("0"); @@ -189,10 +194,17 @@ contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable return bstr; } + /// @dev Convert a uint256 to a string + /// @param num The number to be converted + /// @return The converted string function uint256ToStr(uint256 num) internal pure returns (string memory) { return string(uint256ToB(num)); } + /// @dev Convert a decimal to a string + /// @param interger The interger part of the decimal + /// @param fractional The fractional part of the decimal + /// @return The converted string function decimalToStr(uint256 interger, uint256 fractional) internal pure returns (string memory) { bytes memory intStr = uint256ToB(interger); bytes memory fraStr = uint256ToB(fractional); @@ -201,12 +213,19 @@ contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable return string(abi.encodePacked(intStr, fraStr)); } + /// @dev Unpack a decimal number + /// @param num The number to be unpacked + /// @param unit The unit of the number function unpackDecimal(uint256 num, uint256 unit) internal pure returns (uint256 interger, uint256 fractional) { + // todo maybe use require assert(unit <= 1e18); fractional = (num % unit).mul(1e18).div(unit); return (num / unit, trimFractional(1e18 + fractional)); } + /// @dev Trim the fractional part of a decimal + /// @param fractional The decimal to be trimmed + /// @return The trimmed decimal function trimFractional(uint256 fractional) internal pure returns (uint256) { if (fractional == 0) { return 0; @@ -217,6 +236,10 @@ contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable return fractional; } + /// @dev Convert an array of uint256 to an array of strings + /// @param vals The array of uint256 to be converted + /// @param unit The unit of the numbers + /// @param symbol The symbol of the numbers function uintsToStrs(uint256[] memory vals, uint256 unit, string memory symbol) internal pure returns (bytes32[] memory) { bytes32[] memory res = new bytes32[](vals.length); for (uint256 i = 0; i < vals.length; i++) { @@ -230,6 +253,9 @@ contract NetworkParameterProposal is DelegatecallExecutableProposal, Cancelable return res; } + /// @dev Convert a string to a bytes32 + /// @param str The string to be converted + /// @return The converted bytes32 function strToB32(string memory str) internal pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(str); require(tempEmptyStringTest.length <= 32, "string is too long"); diff --git a/contracts/proposal/PlainTextProposal.sol b/contracts/proposal/PlainTextProposal.sol index e16d680..d42326b 100644 --- a/contracts/proposal/PlainTextProposal.sol +++ b/contracts/proposal/PlainTextProposal.sol @@ -3,9 +3,7 @@ pragma solidity ^0.5.0; import "./base/Cancelable.sol"; import "./base/NonExecutableProposal.sol"; -/** - * @dev PlainText proposal - */ +/// @dev A plain text proposal contract PlainTextProposal is NonExecutableProposal, Cancelable { constructor(string memory __name, string memory __description, bytes32[] memory __options, uint256 __minVotes, uint256 __minAgreement, uint256 __start, uint256 __minEnd, uint256 __maxEnd, address verifier) public { diff --git a/contracts/proposal/SlashingRefundProposal.sol b/contracts/proposal/SlashingRefundProposal.sol index 24a1fb3..14d3dce 100644 --- a/contracts/proposal/SlashingRefundProposal.sol +++ b/contracts/proposal/SlashingRefundProposal.sol @@ -3,15 +3,14 @@ pragma solidity ^0.5.0; import "./base/DelegatecallExecutableProposal.sol"; import "./base/Cancelable.sol"; -/** - * @dev An interface to update slashing penalty ratio - */ +/// @dev An interface to update slashing penalty ratio interface SFC { function updateSlashingRefundRatio(uint256 validatorID, uint256 ratio) external; function isSlashed(uint256 validatorID) external returns(bool); } +/// @dev A proposal to refund a slashed validator contract SlashingRefundProposal is DelegatecallExecutableProposal, Cancelable { uint256 public validatorID; address public sfc; diff --git a/contracts/proposal/SoftwareUpgradeProposal.sol b/contracts/proposal/SoftwareUpgradeProposal.sol index 2ea4547..0110f39 100644 --- a/contracts/proposal/SoftwareUpgradeProposal.sol +++ b/contracts/proposal/SoftwareUpgradeProposal.sol @@ -3,16 +3,12 @@ pragma solidity ^0.5.0; import "./base/Cancelable.sol"; import "./base/DelegatecallExecutableProposal.sol"; -/** - * @dev An interface to update this contract to a destination address - */ +/// @dev An interface to update this contract to a destination address interface Upgradability { function upgradeTo(address newImplementation) external; } -/** - * @dev SoftwareUpgrade proposal - */ +/// @dev A proposal to upgrade a contract to a new implementation contract SoftwareUpgradeProposal is DelegatecallExecutableProposal, Cancelable { address public upgradeableContract; address public newImplementation; diff --git a/contracts/proposal/base/BaseProposal.sol b/contracts/proposal/base/BaseProposal.sol index 48efd0f..10fd7a7 100644 --- a/contracts/proposal/base/BaseProposal.sol +++ b/contracts/proposal/base/BaseProposal.sol @@ -5,9 +5,7 @@ import "./IProposal.sol"; import "../../verifiers/IProposalVerifier.sol"; import "../../governance/Proposal.sol"; -/** - * @dev A base for any proposal - */ +/// @dev A base for any proposal contract BaseProposal is IProposal { using SafeMath for uint256; @@ -17,13 +15,18 @@ contract BaseProposal is IProposal { uint256 _minVotes; uint256 _minAgreement; + // Static scale for front end + // i.e. [1, 2, 3, 4] will result in the scale being divided into 4 parts + // where 1 means the least agreement and 4 means the most uint256[] _opinionScales; - uint256 _start; - uint256 _minEnd; - uint256 _maxEnd; + uint256 _start; // Start of the voting + uint256 _minEnd; // Minimal end time of the voting + uint256 _maxEnd; // Maxinal end time of the voting - // verifyProposalParams passes proposal parameters to a given verifier + /// @dev Verify the parameters of the proposal using a given verifier. + /// @param verifier The address of the verifier contract. + /// @return bool indicating whether the proposal parameters are valid. function verifyProposalParams(address verifier) public view returns (bool) { IProposalVerifier proposalVerifier = IProposalVerifier(verifier); return proposalVerifier.verifyProposalParams(pType(), executable(), minVotes(), minAgreement(), opinionScales(), votingStartTime(), votingMinEndTime(), votingMaxEndTime()); @@ -56,14 +59,17 @@ contract BaseProposal is IProposal { } function votingStartTime() public view returns (uint256) { + // todo should be initialized as const on creation of the contract return block.timestamp + _start; } function votingMinEndTime() public view returns (uint256) { + // todo should be initialized as const on creation of the contract return votingStartTime() + _minEnd; } function votingMaxEndTime() public view returns (uint256) { + // todo should be initialized as const on creation of the contract return votingStartTime() + _maxEnd; } diff --git a/contracts/proposal/base/CallExecutableProposal.sol b/contracts/proposal/base/CallExecutableProposal.sol index 2151446..6f0e3be 100644 --- a/contracts/proposal/base/CallExecutableProposal.sol +++ b/contracts/proposal/base/CallExecutableProposal.sol @@ -3,6 +3,7 @@ pragma solidity ^0.5.0; import "./BaseProposal.sol"; import "../../governance/Proposal.sol"; +/// @dev A base for any proposal that can be executed contract CallExecutableProposal is BaseProposal { // Returns execution type function executable() public view returns (Proposal.ExecType) { diff --git a/contracts/proposal/base/Cancelable.sol b/contracts/proposal/base/Cancelable.sol index f44ec1d..c97921d 100644 --- a/contracts/proposal/base/Cancelable.sol +++ b/contracts/proposal/base/Cancelable.sol @@ -3,11 +3,15 @@ pragma solidity ^0.5.0; import "../../ownership/Ownable.sol"; import "../../governance/Governance.sol"; +/// @dev Extends any contract with the ability to cancel a proposal contract Cancelable is Ownable { constructor() public { Ownable.initialize(msg.sender); } + /// @dev Cancel a proposal + /// @param myID ID of the proposal to cancel + /// @param govAddress Address of the governance contract function cancel(uint256 myID, address govAddress) external onlyOwner { Governance gov = Governance(govAddress); gov.cancelProposal(myID); diff --git a/contracts/proposal/base/DelegatecallExecutableProposal.sol b/contracts/proposal/base/DelegatecallExecutableProposal.sol index 680eb32..e5c9a8f 100644 --- a/contracts/proposal/base/DelegatecallExecutableProposal.sol +++ b/contracts/proposal/base/DelegatecallExecutableProposal.sol @@ -3,8 +3,8 @@ pragma solidity ^0.5.0; import "./BaseProposal.sol"; import "../../governance/Proposal.sol"; +/// @dev A base for any proposal that can be executed with delegatecall contract DelegatecallExecutableProposal is BaseProposal { - // Returns execution type function executable() public view returns (Proposal.ExecType) { return Proposal.ExecType.DELEGATECALL; } diff --git a/contracts/proposal/base/IProposal.sol b/contracts/proposal/base/IProposal.sol index 135de56..53e9d84 100644 --- a/contracts/proposal/base/IProposal.sol +++ b/contracts/proposal/base/IProposal.sol @@ -2,43 +2,66 @@ pragma solidity ^0.5.0; import "../../governance/Proposal.sol"; -/** - * @dev An abstract proposal - */ +/// @dev An abstract proposal contract IProposal { - // Get type of proposal (e.g. plaintext, software upgrade) + + /// @dev Get type of proposal (e.g. plaintext, software upgrade) + /// If BaseProposal.sol is used, must be overridden + /// @return Proposal type function pType() external view returns (uint256); - // Proposal execution type when proposal gets resolved + + /// @dev Proposal execution type when proposal gets resolved + /// If BaseProposal.sol is used, must be overridden + /// @return Proposal execution type function executable() external view returns (Proposal.ExecType); - // Get min. turnout (ratio) + + /// @dev Get min. turnout (ratio) + /// @return Minimal necessary votes function minVotes() external view returns (uint256); - // Get min. agreement for options (ratio) + + /// @dev Get min. agreement for options (ratio) + /// @return Minimal agreement threshold for options function minAgreement() external view returns (uint256); - // Get scales for opinions + + /// @dev Get scales for opinions + /// @return Scales for opinions function opinionScales() external view returns (uint256[] memory); - // Get options to choose from + + /// @dev Get options to choose from + /// @return Options to choose from function options() external view returns (bytes32[] memory); - // Get date when the voting starts + + /// @dev Get date when the voting starts + /// @return Timestamp when the voting starts function votingStartTime() external view returns (uint256); - // Get date of earliest possible voting end + + /// @dev Get date of earliest possible voting end + /// @return Timestamp of earliest possible voting end function votingMinEndTime() external view returns (uint256); - // Get date of latest possible voting end + + /// @dev Get date of latest possible voting end + /// @return Timestamp of latest possible voting end function votingMaxEndTime() external view returns (uint256); - // execute proposal logic on approval (if executable == call) - // Called via call opcode from governance contract + /// @dev execute proposal logic on approval (if executable == call) + /// @dev Called via call opcode from governance contract + /// @param optionID The index of the option to execute function execute_call(uint256 optionID) external; - // execute proposal logic on approval (if executable == delegatecall) - // Called via delegatecall opcode from governance contract, hence selfAddress is provided + /// @dev execute proposal logic on approval (if executable == delegatecall) + /// @dev Called via delegatecall opcode from governance contract, hence selfAddress is provided + /// @param selfAddress The address of the proposal contract + /// @param optionID The index of the option to execute function execute_delegatecall(address selfAddress, uint256 optionID) external; - // Get human-readable name + /// @dev Get human-readable name + /// @return Human-readable name function name() external view returns (string memory); - // Get human-readable description + /// @dev Get human-readable description + /// @return Human-readable description function description() external view returns (string memory); - // Standard proposal types. The standard may be outdated, actual proposal templates may differ + /// @dev Standard proposal types. The standard may be outdated, actual proposal templates may differ enum StdProposalTypes { NOT_INIT, UNKNOWN_NON_EXECUTABLE, diff --git a/contracts/proposal/base/NonExecutableProposal.sol b/contracts/proposal/base/NonExecutableProposal.sol index cf84382..0e62fb0 100644 --- a/contracts/proposal/base/NonExecutableProposal.sol +++ b/contracts/proposal/base/NonExecutableProposal.sol @@ -3,6 +3,7 @@ pragma solidity ^0.5.0; import "./BaseProposal.sol"; import "../../governance/Proposal.sol"; +/// @dev A base for any non-executable proposal contract NonExecutableProposal is BaseProposal { function pType() public view returns (uint256) { return uint256(StdProposalTypes.UNKNOWN_NON_EXECUTABLE); diff --git a/contracts/proposal/test/ExecLoggingProposal.sol b/contracts/proposal/test/ExecLoggingProposal.sol index d88b581..417167d 100644 --- a/contracts/proposal/test/ExecLoggingProposal.sol +++ b/contracts/proposal/test/ExecLoggingProposal.sol @@ -4,6 +4,8 @@ import "../PlainTextProposal.sol"; import "../../governance/Governance.sol"; import "../../governance/Proposal.sol"; +/// @dev A proposal that can be stores data about NonDelegateCall +/// @dev Used for testing purposes contract ExecLoggingProposal is PlainTextProposal { Proposal.ExecType _exec; diff --git a/contracts/proposal/test/ExplicitProposal.sol b/contracts/proposal/test/ExplicitProposal.sol index 94bc837..224dd43 100644 --- a/contracts/proposal/test/ExplicitProposal.sol +++ b/contracts/proposal/test/ExplicitProposal.sol @@ -2,6 +2,9 @@ pragma solidity ^0.5.0; import "../base/BaseProposal.sol"; +// todo maybe rename to MockProposal +/// @dev A proposal with all parameters explicitly set +/// @dev Used for testing purposes contract ExplicitProposal is BaseProposal { using SafeMath for uint256; @@ -72,6 +75,7 @@ contract ExplicitProposal is BaseProposal { return _maxEnd; } + // todo what is differennce function execute_delegatecall(address, uint256) external {} function execute_call(uint256) external {} } diff --git a/contracts/test/FakeVoteRecounter.sol b/contracts/test/FakeVoteRecounter.sol index 72dbbbf..8789883 100644 --- a/contracts/test/FakeVoteRecounter.sol +++ b/contracts/test/FakeVoteRecounter.sol @@ -1,5 +1,6 @@ pragma solidity ^0.5.0; +/// @dev FakeVoteRecounter is a VoteCounter contract for used unit tests contract FakeVoteRecounter { address public expectVoterAddr; address public expectDelegatedTo; diff --git a/contracts/test/UnitTestConstantsManager.sol b/contracts/test/UnitTestConstantsManager.sol index df8f96a..10504f9 100644 --- a/contracts/test/UnitTestConstantsManager.sol +++ b/contracts/test/UnitTestConstantsManager.sol @@ -3,6 +3,7 @@ pragma solidity ^0.5.0; import "../ownership/Ownable.sol"; import "../common/Decimal.sol"; +/// @dev UnitTestConstantsManager is a contract for managing constants for unit tests contract UnitTestConstantsManager is Ownable { // Minimum amount of stake for a validator, i.e., 500000 FTM uint256 public minSelfStake; diff --git a/contracts/test/UnitTestGovernable.sol b/contracts/test/UnitTestGovernable.sol index fadf258..8b070a3 100644 --- a/contracts/test/UnitTestGovernable.sol +++ b/contracts/test/UnitTestGovernable.sol @@ -2,6 +2,7 @@ pragma solidity ^0.5.0; import "../model/Governable.sol"; +/// @dev UnitTestGovernable is a contract for managing stakes and for unit tests contract UnitTestGovernable is Governable { mapping(address => mapping(address => uint256)) delegations; // from, to -> amount mapping(address => uint256) rcvDelegations; diff --git a/contracts/test/UnitTestGovernance.sol b/contracts/test/UnitTestGovernance.sol index c5b742c..1c74f09 100644 --- a/contracts/test/UnitTestGovernance.sol +++ b/contracts/test/UnitTestGovernance.sol @@ -2,6 +2,7 @@ pragma solidity ^0.5.0; import "../governance/Governance.sol"; +/// @dev UnitTestGovernance is a mock Governance which is used for unit tests contract UnitTestGovernance is Governance { // reduce proposal fee in tests function proposalFee() public pure returns (uint256) { diff --git a/contracts/verifiers/IProposalVerifier.sol b/contracts/verifiers/IProposalVerifier.sol index a9eaf2e..5d8e4ec 100644 --- a/contracts/verifiers/IProposalVerifier.sol +++ b/contracts/verifiers/IProposalVerifier.sol @@ -2,13 +2,33 @@ pragma solidity ^0.5.0; import "../governance/Proposal.sol"; -/** - * @dev A verifier can verify a proposal's inputs such as proposal parameters and proposal contract. - */ + +/// @dev A verifier can verify a proposal's inputs such as proposal parameters and proposal contract. interface IProposalVerifier { - // Verifies proposal parameters with respect to the stored template of same type - function verifyProposalParams(uint256 pType, Proposal.ExecType executable, uint256 minVotes, uint256 minAgreement, uint256[] calldata opinionScales, uint256 start, uint256 minEnd, uint256 maxEnd) external view returns (bool); + /// @dev Verify proposal parameters - Each proposal type has a template to which the data in proposal must correspond + /// @param id The ID of the template + /// @param executable The type of execution + /// @param minVotes The minimum number of votes required + /// @param minAgreement The minimum agreement required + /// @param opinionScales The opinion scales + /// @param start The start time + /// @param minEnd The minimum end time + /// @param maxEnd The maximum end time + /// @return true if the proposal parameters are valid + function verifyProposalParams( + uint256 id, + Proposal.ExecType executable, + uint256 minVotes, + uint256 minAgreement, + uint256[] calldata opinionScales, + uint256 start, + uint256 minEnd, + uint256 maxEnd + ) external view returns (bool); - // Verifies proposal contract of the specified type and address - function verifyProposalContract(uint256 pType, address propAddr) external view returns (bool); + /// @dev Verify proposal contract - Each proposal type has a template to which the data in proposal must correspond + /// @param id The ID of the template + /// @param propAddr The address of the proposal contract + /// @return true if the proposal contract is valid + function verifyProposalContract(uint256 id, address propAddr) external view returns (bool); } diff --git a/contracts/verifiers/OwnableVerifier.sol b/contracts/verifiers/OwnableVerifier.sol index 72ec50c..b5e9c70 100644 --- a/contracts/verifiers/OwnableVerifier.sol +++ b/contracts/verifiers/OwnableVerifier.sol @@ -4,7 +4,7 @@ import "../ownership/Ownable.sol"; import "../governance/Governance.sol"; import "./ScopedVerifier.sol"; - +/// @dev OwnableVerifier is a verifier that only allows the owner to create proposals contract OwnableVerifier is ScopedVerifier, Ownable { constructor(address govAddress) public { Ownable.initialize(msg.sender); diff --git a/contracts/verifiers/ProposalTemplates.sol b/contracts/verifiers/ProposalTemplates.sol index 9e44c80..07e933f 100644 --- a/contracts/verifiers/ProposalTemplates.sol +++ b/contracts/verifiers/ProposalTemplates.sol @@ -7,18 +7,20 @@ import "../ownership/Ownable.sol"; import "../version/Version.sol"; import "../common/Initializable.sol"; -/** - * @dev A storage of current proposal templates. Any new proposal will be verified against the stored template of its type. - * Verification checks for parameters and calls additional verifier (if any). - * Supposed to be owned by the governance contract - */ +// @dev A storage of current proposal templates. Any new proposal will be verified against the stored template of its type. +// Verification checks for parameters and calls additional verifier (if any). +// Supposed to be owned by the governance contract contract ProposalTemplates is Initializable, IProposalVerifier, Ownable, Version { function initialize() public initializer { Ownable.initialize(msg.sender); } - event AddedTemplate(uint256 pType); - event ErasedTemplate(uint256 pType); + /// @dev Event emitted when a new template is added + /// @param id The ID of the template + event AddedTemplate(uint256 id); + /// @dev Event emitted when a template is erased + /// @param id The ID of the template + event ErasedTemplate(uint256 id); // Stored data for a proposal template struct ProposalTemplate { @@ -35,26 +37,48 @@ contract ProposalTemplates is Initializable, IProposalVerifier, Ownable, Version } // templates library - mapping(uint256 => ProposalTemplate) proposalTemplates; // proposal type -> ProposalTemplate + mapping(uint256 => ProposalTemplate) proposalTemplates; // proposal id => ProposalTemplate // exists returns true if proposal template is present - function exists(uint256 pType) public view returns (bool) { - return bytes(proposalTemplates[pType].name).length != 0; + function exists(uint256 id) public view returns (bool) { + return bytes(proposalTemplates[id].name).length != 0; } // get returns proposal template - function get(uint256 pType) public view returns (string memory name, address verifier, Proposal.ExecType executable, uint256 minVotes, uint256 minAgreement, uint256[] memory opinionScales, uint256 minVotingDuration, uint256 maxVotingDuration, uint256 minStartDelay, uint256 maxStartDelay) { - ProposalTemplate storage t = proposalTemplates[pType]; + function get(uint256 id) public view returns (string memory name, address verifier, Proposal.ExecType executable, uint256 minVotes, uint256 minAgreement, uint256[] memory opinionScales, uint256 minVotingDuration, uint256 maxVotingDuration, uint256 minStartDelay, uint256 maxStartDelay) { + ProposalTemplate storage t = proposalTemplates[id]; return (t.name, t.verifier, t.executable, t.minVotes, t.minAgreement, t.opinionScales, t.minVotingDuration, t.maxVotingDuration, t.minStartDelay, t.maxStartDelay); } - // addTemplate adds a template into the library - // template must have unique type - function addTemplate(uint256 pType, string calldata name, address verifier, Proposal.ExecType executable, uint256 minVotes, uint256 minAgreement, uint256[] calldata opinionScales, uint256 minVotingDuration, uint256 maxVotingDuration, uint256 minStartDelay, uint256 maxStartDelay) external onlyOwner { - ProposalTemplate storage template = proposalTemplates[pType]; + /// @dev adds a new template to the library - Only the owner can add a template + /// @param id The ID of the template - must not already exist + /// @param name The name of the template + /// @param verifier The address of the verifier contract + /// @param executable The type of execution + /// @param minVotes The minimum number of votes required + /// @param minAgreement The minimum agreement required + /// @param opinionScales The opinion scales + /// @param minVotingDuration The minimum voting duration + /// @param maxVotingDuration The maximum voting duration + /// @param minStartDelay The minimum start delay + /// @param maxStartDelay The maximum start delay + function addTemplate( + uint256 id, + string calldata name, + address verifier, + Proposal.ExecType executable, + uint256 minVotes, + uint256 minAgreement, + uint256[] calldata opinionScales, + uint256 minVotingDuration, + uint256 maxVotingDuration, + uint256 minStartDelay, + uint256 maxStartDelay + ) external onlyOwner { + ProposalTemplate storage template = proposalTemplates[id]; // empty name is a marker of non-existing template require(bytes(name).length != 0, "empty name"); - require(!exists(pType), "template already exists"); + require(!exists(id), "template already exists"); require(opinionScales.length != 0, "empty opinions"); require(checkNonDecreasing(opinionScales), "wrong order of opinions"); require(opinionScales[opinionScales.length - 1] != 0, "all opinions are zero"); @@ -71,40 +95,43 @@ contract ProposalTemplates is Initializable, IProposalVerifier, Ownable, Version template.minStartDelay = minStartDelay; template.maxStartDelay = maxStartDelay; - emit AddedTemplate(pType); + emit AddedTemplate(id); } - // eraseTemplate removes the template of specified type from the library - function eraseTemplate(uint256 pType) external onlyOwner { - require(exists(pType), "template doesn't exist"); - delete (proposalTemplates[pType]); + /// @dev erases a template from the library - Only the owner can erase a template + /// @param id The ID of the template + function eraseTemplate(uint256 id) external onlyOwner { + require(exists(id), "template doesn't exist"); + delete (proposalTemplates[id]); - emit ErasedTemplate(pType); + emit ErasedTemplate(id); } - // verifyProposalParams checks proposal parameters - function verifyProposalParams(uint256 pType, Proposal.ExecType executable, uint256 minVotes, uint256 minAgreement, uint256[] calldata opinionScales, uint256 start, uint256 minEnd, uint256 maxEnd) external view returns (bool) { - if (start < block.timestamp) { - // start in the past - return false; - } - if (minEnd > maxEnd) { - // inconsistent data - return false; - } - if (start > minEnd) { - // inconsistent data - return false; - } - uint256 minDuration = minEnd - start; - uint256 maxDuration = maxEnd - start; - uint256 startDelay_ = start - block.timestamp; - - if (!exists(pType)) { + /// @dev Verify proposal parameters - Each proposal type has a template to which the data in proposal must correspond + /// @param id The ID of the template + /// @param executable The type of execution + /// @param minVotes The minimum number of votes required + /// @param minAgreement The minimum agreement required + /// @param opinionScales The opinion scales + /// @param start The start time + /// @param minEnd The minimum end time + /// @param maxEnd The maximum end time + /// @return true if the proposal parameters are valid + function verifyProposalParams( + uint256 id, + Proposal.ExecType executable, + uint256 minVotes, + uint256 minAgreement, + uint256[] calldata opinionScales, + uint256 start, + uint256 minEnd, + uint256 maxEnd + ) external view returns (bool) { + if (!exists(id)) { // non-existing template return false; } - ProposalTemplate memory template = proposalTemplates[pType]; + ProposalTemplate memory template = proposalTemplates[id]; if (executable != template.executable) { // inconsistent executable flag return false; @@ -135,6 +162,22 @@ contract ProposalTemplates is Initializable, IProposalVerifier, Ownable, Version return false; } } + if (start < block.timestamp) { + // start in the past + return false; + } + if (start > minEnd) { + // inconsistent data + return false; + } + if (minEnd > maxEnd) { + // inconsistent data + return false; + } + + uint256 minDuration = minEnd - start; + uint256 maxDuration = maxEnd - start; + uint256 startDelay_ = start - block.timestamp; if (minDuration < template.minVotingDuration) { // min. voting duration is too short return false; @@ -155,24 +198,31 @@ contract ProposalTemplates is Initializable, IProposalVerifier, Ownable, Version // template with no additional verifier return true; } - return IProposalVerifier(template.verifier).verifyProposalParams(pType, executable, minVotes, minAgreement, opinionScales, start, minEnd, maxEnd); + return IProposalVerifier(template.verifier).verifyProposalParams(id, executable, minVotes, minAgreement, opinionScales, start, minEnd, maxEnd); } // verifyProposalContract verifies proposal using the additional verifier - function verifyProposalContract(uint256 pType, address propAddr) external view returns (bool) { - if (!exists(pType)) { + + /// @dev Verify proposal contract - Each proposal type has a template to which the data in proposal must correspond + /// @param id The ID of the template + /// @param propAddr The address of the proposal contract + /// @return true if the proposal contract is valid + function verifyProposalContract(uint256 id, address propAddr) external view returns (bool) { + if (!exists(id)) { // non-existing template return false; } - ProposalTemplate memory template = proposalTemplates[pType]; + ProposalTemplate memory template = proposalTemplates[id]; if (template.verifier == address(0)) { // template with no additional verifier return true; } - return IProposalVerifier(template.verifier).verifyProposalContract(pType, propAddr); + return IProposalVerifier(template.verifier).verifyProposalContract(id, propAddr); } - // checkNonDecreasing returns true if array values are monotonically nondecreasing + /// @dev Check if array values are monotonically non-decreasing + /// @param arr The array to check + /// @return true if the array is monotonically non-decreasing function checkNonDecreasing(uint256[] memory arr) internal pure returns (bool) { for (uint256 i = 1; i < arr.length; i++) { if (arr[i - 1] > arr[i]) { diff --git a/contracts/version/Version.sol b/contracts/version/Version.sol index 5312874..5019055 100644 --- a/contracts/version/Version.sol +++ b/contracts/version/Version.sol @@ -1,12 +1,8 @@ pragma solidity ^0.5.0; -/** - * @dev The version info of this contract - */ +/// @dev The version info of this contract contract Version { - /** - * @dev Returns the version of this contract. - */ + // @dev Returns the version of this contract. function version() public pure returns (bytes4) { // version 00.0.2 return "0002"; diff --git a/contracts/votebook/votebook.sol b/contracts/votebook/votebook.sol index 80cf680..c0d4efc 100644 --- a/contracts/votebook/votebook.sol +++ b/contracts/votebook/votebook.sol @@ -2,23 +2,28 @@ pragma solidity ^0.5.0; import "../ownership/Ownable.sol"; +/// @dev Interface for the governance contract interface GovernanceI { function recountVote(address voterAddr, address delegatedTo, uint256 proposalID) external; function proposalState(uint256 proposalID) external view returns (uint256 winnerOptionID, uint256 votes, uint256 status); } +/// @dev A contract that keeps track of votes contract VotesBookKeeper is Initializable, Ownable { - address gov; + address gov; // Address of the governance contract - uint256 public maxProposalsPerVoter; + uint256 public maxProposalsPerVoter; // Maximum number of proposals a voter can vote on - // voter, delegatedTo -> []proposal IDs - mapping(address => mapping(address => uint256[])) votesList; + mapping(address => mapping(address => uint256[])) votesList; // voter => delegatedTo => []proposal IDs - // voter, delegatedTo, proposal ID -> {index in the list + 1} + // voter => delegatedTo => proposal ID => {index in the list + 1} mapping(address => mapping(address => mapping(uint256 => uint256))) votesIndex; + /// @dev Initialize the contract + /// @param _owner The owner of the contract + /// @param _gov The address of the governance contract + /// @param _maxProposalsPerVoter The maximum number of proposals a voter can vote on function initialize(address _owner, address _gov, uint256 _maxProposalsPerVoter) public initializer { Ownable.initialize(_owner); gov = _gov; @@ -29,11 +34,18 @@ contract VotesBookKeeper is Initializable, Ownable { return votesList[voter][delegatedTo]; } - // returns vote index plus 1 if vote exists. Returns 0 if vote doesn't exist + /// @dev Get vote index plus 1 if vote exists. Returns 0 if vote doesn't exist + /// @param voter The address of the voter + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal + /// @return The index of the vote plus 1 if the vote exists, otherwise 0 function getVoteIndex(address voter, address delegatedTo, uint256 proposalID) public view returns (uint256) { return votesIndex[voter][delegatedTo][proposalID]; } + /// @dev Recount votes for a voter + /// @param voter The address of the voter + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. function recountVotes(address voter, address delegatedTo) public { uint256[] storage list = votesList[voter][delegatedTo]; uint256 origLen = list.length; @@ -55,6 +67,11 @@ contract VotesBookKeeper is Initializable, Ownable { } } + /// @dev Add a vote to the list of votes + /// Should be called when a new vote is created + /// @param voter The address of the voter + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal function onVoted(address voter, address delegatedTo, uint256 proposalID) external { uint256 idx = votesIndex[voter][delegatedTo][proposalID]; if (idx > 0) { @@ -70,6 +87,11 @@ contract VotesBookKeeper is Initializable, Ownable { votesIndex[voter][delegatedTo][proposalID] = idx; } + /// @dev Remove a vote from the list of votes + /// Should be called when a vote is canceled + /// @param voter The address of the voter + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal function onVoteCanceled(address voter, address delegatedTo, uint256 proposalID) external { uint256 idx = votesIndex[voter][delegatedTo][proposalID]; if (idx == 0) { @@ -78,6 +100,10 @@ contract VotesBookKeeper is Initializable, Ownable { eraseVote(voter, delegatedTo, proposalID, idx); } + /// @dev Remove a vote from the list of votes + /// @param voter The address of the voter + /// @param delegatedTo The address of the delegator which the sender has delegated their stake to. + /// @param proposalID The ID of the proposal function eraseVote(address voter, address delegatedTo, uint256 proposalID, uint256 idx) internal { votesIndex[voter][delegatedTo][proposalID] = 0; uint256[] storage list = votesList[voter][delegatedTo]; @@ -93,6 +119,8 @@ contract VotesBookKeeper is Initializable, Ownable { } } + /// @dev Set the maximum number of proposals a voter can vote on + /// @param v The new maximum number of proposals function setMaxProposalsPerVoter(uint256 v) onlyOwner external { maxProposalsPerVoter = v; }