How to Vote Safely with an ERC20 Token
The fungibility problem in voting and its design trade-offs
In the same way as a token shouldn’t be double-spent, a token shouldn’t vote twice. In this article we will examine four basic design proposals for token voting starting from this basic truth. The design proposals are: (1) injecting state, (2) dropping fungibility, (3) tokens with a history and cut-off date and (4) escrowing tokens after voting. We outline their properties and discuss their tradeoffs.
Why tokens shouldn’t vote twice
ERC20 Tokens can be used as a right to vote for a proposal. For example a Proof of Stake protocol can allow the token to vote on finalization of blocks [Casper] or it can vote on the release of a budget given a capital expenditure proposal [Shareholder].
In order to vote, a certain time frame must be established, for (1) when the vote is valid and (2) the elections resulting action can be executed.
When the token is transferred during this voting period, votes can be multiplied, because an ERC20 Token is ‘naturally’ fungible, i.e. you can’t distinguish one from another. This vote multiplication is a severe manipulation of any election. Therefore we postulate:
Theorem 1: When an ERC20 Token allows it’s holder to vote, then the same token should not be used to vote indiscriminately for a second time on the same topic.
What possible solutions solve the double voting problem?
At its core, the double voting problem is a fungibility problem: would the token be distinguishable, a smart contract could simply register if the identifiable token has already voted. Recently, non-fungible tokens came to the attention of a broader audience with Cryptokittie’s ERC-721 [ERC-721].
The fundamental question here is: should ballots be limited to non-fungible tokens?
Stock markets know both: a registered security [Registered] has a registered owner while with bearer shares [Bearer] the “physical possession of the certificate entitles the holder to exercise all legal rights associated with the stock” [Stock Certificates].
An example for votable bearer securities are the shares of the Swiss pharma company Roche.
“ Because its shares are bearer shares, Roche cannot and does not keep a register of its shareholders. Information for holders of bearer shares, including invitations to the Annual General Meeting, is disseminated by publication in the Schweizerisches Handelsamtsblatt (Swiss Official Gazette of Commerce), various Swiss daily and financial newspapers and the Roche Internet.” [Roche]
Thus we find evidence that fungible securities have been issued and used to vote in the past. We infer that there is no fundamental economical problem to use ERC20 tokens for voting.
The historical argument supports our hypothesis, but it is necessary to find architectures that hold against the double voting problem.
Proposal 1: Injecting State into the Token
In the previous chapter we described ERC20 as ‘naturally’ fungible. In fact, the specification does not demand fungibility explicitly. But we see that an individual asset can not be addressed in the standard, tokens are treated as amounts rendering them indistinguishable.
1 // https://github.com/ethereum/EIPs/issues/20
2 contract ERC20 {
3 function totalSupply() constant returns (uint totalSupply);
4 function balanceOf(address _owner) constant
returns (uint balance);
5 function transfer(address _to, uint _value)
returns (bool success);
6 function transferFrom(address _from, address _to, uint _value)
returns (bool success);
7 function approve(address _spender, uint _value)
returns (bool success);
8 function allowance(address _owner, address _spender) constant
returns (uint remaining);
9 event Transfer(address indexed _from,
address indexed _to, uint _value);
10 event Approval(address indexed _owner,
address indexed _spender, uint _value);
11 }
Fungibility can be defined as economic equivalence of utility [Utility Theory].
Theorem 2: A token contract is fungible if the utility of one unit of the token is the same as any other unit of the token. U(ta)=U(tb) for all transactions.
A developer could implement ERC20 as a list of distinct tokens and provide additional functions for voting. Fungibility would be broken since:
Theorem 3: The utility of a token that has voted is smaller or equal the utility of a token that has not voted.
In most cases the utility will be smaller. Only if there is no benefit from voting, a token that has voted will have the equivalent utility of a token that has not voted.
Whatever workaround one proposes, external contracts that cope with ERC20 Tokens will implicitly assume fungibility and can not take into account that one unit is more valuable than another unit of the same token. The consequences are that known exchanges like EtherDelta would treat tokens of different value the same.
The coercion of identity onto an ERC20 Token makes the token less transparent and less secure.
Proposal 2: Voting with ERC721 non-fungible tokens
A non-fungible token has a unique identity and can hold an individual state voted/ not-voted. Because every single token is valued individually there is no source of confusion or error.
Theorem 4: The utility of a non-fungible token is independent of the utility of another token of the same class.
Yet often it is desirable to have fungible tokens as they regulate a cryptoeconomic system. Ethereum is in itself an example for the usefulness of the token Ether which is used to pay miners to run smart contracts.
The fungibility dilemma arises from the fact that indistinguishable tokens can be traded during a voting period. While Proposal 1 and 2 tackle the “distinguishable” side, Proposal 3 and 4 cope with the fact that the token is traded. Now let’s discuss this aspect.
Proposal 3: Tokens with a history
A blockchain never forgets. Therefore we can define a cut-off date at which point in time the bearer of the tokens is eligible to vote. Even if the tokens are traded during the voting period, only the votes of the bearers of the cut-off date count.
This is what we at Validity Labs have implemented in our Token Voting System proof of concept [TokenVoting].
While the blockchain never forgets, the EVM does not allow smart contracts to read the state of past blocks. Therefore the token must keep track of its balances at a certain point in time. Validity Labs lent the basic concepts of checkpoints from GiveEth’s MiniMe Token [GiveEth] that presents us with a function where the historical balance of the token can be retrieved by performing a binary search over an array of checkpoints [MiniMe]:
/// @dev `getValueAt` retrieves the number of tokens at a given block number
/// @param checkpoints The history of values being queried
/// @param _block The block number to retrieve the value at
/// @return The number of tokens being queried function getValueAt(Checkpoint[] storage checkpoints,
uint _block )
constant internal returns (uint) {
if (checkpoints.length == 0) return 0;
if (_block >= checkpoints[checkpoints.length-1].fromBlock)
return checkpoints[checkpoints.length-1].value;
if (_block < checkpoints[0].fromBlock) return 0;
// Binary search of the value in the array
uint min = 0;
uint max = checkpoints.length-1;
while (max > min) {
uint mid = (max + min + 1)/ 2;
if (checkpoints[mid].fromBlock<=_block) {
min = mid;
} else {
max = mid-1;
}
}
Proposal 4: Escrowing voted tokens
In this approach, in order to preserve fungibility between voted and not-voted tokens, we retain all tokens that have voted in an escrow until the voting period is over. This approach was implemented by “theDAO”, one of the first major token voting systems in Ethereum.
A drawback of this approach is that it disincentives voting, as we can demonstrate with a thought experiment: suppose all but one token voted at the beginning of a two month voting period. Would the value of the token left increase or decrease? Resource constraints and the mechanics of supply and demand would very likely to lead to an appreciation of the token. A voter is better off if he waits until the very last instance of the voting period to cast his vote.
Conclusions
In this article we deduced that it is harmful for a ballot if tokens vote twice. We recognized that fungibility makes it hard to prevent this. We then laid out four comprehensive approaches to tackle the problem with different trade offs:
- injecting state
pro: simplicity
con: fungible tokens aquire different values - dropping fungibility
pro: clean solution, ERC721
con: fungibility and ERC20 is often desirable - tokens with a history and cut-off date
pro: tokens are fungible and tradeable all the time
con: needs a custom token that remembers its history - escrowing tokens after voting
pro: compatible with any ERC20 Token
con: token is subtracted from market after voting, incentivises late voting/ abstinence.
Nota Bene: Voting is not the only application where token fungibility is in danger. This analysis is transferable almost identical to a dividend payments system or any application where state is combined with an ERC20 token.
Final advice:
If fungibility is desirable, one has to decide if any token should be able to vote or a custom token is acceptable and pick the corresponding solution.
Appendix
All web sources as of 2017–12-14:
[Casper] see Casper the Friendly Finality Gadget
[Shareholder] see Ethereum Example Sharholder Association
[ERC-721] Ethereum Request for Comment on Non-Fungible Tokens https://github.com/ethereum/EIPs/issues/721
[Bearer] http://www.businessdictionary.com/definition/bearer-security.html
[Registered] http://www.businessdictionary.com/definition/registered-security.html
[Stock Certificates] see https://en.wikipedia.org/wiki/Stock_certificate#History
[Roche] https://www.roche.com/investors/faq_investors.htm
[Utility Theory] https://en.wikipedia.org/wiki/Utility
[ERC721] https://github.com/ethereum/EIPs/issues/721
[TokenVoting] https://github.com/validitylabs/token-voting-system
[GiveEth] https://github.com/Giveth/minime
[MiniMe] https://github.com/Giveth/minime/blob/master/contracts/MiniMeToken.sol#L420