Stage I
Abstract
TON Improvement Proposal (or TIP) for Soft Majority Voting contracts specification
Copyright
TON Labs
Motivation
We need a system of voting contracts to regulate governance and decision making in Free TON. This contract is a principal tool of decentralization. It is supposed to be used in many aspects of Free TON blockchain to provide mechanism for community governance.
As such it should meet the following requirements:
Transparency — everyone in the community should be able to verify the results of the Voting, review proposal and links to its description and how results are implemented.
Participation — the voting should work even in case of low participation by the community members
Ease of use — every community member should be able to participate even without a deep technical knowledge
Security — the highest level of security should apply
Flexibility — it should be flexible enough to serve many applications requiring voting mechanism in Free TON
We proposes a decentralized Soft Majority Voting system based in part on TIP-3 Token specifications presented earlier. The system takes care of the following user flow: a Super Root contract is deployed by any party which wants to initiate an SMV pool. Super Root contract is the only place where all initiated proposals are stored. This is useful for example as a record keeping of set of proposals. Additional logic of super root tree can be implemented if needed for a more complex governance process where a combination of proposals needed to advance certain matter.
In the current proposal an Initiator, a party which would like to submit a proposal for a voting pays certain amount of funds into a Super Root contract for proposal initiation.
The payment may act like an anti-spam measure or to be used as deposit in more advanced logic, involving prediction betting, which is beyond the scope of the current TIP.
The parameters of any Soft Majority system must include a voting timeframe and a total number of possible votes. Therefore the proposal must contain the rule by which Voters will receive their rights to vote.
Voters can receive (or request) voting tokens from Proposal Root Contract (think of Ballot). User is requesting a Ballot by depositing funds for each Ballot.
Specification
Glossary
Initiator - user who wants to create new proposal.
Initiator Wallet - any smart contract that can make transfers with payload.
Super Root - smart contract which creates new proposal and deploys new SMV contract.
Proposal Root - smart contract which implements rule for distributing votes, implements SMV algorithm and deploys Voting Wallets; based on Root Token Wallet (see TIP-3).
SMV contract - the same as Proposal Root.
Voting Wallet - smart contract deployed by Proposal Root; can vote for proposal on behalf of user; based on TON Token Wallet (see TIP-3).
User Wallet (Ballot) - any smart contract that can work like a wallet; required to request votes from Proposal Root.
User - any person who wants to vote for proposal.
Asset Collateral Tokens (ACT) — is some Token contract that holds an Asset as Deposit for Token Deployment
Design
Description
Soft Majority Voting system consists of 3 smart contracts:
-
Super Root
- Deployed only once. Contains code of Proposal Root and some option parameters for vote distribution rule.
- Contains
createProposal
function which creates new proposal by deploying Proposal Root contract with defined total votes, proposal timeframe and proposal description as a requirement
-
createProposal
can optionally define a set of rules for ballot granting as for example pubKeys of Voters or other such scheme.
-
createProposal
function can be called by any smart contract. Proposal Initiator Wallet must send tokens to cover for Proposal deployment and Super Root gas costs and optionally voters gas costs (but this scheme is not described int he current proposal).
- Stores Proposal Root code.
- Stores dictionary with created proposals.
-
Proposal Root
- Deployed by Super Root.
- Stores root key, Voting Wallet code.
- Contains
RequestBallot
function that can be called by User Wallet. The function accepts User public key and value of tons deposit. The function can also accept Asset Collateral Tokens as deposits.
-
RequestBallot
applies vote distribution rule to deposit value and calculates amount of votes that user will have. Then it deploys user Voting Wallet which will be controlled by accepted user public key. Deploy message also contains encoded deployWallet
call with the following parameters: calculated amount of votes, User deposit value, User Wallet Address.
- Generates User Voting Wallet address by constructing
InitState
structure with initial data and code. Initial data consists of Proposal Root key, Token Wallet code, User public key, zero amount of votes and zero deposit value.
- Accumulates deposits of all users on its balance until the proposal is expired. When this happens User can request to return its deposit back by calling
returnDeposit
function using its Voting Wallet. The function must implement a Verification Check
described in TIP-3 to be sure that the caller is a valid Voting Wallet and if the check is successful do simple transfer (with requested number of tons) to user wallet.
- Can deploy as many wallets as requested by users.
- Implements SMV algorithm and has
vote
function to receive ‘yes’ or ‘no’ votes from Voting Wallet. The function must verify that the vote
sender is a valid Voting Wallet by implementing a Verification Check
described in TIP-3.
- Does not store addresses of voted Voting Wallets but only counts a total number of ‘yes’ and ‘no’ votes.
-
Ballot
- Deployed by Proposal Root. When its
deployBallot
function is called inits variable with amount of user votes and variable with user deposit sent to Proposal Root.
- Contains
sendVote
function that must be called by User to vote for proposal. The function can contain uint votes
argument if Ballot will have more than 1 vote by design. sendVote
must call vote
function of Proposal Root and decrease amount of votes by 1 (or by votes
value).
- When proposal is expired User can call Voting Wallet’s
requestDeposit
function which must implement next algorithm:
- throw exception if variable with user deposit isnt’ zero;
- set user deposit variable to zero;
- calls Proposal Root’s
returnDeposit
function;
- (Optional) can transfer votes to another Voting Wallet by calling
internalTransfer
function of receiver wallet (see TIP-3 spec).
- (Optional) can have
internalTransfer
function to receive votes from other Voting Wallet. internalTransfer
must implement Verification Check
to be sure that the sender is a valid Voting Wallet (check is described in TIP-3).
Functions
Super Root Interface
The contract must not have functions that can be called by external message. So no one must control it.
Anyone can deploy this contract thus deploying new SMV voting system.
Contract’s persistent data must store code of Proposal Root and Voting Wallet. They can be part of initial data or be initiated in constructor function.
Also contract must store a dictionary of created proposals: m_proposals
(id ⇒ Proposal) which initially must be empty.
Proposal
struct must have at least the following fields:
Proposal: {
root: address, // address of proposal root contract
...
}
createProposal
Can be called by other contracts only.
Adds new proposal to m_proposals
, prepares initial data for new Proposal Root contract, calculates it address and deploys it.
Proposal Root’s initial data must contain totalVotes
, startime
, endtime
, description
, random RootKey
(uint256) and optionally other params necessary for vote distribution rule (e.g. the price in tons of 1 vote).
NOTE: function must check that inbound msg.value ≥ X tons, where X is a fee for creation of proposal. X must cover function gas fees and forward fees for sending outbound internal message.
function createProposal(uint256 id, uint256 totalVotes, uint32 startime, uint32 endtime, string desc)
The function sends an internal confirmation message back to caller in case of success.
getProposalRootCode
get-method
Returns code of Proposal Root smart contract.
function getProposalRootCode() public returns (cell propRootCode)
getProposals
get-method
Returns a list with created proposals
function getProposals() public returns (Proposal[])
Proposal Root Interface
The contract must not have functions that can be called by external messages.
The contract must store:
- Voting Wallet’s code
walletCode
;
-
rootKey
(inserted into data by Super Root contract);
- 2 counters:
yesVotes
and noVotes
.
- vote distribution parameters:
- Proposal status:
true
- approved, false
- active;
requestBallot
Can be called by any smart contract.
Allows user to get a vote (or votes) for proposal.
Constructs Voting Wallet’s initial data (which contains zero votes, zero deposit value and Voting Wallet pubkey
, calculates user its address and deploys it. Deploy message must also contains encoded deployWallet
call with calculated number of votes and user deposit (see deployWallet
function).
NOTE: the function calculates number of user votes from inbound msg.value according to vote price (see example below), thus user wallet must send enough tons to request a vote. For example, if 1 vote costs 100 tons, then to request 5 votes user wallet must send 500 tons. If wallet sends 120 tons then only 1 vote must by assigned and 20 tons must be returned back.
function requestBallot(uint256 pubkey, uint256 deposit) public
The function sends internal confirmation message with unused deposit back to caller if succeeded.
The caller must pay for gas consumed by this function.
vote
Allows a voting wallet to vote for proposal.
The function increases values of yesVotes
and noVotes
and implements SMV algorithm.
The function can change the status of proposal.
NOTE: the function must throw an exception if vote is received outside the start and end time of proposal.
NOTE: the function must do a Verification Check
using pubkey
, rootKey
and walletCode
and throw exception in case of failure.
function vote(uint256 pubkey, uint256 yesVotes, uint256 noVotes)
The function sends internal confirmation message back to caller if succeeded.
The caller must pay for gas consumed by this function.
returnDeposit
Allows to return user deposit after the end of proposal lifetime.
Must be called by user’s Voting Wallet.
NOTE: the function must do a Verification Check
using pubkey
, rootKey
and walletCode
and throw exception if it is failed.
function returnDeposit(uint256 pubkey, address userWallet, uint256 deposit)
The function sends internal message with a specified deposit to the specified address.
The caller must pay for gas consumed by this function.
getVoteParams
get-method
Returns a price value in tons that must be deposited for one vote.
function getVotePrice() public returns (uint256 votePrice)
getProposal
get-method
Returns full information about proposal.
getProposal() public returns (
uint256 id,
uint32 start,
uint32 end,
string desc,
bool status,
uint256 totalVotes,
uint256 currentVotes,
uint256 yesVotes
)
Voting Wallet Interface
The contract is based on Token Wallet contract (see TIP-3).
deployWallet
Can be called by Proposal Root only.
Initializes wallet variables: number of wallet votes, user deposit value and user wallet address to return deposit to.
function deployWallet(uint256 votes, uint256 deposit, address userWallet)
sendVote
Can be called by external message only. The message must be signed with voting wallet’s key.
Sends defined number of votes to Proposal Root contract.
The function must call Proposal Root’s vote
function and decrease votes
counter by count
value.
NOTE: the function must not accept
message if wallet variable votes
is zero.
function sendVote(uint256 count, bool yesOrNo)
requestDeposit
Can be called by external message only. The message must be signed with voting wallet’s key.
The function must set to zero deposit
variable and call Proposal Root’s returnDeposit
function.
NOTE: the function must not accept
message if wallet variable deposit
is zero.
function requestDeposit()
Implementation example
__interface ISuperRoot {
__attribute__((external))
void constructor() = 1;
// Adds new proposal to m_proposals, prepares initial data for
// new Proposal Root contract, calculates it address and deploys it
__attribute__((internal))
void createProposal(uint256 id, uint256 totalVotes, uint32 startime, uint32 endtime,
bytes desc) = 2;
// Returns code of Proposal Root smart contract
__attribute__((getter))
cell getProposalRootCode() = 3;
// Returns a list with created proposals
__attribute__((getter))
dict_map<uint256, Proposal> getProposals() = 4;
};
struct ProposalInfo {
uint256 id;
uint32 start;
uint32 end;
bytes desc;
bool_t status;
uint256 totalVotes;
uint256 currentVotes;
uint256 yesVotes;
};
__interface IProposalRoot {
// Allows user to get a vote (or votes) for proposal
__attribute__((internal))
void requestBallot(uint256 pubkey) = 1;
// Allows a voting wallet to vote for proposal
__attribute__((internal))
void vote(uint256 pubkey, uint256 yesVotes, uint256 noVotes) = 2;
// Allows to return user deposit after the end of proposal lifetime
__attribute__((internal))
void returnDeposit(uint256 pubkey, address userWallet, uint256 deposit) = 3;
// Returns a price value in tons that must be deposited for one vote
__attribute__((getter))
uint256 getVoteParams() = 4;
// Returns full information about proposal
__attribute__((getter))
ProposalInfo getProposal() = 5;
};
__interface IVotingWallet {
// Initializes wallet variables
__attribute__((internal))
void deployWallet(uint256 votes, uint256 deposit, address userWallet) = 1;
// Sends defined number of votes to Proposal Root contract
__attribute__((external))
void sendVote(uint256 count, bool_t yesOrNo) = 2;
// The function must set to zero deposit variable and call Proposal Root's
// returnDeposit function
__attribute__((external))
void returnDeposit(uint256 count, bool_t yesOrNo) = 3;
}