pragma solidity 0.4.26;
/// @title uniquely identifies deployable (non-abstract) platform contract
/// @notice cheap way of assigning implementations to knownInterfaces which represent system services
/// unfortunatelly ERC165 does not include full public interface (ABI) and does not provide way to list implemented interfaces
/// EIP820 still in the making
/// @dev ids are generated as follows keccak256("neufund-platform:<contract name>")
/// ids roughly correspond to ABIs
contract IContractId {
/// @param id defined as above
/// @param version implementation version
function contractId() public pure returns (bytes32 id, uint256 version);
}
/// @title access to snapshots of a token
/// @notice allows to implement complex token holder rights like revenue disbursal or voting
/// @notice snapshots are series of values with assigned ids. ids increase strictly. particular id mechanism is not assumed
contract ITokenSnapshots {
////////////////////////
// Public functions
////////////////////////
/// @notice Total amount of tokens at a specific `snapshotId`.
/// @param snapshotId of snapshot at which totalSupply is queried
/// @return The total amount of tokens at `snapshotId`
/// @dev reverts on snapshotIds greater than currentSnapshotId()
/// @dev returns 0 for snapshotIds less than snapshotId of first value
function totalSupplyAt(uint256 snapshotId)
public
constant
returns(uint256);
/// @dev Queries the balance of `owner` at a specific `snapshotId`
/// @param owner The address from which the balance will be retrieved
/// @param snapshotId of snapshot at which the balance is queried
/// @return The balance at `snapshotId`
function balanceOfAt(address owner, uint256 snapshotId)
public
constant
returns (uint256);
/// @notice upper bound of series of snapshotIds for which there's a value in series
/// @return snapshotId
function currentSnapshotId()
public
constant
returns (uint256);
}
// standard methods of VotingCenter contract that governs voting procedures on the whole platform
contract IVotingCenter is IContractId {
/// @dev Creates a proposal, uniquely identifiable by its assigned proposalId
/// @param proposalId unique identifier of the proposal, e.g. ipfs-hash of info
/// @param token a token where balances give voting power to holders
/// @param campaignDuration duration (s) in which proposal has to gather enough votes to be made public (see campaignQuorum)
/// @param campaignQuorumFraction fraction (10**18 = 1) of token holders who have to support a proposal in order for it to be trigger an event
/// @param votingPeriod total duration (s) in which the proposal can be voted on by tokenholders after it was created
/// @param votingLegalRep a legal representative for the vote, which may provide off-chain voting results
/// @param offchainVotePeriod duration (s) after voting is ended when voting legal rep may provide results
/// @param totalVotingPower combined voting power of on-chain (token total supply) and held off-chain (ie. shares) expressed in tokens
/// @param action initiator defined action code on which voting happens
/// @param actionPayload initiator defined action payload on which voting happens
function addProposal(
bytes32 proposalId,
ITokenSnapshots token,
uint32 campaignDuration,
uint256 campaignQuorumFraction,
uint32 votingPeriod,
address votingLegalRep,
uint32 offchainVotePeriod,
uint256 totalVotingPower,
uint256 action,
bytes actionPayload,
bool enableObserver
)
public;
/// @dev increase the voting power on a given proposal by the token balance of the sender
/// throws if proposal does not exist or the vote on it has ended already. Votes are final,
/// changing the vote is not allowed
/// @dev reverts if proposal does not exist
/// @param proposalId of the proposal to be voted on
/// @param voteInFavor if true, voting power goes for proposal, if false - against
function vote(bytes32 proposalId, bool voteInFavor)
public;
/// @notice add off-chain votes, inFavor + against may not cross the offchainVotingPower, but may be less
/// to reflect abstaining from vote
/// @param inFavor voting power (expressed in tokens) being in favor of the proposal
/// @param against voting power (expressed in tokens) being against the proposal
/// @param documentUri official document with final voting results
function addOffchainVote(bytes32 proposalId, uint256 inFavor, uint256 against, string documentUri)
public;
/// @notice Returns the current tally of a proposal. Only Final proposal have immutable tally
/// @return the voting power on a finished proposal and the total voting power
/// @dev please again note that VotingCenter does not say if voting passed in favor or against. it just carries on
/// the voting and it's up to initiator to say what is the outcome, see IProposalObserver
function tally(bytes32 proposalId)
public
constant
returns(
uint8 s,
uint256 inFavor,
uint256 against,
uint256 offchainInFavor,
uint256 offchainAgainst,
uint256 tokenVotingPower,
uint256 totalVotingPower,
uint256 campaignQuorumTokenAmount,
address initiator,
bool hasObserverInterface
);
/// @notice returns official document with off-chain vote result/statement
/// @dev meaningful only in final state
function offchainVoteDocumentUri(bytes32 proposalId)
public
constant
returns (string);
/// @notice obtains proposal after internal state is updated due to time
/// @dev this is the getter you should use
/// @dev reverts if proposal does not exist
function timedProposal(bytes32 proposalId)
public
constant
returns (
uint8 s,
address token,
uint256 snapshotId,
address initiator,
address votingLegalRep,
uint256 campaignQuorumTokenAmount,
uint256 offchainVotingPower,
uint256 action,
bytes actionPayload,
bool enableObserver,
uint32[5] deadlines
);
/// @notice obtains proposal state without time transitions
/// @dev used mostly to detect propositions requiring timed transitions
/// @dev reverts if proposal does not exist
function proposal(bytes32 proposalId)
public
constant
returns (
uint8 s,
address token,
uint256 snapshotId,
address initiator,
address votingLegalRep,
uint256 campaignQuorumTokenAmount,
uint256 offchainVotingPower,
uint256 action,
bytes actionPayload,
bool enableObserver,
uint32[5] deadlines
);
/// @notice tells if voter cast a for/against vote or abstained
/// @dev reverts if proposal does not exist
/// @return see TriState in VotingProposal
function getVote(bytes32 proposalId, address voter)
public
constant
returns (uint8);
/// @notice tells if proposal with given id was opened
function hasProposal(bytes32 proposalId)
public
constant
returns (bool);
/// @notice returns voting power for given proposal / voter
/// @dev voters with zero voting power cannot participate in voting
function getVotingPower(bytes32 proposalId, address voter)
public
constant
returns (uint256);
}
contract IVotingController is IContractId {
// token must be NEU or equity token
// if initiator is not equity token then proposals start in campaign mode
// if token is NEU then default values must apply
function onAddProposal(bytes32 proposalId, address initiator, address token)
public
constant
returns (bool);
/// @notice check wether the disbursal controller may be changed
function onChangeVotingController(address sender, IVotingController newController)
public
constant
returns (bool);
}
library Math {
////////////////////////
// Internal functions
////////////////////////
// absolute difference: |v1 - v2|
function absDiff(uint256 v1, uint256 v2)
internal
pure
returns(uint256)
{
return v1 > v2 ? v1 - v2 : v2 - v1;
}
// divide v by d, round up if remainder is 0.5 or more
function divRound(uint256 v, uint256 d)
internal
pure
returns(uint256)
{
return add(v, d/2) / d;
}
// computes decimal decimalFraction 'frac' of 'amount' with maximum precision (multiplication first)
// both amount and decimalFraction must have 18 decimals precision, frac 10**18 represents a whole (100% of) amount
// mind loss of precision as decimal fractions do not have finite binary expansion
// do not use instead of division
function decimalFraction(uint256 amount, uint256 frac)
internal
pure
returns(uint256)
{
// it's like 1 ether is 100% proportion
return proportion(amount, frac, 10**18);
}
// computes part/total of amount with maximum precision (multiplication first)
// part and total must have the same units
function proportion(uint256 amount, uint256 part, uint256 total)
internal
pure
returns(uint256)
{
return divRound(mul(amount, part), total);
}
//
// Open Zeppelin Math library below
//
function mul(uint256 a, uint256 b)
internal
pure
returns (uint256)
{
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function sub(uint256 a, uint256 b)
internal
pure
returns (uint256)
{
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b)
internal
pure
returns (uint256)
{
uint256 c = a + b;
assert(c >= a);
return c;
}
function min(uint256 a, uint256 b)
internal
pure
returns (uint256)
{
return a < b ? a : b;
}
function max(uint256 a, uint256 b)
internal
pure
returns (uint256)
{
return a > b ? a : b;
}
}
/// @notice should be implemented by all contracts that initate the voting center procedure
/// must be implemented by all contracts that initate voting procedure AND request observer callbacks
contract IVotingObserver {
/// @notice if requested, voting center will pass state transitions of proposal to observer
/// @dev refer to VotingProposal for state variable values
function onProposalStateTransition(
bytes32 proposalId,
uint8 oldState,
uint8 newState)
public;
/// @notice only observer may tell if vote was in favor or not, voting center only carries on voting procedure
/// example is equity token controller as observer which will count outcome as passed depending on company bylaws
/// @param votingCenter at which voting center to look for the results
/// @param proposalId for which proposalId to deliver results
/// @return true means inFavor, false means agains, revert means that procedure is not yet final or any other problem
/// @dev please note the revert/false distinction above, do not returns false in case voting is unknown or not yet final
function votingResult(address votingCenter, bytes32 proposalId)
public
constant
returns (bool inFavor);
}
library VotingProposal {
////////////////////////
// Constants
////////////////////////
uint256 private constant STATES_COUNT = 5;
////////////////////////
// Types
////////////////////////
enum State {
// Initial state where voting owner can build quorum for public visibility
Campaigning,
// has passed campaign-quorum in time, voting publicly announced
Public,
// reveal state where meta-transactions are gathered
Reveal,
// For votings that have off-chain counterpart, this is the time to upload the tally
Tally,
// Vote count will not change and tally is available, terminal state
Final
}
// Łukasiewicz logic values for state of a vote of particular voter
/// @dev note that Abstain is meaningful only in Final/Tally state
enum TriState {
Abstain,
InFavor,
Against
}
/// @dev note that voting power is always expressed in tokens of the associated snapshot token
/// and reflect decimals of the token. voting power of 1 token with 18 decimals is Q18
struct Proposal {
// voting power comes from here
ITokenSnapshots token;
// balances at this snapshot count
uint256 snapshotId;
// on-chain tally
uint256 inFavor;
uint256 against;
// off-chain tally
uint256 offchainInFavor;
uint256 offchainAgainst;
// quorum needed to reach public phase
uint256 campaignQuorumTokenAmount;
// off-chain voting power
uint256 offchainVotingPower;
// voting initiator
IVotingObserver initiator;
// voting legal representative
address votingLegalRep;
// proposal action as set by initiator
uint256 action;
// on chain proposal action payload
bytes actionPayload;
// off-chain official results
string offchainVoteDocumentUri;
// when states end, indexed by state, keep it word aligned
uint32[STATES_COUNT] deadlines;
// current state of the voting
State state;
// observer function requested to owner?
bool observing;
// you can vote only once
mapping (address => TriState) hasVoted;
}
/////////////////////////
// Events
////////////////////////
event LogProposalStateTransition(
bytes32 indexed proposalId,
address initiator,
address votingLegalRep,
address token,
State oldState,
State newState
);
/////////////////////////
// Internal Lib Functions
////////////////////////
function isVotingOpen(VotingProposal.Proposal storage p)
internal
constant
returns (bool)
{
return p.state == State.Campaigning || p.state == State.Public;
}
function isRelayOpen(VotingProposal.Proposal storage p)
internal
constant
returns (bool)
{
return isVotingOpen(p) || p.state == State.Reveal;
}
function initialize(
Proposal storage p,
bytes32 proposalId,
ITokenSnapshots token,
uint256 snapshotId,
uint32 campaignDuration,
uint256 campaignQuorumFraction,
uint32 votingPeriod,
address votingLegalRep,
uint32 offchainVotePeriod,
uint256 totalVotingPower,
uint256 action,
bool enableObserver
)
internal
{
uint256 totalTokenVotes = token.totalSupplyAt(snapshotId);
require(totalTokenVotes > 0, "NF_VC_EMPTY_TOKEN");
require(totalVotingPower == 0 || totalVotingPower >= totalTokenVotes, "NF_VC_TOTPOWER_LT_TOKEN");
// set initial deadlines
uint32[STATES_COUNT] memory deadlines;
uint32 t = uint32(now);
deadlines[0] = t;
deadlines[1] = t + campaignDuration;
// no reveal now
deadlines[2] = deadlines[3] = t + votingPeriod;
// offchain voting deadline
// if all voting power belongs to token holders then off-chain tally must be skipped
deadlines[4] = deadlines[3] + (totalVotingPower == totalTokenVotes ? 0 : offchainVotePeriod);
// can't use struct constructor because it goes through memory
// p is already allocated storage slot
p.token = token;
p.snapshotId = snapshotId;
p.observing = enableObserver;
p.votingLegalRep = votingLegalRep;
p.offchainVotingPower = totalVotingPower > 0 ? Math.sub(totalVotingPower, totalTokenVotes) : 0;
// campaign must cross total voting power
p.campaignQuorumTokenAmount = Math.decimalFraction(totalTokenVotes + p.offchainVotingPower, campaignQuorumFraction);
require(p.campaignQuorumTokenAmount <= totalTokenVotes, "NF_VC_NO_CAMP_VOTING_POWER");
p.initiator = IVotingObserver(msg.sender);
p.deadlines = deadlines;
p.state = State.Campaigning;
p.action = action;
// advance campaigning state to public if quorum not specified
// that will also emit event if such transition happen
advanceLogicState(p, proposalId);
}
// @dev don't use `else if` and keep sorted by time and call `state()`
// or else multiple transitions won't cascade properly.
function advanceTimedState(Proposal storage p, bytes32 proposalId)
internal
{
uint32 t = uint32(now);
// campaign timeout to final
if (p.state == State.Campaigning && t >= p.deadlines[uint32(State.Public)]) {
transitionTo(p, proposalId, State.Final);
}
// other states go one by one, terminal state stops
while(p.state != State.Final && t >= p.deadlines[uint32(p.state) + 1]) {
transitionTo(p, proposalId, State(uint8(p.state) + 1));
}
}
// @notice transitions due to business logic
// @dev called after logic
function advanceLogicState(Proposal storage p, bytes32 proposalId)
internal
{
// State state = p.state;
// if crossed campaign quorum
if (p.state == State.Campaigning && p.inFavor + p.against >= p.campaignQuorumTokenAmount) {
// go to public state
transitionTo(p, proposalId, State.Public);
}
// if off-chain tally done
if (p.state == State.Tally && p.offchainAgainst + p.offchainInFavor > 0) {
// finalize
transitionTo(p, proposalId, State.Final);
}
}
/// @notice executes transition state function
function transitionTo(Proposal storage p, bytes32 proposalId, State newState)
private
{
State oldState = p.state;
// get deadline for old state and check the delta for other states
uint32 delta;
uint32 deadline = p.deadlines[uint256(oldState) + 1];
// if transition came before deadline, count time from timestamp, if after always count from deadline
if (uint32(now) < deadline) {
delta = deadline - uint32(now);
}
if (delta > 0) {
// shift dealines for other states
uint32[STATES_COUNT] memory newDeadlines = p.deadlines;
for (uint256 ii = uint256(oldState) + 1; ii < STATES_COUNT; ii += 1) {
newDeadlines[ii] -= delta;
}
p.deadlines = newDeadlines;
}
// write storage
p.state = newState;
// do not emit events and observer if campaigning failed
if (oldState == State.Campaigning && newState == State.Final) {
return;
}
emit LogProposalStateTransition(proposalId, p.initiator, p.votingLegalRep, p.token, oldState, newState);
if (p.observing) {
// call observer on best-effort. ignore errors
bytes4 sel = p.initiator.onProposalStateTransition.selector;
(address(p.initiator)).call(
abi.encodeWithSelector(sel, proposalId, oldState, newState)
);
}
}
}
/// Contract to allow voting based on a snapshotable token (with relayed, batched voting)
contract VotingCenter is IVotingCenter {
using VotingProposal for VotingProposal.Proposal;
/////////////////////////
// Modifiers
////////////////////////
// @dev This modifier needs to be applied to all external non-constant functions.
// this modifier goes _before_ other state modifiers like `onlyState`.
// after function body execution state may transition again in `advanceLogicState`
modifier withStateTransition(bytes32 proposalId) {
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
// switch state due to time
VotingProposal.advanceTimedState(p, proposalId);
// execute function body
_;
// switch state due to business logic
VotingProposal.advanceLogicState(p, proposalId);
}
// @dev This modifier needs to be applied to all external non-constant functions.
// this modifier goes _before_ other state modifiers like `onlyState`.
// note that this function actually modifies state so it will generate warnings
// and is incompatible with STATICCALL
modifier withTimedTransition(bytes32 proposalId) {
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
// switch state due to time
VotingProposal.advanceTimedState(p, proposalId);
// execute function body
_;
}
modifier withVotingOpen(bytes32 proposalId) {
VotingProposal.Proposal storage p = _proposals[proposalId];
require(VotingProposal.isVotingOpen(p), "NV_VC_VOTING_CLOSED");
_;
}
modifier withRelayingOpen(bytes32 proposalId) {
VotingProposal.Proposal storage p = _proposals[proposalId];
require(VotingProposal.isRelayOpen(p), "NV_VC_VOTING_CLOSED");
_;
}
modifier onlyTally(bytes32 proposalId) {
VotingProposal.Proposal storage p = _proposals[proposalId];
require(p.state == VotingProposal.State.Tally, "NV_VC_NOT_TALLYING");
_;
}
/////////////////////////
// Mutable state
////////////////////////
mapping (bytes32 => VotingProposal.Proposal) private _proposals;
IVotingController private _votingController;
/////////////////////////
// Events
////////////////////////
// must be in sync with library event, events cannot be shared
event LogProposalStateTransition(
bytes32 indexed proposalId,
address initiator,
address votingLegalRep,
address token,
VotingProposal.State oldState,
VotingProposal.State newState
);
// logged when voter casts a vote
event LogVoteCast(
bytes32 indexed proposalId,
address initiator,
address token,
address voter,
bool voteInFavor,
uint256 power
);
// logged when proposal legal rep provides off-chain voting results
event LogOffChainProposalResult(
bytes32 indexed proposalId,
address initiator,
address token,
address votingLegalRep,
uint256 inFavor,
uint256 against,
string documentUri
);
// logged when controller changed
event LogChangeVotingController(
address oldController,
address newController,
address by
);
////////////////////////
// Constructor
////////////////////////
constructor(IVotingController controller) public {
_votingController = controller;
}
/////////////////////////
// Public functions
////////////////////////
//
// IVotingCenter implementation
//
function addProposal(
bytes32 proposalId,
ITokenSnapshots token,
uint32 campaignDuration,
uint256 campaignQuorumFraction,
uint32 votingPeriod,
address votingLegalRep,
uint32 offchainVotePeriod,
uint256 totalVotingPower,
uint256 action,
bytes actionPayload,
bool enableObserver
)
public
{
require(token != address(0));
VotingProposal.Proposal storage p = _proposals[proposalId];
require(p.token == address(0), "NF_VC_P_ID_NON_UNIQ");
// campaign duration must be less or eq total voting period
require(campaignDuration <= votingPeriod, "NF_VC_CAMPAIGN_OVR_TOTAL");
require(campaignQuorumFraction <= 10**18, "NF_VC_INVALID_CAMPAIGN_Q");
require(
campaignQuorumFraction == 0 && campaignDuration == 0 ||
campaignQuorumFraction > 0 && campaignDuration > 0,
"NF_VC_CAMP_INCONSISTENT"
);
require(
offchainVotePeriod > 0 && totalVotingPower > 0 && votingLegalRep != address(0) ||
offchainVotePeriod == 0 && totalVotingPower == 0 && votingLegalRep == address(0),
"NF_VC_TALLY_INCONSISTENT"
);
// take sealed snapshot
uint256 sId = token.currentSnapshotId() - 1;
p.initialize(
proposalId,
token,
sId,
campaignDuration,
campaignQuorumFraction,
votingPeriod,
votingLegalRep,
offchainVotePeriod,
totalVotingPower,
action,
enableObserver
);
// we should do it in initialize bo stack is too small
p.actionPayload = actionPayload;
// call controller now when proposal is available via proposal method
require(_votingController.onAddProposal(proposalId, msg.sender, token), "NF_VC_CTR_ADD_REJECTED");
}
function vote(bytes32 proposalId, bool voteInFavor)
public
withStateTransition(proposalId)
withVotingOpen(proposalId)
{
VotingProposal.Proposal storage p = _proposals[proposalId];
require(p.hasVoted[msg.sender] == VotingProposal.TriState.Abstain, "NF_VC_ALREADY_VOTED");
castVote(p, proposalId, voteInFavor, msg.sender);
}
function addOffchainVote(bytes32 proposalId, uint256 inFavor, uint256 against, string documentUri)
public
withStateTransition(proposalId)
onlyTally(proposalId)
{
VotingProposal.Proposal storage p = _proposals[proposalId];
require(msg.sender == p.votingLegalRep, "NF_VC_ONLY_VOTING_LEGAL_REP");
// may not cross offchainVotingPower
require(inFavor + against <= p.offchainVotingPower, "NF_VC_EXCEEDS_OFFLINE_V_POWER");
require(inFavor + against > 0, "NF_VC_NO_OFF_EMPTY_VOTE");
p.offchainInFavor = inFavor;
p.offchainAgainst = against;
p.offchainVoteDocumentUri = documentUri;
emit LogOffChainProposalResult(proposalId, p.initiator, p.token, msg.sender, inFavor, against, documentUri);
}
function tally(bytes32 proposalId)
public
constant
withTimedTransition(proposalId)
returns(
uint8 s,
uint256 inFavor,
uint256 against,
uint256 offchainInFavor,
uint256 offchainAgainst,
uint256 tokenVotingPower,
uint256 totalVotingPower,
uint256 campaignQuorumTokenAmount,
address initiator,
bool hasObserverInterface
)
{
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
s = uint8(p.state);
inFavor = p.inFavor;
against = p.against;
offchainInFavor = p.offchainInFavor;
offchainAgainst = p.offchainAgainst;
initiator = p.initiator;
hasObserverInterface = p.observing;
tokenVotingPower = p.token.totalSupplyAt(p.snapshotId);
totalVotingPower = tokenVotingPower + p.offchainVotingPower;
campaignQuorumTokenAmount = p.campaignQuorumTokenAmount;
}
function offchainVoteDocumentUri(bytes32 proposalId)
public
constant
returns (string)
{
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
return p.offchainVoteDocumentUri;
}
function timedProposal(bytes32 proposalId)
public
withTimedTransition(proposalId)
constant
returns (
uint8 s,
address token,
uint256 snapshotId,
address initiator,
address votingLegalRep,
uint256 campaignQuorumTokenAmount,
uint256 offchainVotingPower,
uint256 action,
bytes actionPayload,
bool enableObserver,
uint32[5] deadlines
)
{
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
s = uint8(p.state);
token = p.token;
snapshotId = p.snapshotId;
enableObserver = p.observing;
campaignQuorumTokenAmount = p.campaignQuorumTokenAmount;
initiator = p.initiator;
votingLegalRep = p.votingLegalRep;
offchainVotingPower = p.offchainVotingPower;
deadlines = p.deadlines;
action = p.action;
actionPayload = p.actionPayload;
}
function proposal(bytes32 proposalId)
public
constant
returns (
uint8 s,
address token,
uint256 snapshotId,
address initiator,
address votingLegalRep,
uint256 campaignQuorumTokenAmount,
uint256 offchainVotingPower,
uint256 action,
bytes actionPayload,
bool enableObserver,
uint32[5] deadlines
)
{
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
s = uint8(p.state);
token = p.token;
snapshotId = p.snapshotId;
enableObserver = p.observing;
campaignQuorumTokenAmount = p.campaignQuorumTokenAmount;
initiator = p.initiator;
votingLegalRep = p.votingLegalRep;
offchainVotingPower = p.offchainVotingPower;
deadlines = p.deadlines;
action = p.action;
actionPayload = p.actionPayload;
}
function getVote(bytes32 proposalId, address voter)
public
constant
returns (uint8)
{
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
return uint8(p.hasVoted[voter]);
}
function hasProposal(bytes32 proposalId)
public
constant
returns (bool)
{
VotingProposal.Proposal storage p = _proposals[proposalId];
return p.token != address(0);
}
function getVotingPower(bytes32 proposalId, address voter)
public
constant
returns (uint256)
{
VotingProposal.Proposal storage p = ensureExistingProposal(proposalId);
return p.token.balanceOfAt(voter, p.snapshotId);
}
//
// IContractId Implementation
//
function contractId()
public
pure
returns (bytes32 id, uint256 version)
{
return (0xbbf540c4111754f6dbce914d5e55e1c0cb26515adbc288b5ea8baa544adfbfa4, 0);
}
//
// IVotingController hook
//
/// @notice get current controller
function votingController()
public
constant
returns (IVotingController)
{
return _votingController;
}
/// @notice update current controller
function changeVotingController(IVotingController newController)
public
{
require(_votingController.onChangeVotingController(msg.sender, newController), "NF_VC_CHANGING_CTR_REJECTED");
address oldController = address(_votingController);
_votingController = newController;
emit LogChangeVotingController(oldController, address(newController), msg.sender);
}
//
// Other methods
//
/// @dev same as vote, only for a relayed vote. Will throw if provided signature (v,r,s) does not match
/// the address of the voter
/// @param voter address whose token balance should be used as voting power
function relayedVote(
bytes32 proposalId,
bool voteInFavor,
address voter,
bytes32 r,
bytes32 s,
uint8 v
)
public
withStateTransition(proposalId)
withRelayingOpen(proposalId)
{
// check that message signature matches the voter address
assert(isValidSignature(proposalId, voteInFavor, voter, r, s, v));
// solium-enable indentation
VotingProposal.Proposal storage p = _proposals[proposalId];
require(p.hasVoted[voter] == VotingProposal.TriState.Abstain, "NF_VC_ALREADY_VOTED");
castVote(p, proposalId, voteInFavor, voter);
}
// batches should be grouped by proposal, that allows to tally in place and write to storage once
function batchRelayedVotes(
bytes32 proposalId,
bool[] votePreferences,
bytes32[] r,
bytes32[] s,
uint8[] v
)
public
withStateTransition(proposalId)
withRelayingOpen(proposalId)
{
assert(
votePreferences.length == r.length && r.length == s.length && s.length == v.length
);
relayBatchInternal(
proposalId,
votePreferences,
r, s, v
);
}
function handleStateTransitions(bytes32 proposalId)
public
withTimedTransition(proposalId)
{}
//
// Utility public functions
//
function isValidSignature(
bytes32 proposalId,
bool voteInFavor,
address voter,
bytes32 r,
bytes32 s,
uint8 v
)
public
constant
returns (bool)
{
// solium-disable indentation
return ecrecoverVoterAddress(proposalId, voteInFavor, r, s, v) == voter;
}
function ecrecoverVoterAddress(
bytes32 proposalId,
bool voteInFavor,
bytes32 r,
bytes32 s,
uint8 v
)
public
constant
returns (address)
{
// solium-disable indentation
return ecrecover(
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
keccak256(abi.encodePacked(byte(0), address(this), proposalId, voteInFavor)))),
v, r, s);
}
/////////////////////////
// Internal functions
////////////////////////
function ensureExistingProposal(bytes32 proposalId)
internal
constant
returns (VotingProposal.Proposal storage p)
{
p = _proposals[proposalId];
require(p.token != address(0), "NF_VC_PROP_NOT_EXIST");
return p;
}
/////////////////////////
// Private functions
////////////////////////
/// @dev increase the votecount on a given proposal by the token balance of a given address,
/// throws if proposal does not exist or the vote on it has ended already. Votes are final,
/// changing the vote is not allowed
/// @param p proposal storage pointer
/// @param proposalId of the proposal to be voted on
/// @param voteInFavor of the desired proposal
/// @param voter address whose tokenBalance is to be used as voting-power
function castVote(VotingProposal.Proposal storage p, bytes32 proposalId, bool voteInFavor, address voter)
private
{
uint256 power = p.token.balanceOfAt(voter, p.snapshotId);
if (voteInFavor) {
p.inFavor = Math.add(p.inFavor, power);
} else {
p.against = Math.add(p.against, power);
}
markVoteCast(p, proposalId, voter, voteInFavor, power);
}
function relayBatchInternal(
bytes32 proposalId,
bool[] votePreferences,
bytes32[] r,
bytes32[] s,
uint8[] v
)
private
{
uint256 inFavor;
uint256 against;
VotingProposal.Proposal storage p = _proposals[proposalId];
for (uint256 i = 0; i < votePreferences.length; i++) {
uint256 power = relayBatchElement(
p,
proposalId,
votePreferences[i],
r[i], s[i], v[i]);
if (votePreferences[i]) {
inFavor = Math.add(inFavor, power);
} else {
against = Math.add(against, power);
}
}
// write votes to storage
p.inFavor = Math.add(p.inFavor, inFavor);
p.against = Math.add(p.against, against);
}
function relayBatchElement(
VotingProposal.Proposal storage p,
bytes32 proposalId,
bool voteInFavor,
bytes32 r,
bytes32 s,
uint8 v
)
private
returns (uint256 power)
{
// recover voter from signature, mangled data produces mangeld voter address, which will be
// eliminated later
address voter = ecrecoverVoterAddress(
proposalId,
voteInFavor,
r, s, v
);
// cast vote if not cast before
if (p.hasVoted[voter] == VotingProposal.TriState.Abstain) {
power = p.token.balanceOfAt(voter, p.snapshotId);
// if not holding token, power is 0
markVoteCast(p, proposalId, voter, voteInFavor, power);
}
// returns voting power which is zero in case of failed vote
}
function markVoteCast(VotingProposal.Proposal storage p, bytes32 proposalId, address voter, bool voteInFavor, uint256 power)
private
{
if (power > 0) {
p.hasVoted[voter] = voteInFavor ? VotingProposal.TriState.InFavor : VotingProposal.TriState.Against;
emit LogVoteCast(proposalId, p.initiator, p.token, voter, voteInFavor, power);
}
}
}
{
"compilationTarget": {
"VotingCenter.sol": "VotingCenter"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"proposalId","type":"bytes32"}],"name":"handleStateTransitions","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"voteInFavor","type":"bool"},{"name":"voter","type":"address"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"v","type":"uint8"}],"name":"relayedVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"voteInFavor","type":"bool"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"v","type":"uint8"}],"name":"ecrecoverVoterAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"token","type":"address"},{"name":"campaignDuration","type":"uint32"},{"name":"campaignQuorumFraction","type":"uint256"},{"name":"votingPeriod","type":"uint32"},{"name":"votingLegalRep","type":"address"},{"name":"offchainVotePeriod","type":"uint32"},{"name":"totalVotingPower","type":"uint256"},{"name":"action","type":"uint256"},{"name":"actionPayload","type":"bytes"},{"name":"enableObserver","type":"bool"}],"name":"addProposal","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"votingController","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"}],"name":"offchainVoteDocumentUri","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"voteInFavor","type":"bool"},{"name":"voter","type":"address"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"v","type":"uint8"}],"name":"isValidSignature","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"}],"name":"timedProposal","outputs":[{"name":"s","type":"uint8"},{"name":"token","type":"address"},{"name":"snapshotId","type":"uint256"},{"name":"initiator","type":"address"},{"name":"votingLegalRep","type":"address"},{"name":"campaignQuorumTokenAmount","type":"uint256"},{"name":"offchainVotingPower","type":"uint256"},{"name":"action","type":"uint256"},{"name":"actionPayload","type":"bytes"},{"name":"enableObserver","type":"bool"},{"name":"deadlines","type":"uint32[5]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"}],"name":"proposal","outputs":[{"name":"s","type":"uint8"},{"name":"token","type":"address"},{"name":"snapshotId","type":"uint256"},{"name":"initiator","type":"address"},{"name":"votingLegalRep","type":"address"},{"name":"campaignQuorumTokenAmount","type":"uint256"},{"name":"offchainVotingPower","type":"uint256"},{"name":"action","type":"uint256"},{"name":"actionPayload","type":"bytes"},{"name":"enableObserver","type":"bool"},{"name":"deadlines","type":"uint32[5]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"inFavor","type":"uint256"},{"name":"against","type":"uint256"},{"name":"documentUri","type":"string"}],"name":"addOffchainVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"contractId","outputs":[{"name":"id","type":"bytes32"},{"name":"version","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"votePreferences","type":"bool[]"},{"name":"r","type":"bytes32[]"},{"name":"s","type":"bytes32[]"},{"name":"v","type":"uint8[]"}],"name":"batchRelayedVotes","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"voteInFavor","type":"bool"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"voter","type":"address"}],"name":"getVotingPower","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"},{"name":"voter","type":"address"}],"name":"getVote","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newController","type":"address"}],"name":"changeVotingController","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"}],"name":"tally","outputs":[{"name":"s","type":"uint8"},{"name":"inFavor","type":"uint256"},{"name":"against","type":"uint256"},{"name":"offchainInFavor","type":"uint256"},{"name":"offchainAgainst","type":"uint256"},{"name":"tokenVotingPower","type":"uint256"},{"name":"totalVotingPower","type":"uint256"},{"name":"campaignQuorumTokenAmount","type":"uint256"},{"name":"initiator","type":"address"},{"name":"hasObserverInterface","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"bytes32"}],"name":"hasProposal","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"controller","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"votingLegalRep","type":"address"},{"indexed":false,"name":"token","type":"address"},{"indexed":false,"name":"oldState","type":"uint8"},{"indexed":false,"name":"newState","type":"uint8"}],"name":"LogProposalStateTransition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"token","type":"address"},{"indexed":false,"name":"voter","type":"address"},{"indexed":false,"name":"voteInFavor","type":"bool"},{"indexed":false,"name":"power","type":"uint256"}],"name":"LogVoteCast","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"token","type":"address"},{"indexed":false,"name":"votingLegalRep","type":"address"},{"indexed":false,"name":"inFavor","type":"uint256"},{"indexed":false,"name":"against","type":"uint256"},{"indexed":false,"name":"documentUri","type":"string"}],"name":"LogOffChainProposalResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldController","type":"address"},{"indexed":false,"name":"newController","type":"address"},{"indexed":false,"name":"by","type":"address"}],"name":"LogChangeVotingController","type":"event"}]