From 13a599ef6b0f1d5be9bfa0c6910b6322c70048c1 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 20 Oct 2022 12:28:46 +0200 Subject: [PATCH 1/9] initial draft --- EIPS/eip-XXXX.md | 407 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 EIPS/eip-XXXX.md diff --git a/EIPS/eip-XXXX.md b/EIPS/eip-XXXX.md new file mode 100644 index 0000000000000..aee766a9f8735 --- /dev/null +++ b/EIPS/eip-XXXX.md @@ -0,0 +1,407 @@ +--- +eip: +title: Voting with delegation +description: An interface for voting weight tracking, with delegation support +author: Hadrien Croubois (@Amxx) +discussions-to: +status: draft +type: Standards Track +category: ERC +created: 2022-07-04 +requires: 712 +--- + +## Abstract + +Many DAOs (decentralized autonomous organizations) rely on tokens to represent one's voting power. In order to perform this task effectively, the token contracts need to include specific mechanisms such as checkpoints and delegation. + +The existing implementations are not standardized. + +This EIP proposes to standardize the way votes are delegated by an account to another, and the way current and past votes are tracked and queried. The corresponding behavior is compatible with many token types, including but not limited to [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md). + +This EIP also considers the diversity of time tracking functions, allowing the voting tokens (and any contract associated with it) to track the votes based on `block.number`, `block.timestamp`, or any other non-decreasing function. + +## Motivation + +Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the first use cases of blockchain and smart contract technologies. Many communities are today organized around a governance contract that allows users to vote. Among these communities, some represent the voting power using transferable tokens ([EIP-20](./eip-20.md), [ERC-721](./eip-721.md), other). In this context, the more tokens you own, the more voting power you have. Governor contracts, such as Compound's GovernorBravo, read from these "voting token" contracts to get the voting power of the users. + +Unfortunately, simply using the `balanceOf(address)` function present in most token standards is not good enough: +- The values are not checkpointed, so a user can vote, transfer its tokens to a new account, and vote again with the same tokens. +- A user cannot delegate their voting power to someone else without transferring full ownership of the tokens. + +These constraints have led to the emergence of voting tokens with delegation that the following logic: +- Users can delegate the voting power of their tokens to themselves or a third party. This creates a distinction between balance and voting weight. +- The voting weights of accounts are checkpointed, allowing lookups for past values at different points in time. +- The balances are not checkpointed. + +This EIP is proposing to standardize the interface and behavior of these voting tokens. + +Additionally, the existing (non-standardized) implementations are limited to `block.number` based checkpoints. This choice causes many issues in a multichain environment, where some chains (particularly L2s) have an inconsistent or unpredictable time between blocks. This EIP also addresses this issue by allowing the voting token to use any time tracking function it wants, and exposing it so that other contracts (such as a Governor) can stay consistent with the token checkpoints. + +## Specifications + +Following pre-existing (but not-standardized) implementation, the EIP proposes the following mechanism. + +Each user account (address) can delegate to an account of its choice. This can be itself, someone else, or no one (represented by `address(0)`). Assets held by the user cannot express their voting power unless they are delegated. + +When a "delegator" delegates its tokens voting power to a "delegatee", its balance is added to the voting power of the delegatee. If the delegator changes its delegation, the voting power is subtracted from the old delegatee's voting power and added to the new delegate's voting power. The voting power of each account is tracked through time so that it is possible to query its value in the past. With tokens being delegated to at most one delegate at a given point in time, double voting is prevented. + +Whenever tokens are transferred from one account to another, the associated voting power should be deducted from the sender's delegate and added to the receiver's delegate. + +Tokens that are delegated to `address(0)` should not be tracked. This allows users to optimize the gas cost of their token transfers by skipping the checkpoint update for their delegate. + +To accommodate different types of chains, we want the voting checkpoint system to support different forms of time tracking. On the Ethereum mainnet, using block numbers provides backward compatibility with applications that historically use it. On the other hand, using timestamps provides better semantics for end users, and accommodates use cases where the duration is expressed in seconds. Other monotonic functions could also be deemed relevant by developers based on the characteristics of future applications and blockchains. + +Both timestamps, block numbers, and other possible modes use the same external interfaces. This allows transparent binding of third-party contracts, such as governor systems, to the vote tracking built into the voting contracts. For this to be effective, the voting contracts must, in addition to all the vote-tracking functions, expose the current value used for time-tracking. + +### Methods + +#### now + +This function returns the current timepoint. It could be `block.timestamp`, `block.number` (or any other **non-decreasing** function) depending on the mode the contract is operating on. + +- If operating using **block number**, then this function SHOULD be implemented. +- If operating using **timestamp**, then this function MUST be implemented. +- If operating using any other mode, then this function MUST be implemented. + +This function is thus optional, and its absence should be considered as a marker of the contract operating using block number. (This makes this EIP compatible with pre-existing voting contracts) + +```yaml +- name: now + type: function + stateMutability: view + inputs: [] + outputs: + - name: timepoint + type: uint256 +``` + +#### getVotes + +This function returns the current voting weight of an account. This corresponds to all the voting power delegated to it at the moment this function is called. + +As tokens delegated to `address(0)` should not be counted/snapshoted, `getVotes(0)` SHOULD always return `0`. + +This function MUST be implemented + +```yaml +- name: getVotes + type: function + stateMutability: view + inputs: + - name: account + type: address + outputs: + - name: votingWeight + type: uint256 +``` + +#### getPastVotes + +This function returns the historical voting weight of an account. This corresponds to all the voting power delegated to it at a specific timepoint. The timepoint parameter should match the operating mode of the contract. This function SHOULD only serve past checkpoints that are immutable. Calling this function with a timepoint that is greater or equal to `now()` SHOULD revert. For any timepoint that is strictly smaller than `now()`, the value returned by `getPastVotes` should be constant. + +As tokens delegated to `address(0)` should not be counted/snapshoted, `getPastVotes(0,x)` SHOULD always return `0` (for all values of `x`). + +This function MUST be implemented + +```yaml +- name: getPastVotes + type: function + stateMutability: view + inputs: + - name: account + type: address + - name: timepoint + type: uint256 + outputs: + - name: votingWeight + type: uint256 +``` + +#### delegates + +This function returns the address to which the voting power of an account is currently delegated. + +Note that if the delegate is `address(0)` then the voting power SHOULD NOT be checkpointed, and it should not be possible to vote with it. + +This function MUST be implemented + +```yaml +- name: delegates + type: function + stateMutability: view + inputs: + - name: account + type: address + outputs: + - name: delegatee + type: address +``` + +#### delegate + +This function changes the caller's delegate, updating the vote delegation in the meantime. + +This function MUST be implemented + +```yaml +- name: delegates + type: function + stateMutability: nonpayable + inputs: + - name: delegatee + type: address + outputs: [] +``` + +#### delegateBySig + +This function changes an account's delegate using a signature, updating the vote delegation in the meantime. + +This function MUST be implemented + +```yaml +- name: delegateBySig + type: function + stateMutability: nonpayable + inputs: + - name: delegatee + type: address + - name: nonce + type: uint256 + - name: expiry + type: uint256 + - name: v + type: uint8 + - name: r + type: bytes32 + - name: s + type: bytes32 + outputs: [] +``` + +This signature should follow the [EIP-712](./eip-712.md) format: + +A call to `delegateBySig(delegatee, nonce, expiry, v, r, s)` changes the signer's delegate to `delegatee`, increment the signer's nonce by 1, and emits a corresponding `DelegateChanged` event, and possibly `DelegateVotesChanged` events for the old and the new delegate accounts, if and only if the following conditions are met: + + +- The current timestamp is less than or equal to `expiry`. +- `nonces(signer)` (before the state update) is equal to `nonce`. + +If any of these conditions are not met, the `delegateBySig` call must revert. This translate to the following solidity code: + +```sol +require(expiry <= block.timestamp) +bytes signer = ecrecover( + keccak256(abi.encodePacked( + hex"1901", + DOMAIN_SEPARATOR, + keccak256(abi.encode( + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"), + delegatee, + nonce, + expiry)), + v, r, s) +require(signer != address(0)); +require(nounces[signer] == nonce); +// increment nonce +// set delegation of `signer` to `delegatee` +``` + +where `DOMAIN_SEPARATOR` is defined according to [EIP-712](./eip-712.md). The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains, +and satisfy the requirements of EIP-712, but is otherwise unconstrained. + +A common choice for `DOMAIN_SEPARATOR` is: +```solidity +DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), + keccak256(bytes(name)), + keccak256(bytes(version)), + chainid, + address(this) +)); +``` + +In other words, the message is the ERC-712 typed structure: + +```js +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Delegation": [{ + "name": "delegatee", + "type": "address" + }, + { + "name": "nonce", + "type": "uint256" + }, + { + "name": "expiry", + "type": "uint256" + } + ], + "primaryType": "Permit", + "domain": { + "name": contractName, + "version": version, + "chainId": chainid, + "verifyingContract": contractAddress + }, + "message": { + "delegatee": delegatee, + "nonce": nonce, + "expiry": expiry + } +}} +``` + +Note that nowhere in this definition do we refer to `msg.sender`. The caller of the `delegateBySig` function can be any address. + +When this function is successfully executed, the delegator's nonce MUST be incremented to prevent replay attacks. + +#### nonces + +This function returns the current nonce for a given account. + +Signed delegations (see `delegateBySig`) are only accepted if the nonce used in the EIP-712 signature matches the return of this function. This value of `nonce(delegator)` should be incremented whenever a call to `delegateBySig` is performed on behalf of `delegator`. + +This function MUST be implemented + +```yaml +- name: nonces + type: function + stateMutability: view + inputs: + - name: account + type: delegator + outputs: + - name: nonce + type: uint256 +``` + +### Events + +#### DelegateChanged + +`delegator` changes the delegation of its assets from `fromDelegate` to `toDelegate`. + +MUST be emitted when the delegate for an account is modified by `delegate(address)` or `delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)`. + +```yaml +- name: DelegateChanged + type: event + inputs: + - name: delegator + indexed: true + type: address + - name: fromDelegate + indexed: true + type: address + - name: toDelegate + indexed: true + type: address +``` + +#### DelegateVotesChanged + +`delegate` available voting power changes from `previousBalance` to `newBalance`. + +This MUST be emitted when: +- an account (that holds more than 0 assets) updates its delegation from or to `delegate`, +- an asset transfer from or to an account that is delegated to `delegate`. + +```yaml +- name: DelegateVotesChanged + type: event + inputs: + - name: delegate + indexed: true + type: address + - name: previousBalance + indexed: false + type: uint256 + - name: newBalance + indexed: false + type: uint256 +``` + +### Solidity interface + +```sol +interface IERC_XXXX { + event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); + event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); + + function now() external view returns (uint256); + function getVotes(address account) external view returns (uint256); + function getPastVotes(address account, uint256 timepoint) external view returns (uint256); + function delegates(address account) external view returns (address); + function nonces(address owner) public view virtual returns (uint256) + + function delegate(address delegatee) external; + function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; +} +``` + +### Expected properties + +- The `now()` function MUST be non-decreasing. +- For all timepoints `t < now()`, `getVotes(address(0))` and `getPastVotes(address(0), t)` SHOULD return 0. +- For all accounts `a != 0`, `getVotes(a)` SHOULD be the sum of the "balances" of all the accounts that delegate to `a`. +- For all accounts `a != 0` and all timestamp `t < now()`, `getPastVotes(a, t)` SHOULD be the sum of the "balances" of all the accounts that delegated to `a` when `now()` overtook `t`. +- For all accounts `a`, `getPastVotes(a, t)` MUST be constant after `t < now()` is reached. +- For all accounts `a`, the action of changing the delegate from `b` to `c` MUST not increase the current voting power of `b` (`getVotes(b)`) and MUST not decrease the current voting power of `c` (`getVotes(c)`). + +## Rationale + +Delegation allows token holders to trust a delegate with their vote while keeping full custody of their token. This means that only a small-ish number of delegates need to pay gas for voting. This leads to better representation of small token holders by allowing their votes to be cast without requiring them to pay expensive gas fees. Users can take over their voting power at any point, and delegate it to someone else, or to themselves. + +The use of checkpoints prevents double voting. Votes, for example in the context of a governance proposal, should rely on a snapshot defined by a timepoint. Only tokens delegated at that timepoint can be used for voting. This means any token transfer performed after the snapshot will not affect the voting power of the sender/receiver's delegate. This also means that in order to vote, someone must acquire tokens and delegate them before the snapshot is taken. Governors can, and do, include a delay between the proposal is submitted and the snapshot is taken so that users can take the necessary actions (change their delegation, buy more tokens, ...). + +`delegateBySig` is necessary to offer a gasless workflow to token holders that do not want to pay gas for voting. + +The `nonces` mapping is given for replay protection. + +ERC-712 typed messages are included because of its widespread adoption in many wallet providers. + +## Backwards Compatibility + +Compound and OpenZeppelin already provide implementations of voting tokens. The delegation-related methods are shared between the two implementations and this EIP. For the vote lookup, this EIP uses OpenZeppelin's implementation (with return type uint256) as Compound's implementation causes significant restrictions of the acceptable values (return type is uint96). + +Both implementations use `block.number` for their checkpoints and do not implement the `now()` method, which is compatible with this EIP. + +Existing governors, that are currently compatible with OpenZeppelin's implementation will be compatible with the "block number mode" of this EIP. + +## Security Considerations + +Before doing a lookup, one should check the return value of `now()` and make sure that the parameters of the lookup are consistent. Performing a lookup using a timestamp argument on a contract that uses block numbers will very likely cause a revert. On the other end, performing a lookup using a block number argument on a contract that uses timestamps will likely return 0. + +Though the signer of a `Delegation` may have a certain party in mind to submit their transaction, another party can always front-run this transaction and call `delegateBySig` before the intended party. The result is the same for the `Delegation` signer, however. + +Since the ecrecover precompile fails silently and just returns the zero address as `signer` when given malformed messages, it is important to ensure `signer != address(0)` to avoid `delegateBySig` from delegating "zombie funds" belonging to the zero address. + +Signed `Delegation` messages are censorable. The relaying party can always choose to not submit the `Delegation` after having received it, withholding the option to submit it. The `expiry` parameter is one mitigation to this. If the signing party holds ETH they can also just submit the `Delegation` themselves, which can render previously signed `Delegation`s invalid. + +If the `DOMAIN_SEPARATOR` contains the `chainId` and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split. + +## Copyright +Copyright and related rights waived via [CC0](../LICENSE.md). From 4c9b8639824cd247b39105c7c71c83af555d79d3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 20 Oct 2022 12:30:36 +0200 Subject: [PATCH 2/9] add eip number --- EIPS/eip-XXXX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-XXXX.md b/EIPS/eip-XXXX.md index aee766a9f8735..c0d7c1d895f2d 100644 --- a/EIPS/eip-XXXX.md +++ b/EIPS/eip-XXXX.md @@ -1,5 +1,5 @@ --- -eip: +eip: 5805 title: Voting with delegation description: An interface for voting weight tracking, with delegation support author: Hadrien Croubois (@Amxx) From ede206e74bc74aedce86a6d9c2cb031e49b9845b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 20 Oct 2022 12:31:12 +0200 Subject: [PATCH 3/9] rename focument --- EIPS/{eip-XXXX.md => eip-5805.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename EIPS/{eip-XXXX.md => eip-5805.md} (100%) diff --git a/EIPS/eip-XXXX.md b/EIPS/eip-5805.md similarity index 100% rename from EIPS/eip-XXXX.md rename to EIPS/eip-5805.md From 84f28f27e68127b59d4af6dc47bc4a48aafe02dd Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 20 Oct 2022 14:02:06 +0200 Subject: [PATCH 4/9] add discussion --- EIPS/eip-5805.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index c0d7c1d895f2d..7832645445682 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -3,7 +3,7 @@ eip: 5805 title: Voting with delegation description: An interface for voting weight tracking, with delegation support author: Hadrien Croubois (@Amxx) -discussions-to: +discussions-to: https://ethereum-magicians.org/t/eip-5805-voting-with-delegation/11407 status: draft type: Standards Track category: ERC From 3daaf3811539b44e3759ecbdc090c1652130f192 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 20 Oct 2022 14:54:48 +0200 Subject: [PATCH 5/9] fix EIP Walidator checks --- EIPS/eip-5805.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index 7832645445682..8098853c69499 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -4,7 +4,7 @@ title: Voting with delegation description: An interface for voting weight tracking, with delegation support author: Hadrien Croubois (@Amxx) discussions-to: https://ethereum-magicians.org/t/eip-5805-voting-with-delegation/11407 -status: draft +status: Draft type: Standards Track category: ERC created: 2022-07-04 @@ -17,13 +17,13 @@ Many DAOs (decentralized autonomous organizations) rely on tokens to represent o The existing implementations are not standardized. -This EIP proposes to standardize the way votes are delegated by an account to another, and the way current and past votes are tracked and queried. The corresponding behavior is compatible with many token types, including but not limited to [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md). +This EIP proposes to standardize the way votes are delegated by an account to another, and the way current and past votes are tracked and queried. The corresponding behavior is compatible with many token types, including but not limited to [EIP-20](./eip-20.md) and [EIP-721](./eip-721.md). This EIP also considers the diversity of time tracking functions, allowing the voting tokens (and any contract associated with it) to track the votes based on `block.number`, `block.timestamp`, or any other non-decreasing function. ## Motivation -Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the first use cases of blockchain and smart contract technologies. Many communities are today organized around a governance contract that allows users to vote. Among these communities, some represent the voting power using transferable tokens ([EIP-20](./eip-20.md), [ERC-721](./eip-721.md), other). In this context, the more tokens you own, the more voting power you have. Governor contracts, such as Compound's GovernorBravo, read from these "voting token" contracts to get the voting power of the users. +Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the first use cases of blockchain and smart contract technologies. Many communities are today organized around a governance contract that allows users to vote. Among these communities, some represent the voting power using transferable tokens ([EIP-20](./eip-20.md), [EIP-721](./eip-721.md), other). In this context, the more tokens you own, the more voting power you have. Governor contracts, such as Compound's GovernorBravo, read from these "voting token" contracts to get the voting power of the users. Unfortunately, simply using the `balanceOf(address)` function present in most token standards is not good enough: - The values are not checkpointed, so a user can vote, transfer its tokens to a new account, and vote again with the same tokens. @@ -38,7 +38,7 @@ This EIP is proposing to standardize the interface and behavior of these voting Additionally, the existing (non-standardized) implementations are limited to `block.number` based checkpoints. This choice causes many issues in a multichain environment, where some chains (particularly L2s) have an inconsistent or unpredictable time between blocks. This EIP also addresses this issue by allowing the voting token to use any time tracking function it wants, and exposing it so that other contracts (such as a Governor) can stay consistent with the token checkpoints. -## Specifications +## Specification Following pre-existing (but not-standardized) implementation, the EIP proposes the following mechanism. @@ -223,7 +223,7 @@ DOMAIN_SEPARATOR = keccak256( )); ``` -In other words, the message is the ERC-712 typed structure: +In other words, the message is the EIP-712 typed structure: ```js { @@ -381,7 +381,7 @@ The use of checkpoints prevents double voting. Votes, for example in the context The `nonces` mapping is given for replay protection. -ERC-712 typed messages are included because of its widespread adoption in many wallet providers. +EIP-712 typed messages are included because of its widespread adoption in many wallet providers. ## Backwards Compatibility From 26702774e8a0eb119b82c9ce2d92a80b77636670 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 24 Oct 2022 13:56:47 +0200 Subject: [PATCH 6/9] Apply suggestions from code review Co-authored-by: Pandapip1 <45835846+Pandapip1@users.noreply.github.com> --- EIPS/eip-5805.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index 8098853c69499..b368e9d762a32 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -13,17 +13,11 @@ requires: 712 ## Abstract -Many DAOs (decentralized autonomous organizations) rely on tokens to represent one's voting power. In order to perform this task effectively, the token contracts need to include specific mechanisms such as checkpoints and delegation. - -The existing implementations are not standardized. - -This EIP proposes to standardize the way votes are delegated by an account to another, and the way current and past votes are tracked and queried. The corresponding behavior is compatible with many token types, including but not limited to [EIP-20](./eip-20.md) and [EIP-721](./eip-721.md). - -This EIP also considers the diversity of time tracking functions, allowing the voting tokens (and any contract associated with it) to track the votes based on `block.number`, `block.timestamp`, or any other non-decreasing function. +Many DAOs (decentralized autonomous organizations) rely on tokens to represent one's voting power. In order to perform this task effectively, the token contracts need to include specific mechanisms such as checkpoints and delegation. The existing implementations are not standardized. This EIP proposes to standardize the way votes are delegated from one account to another, and the way current and past votes are tracked and queried. The corresponding behavior is compatible with many token types, including but not limited to [EIP-20](./eip-20.md) and [EIP-721](./eip-721.md). This EIP also considers the diversity of time tracking functions, allowing the voting tokens (and any contract associated with it) to track the votes based on `block.number`, `block.timestamp`, or any other non-decreasing function. ## Motivation -Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the first use cases of blockchain and smart contract technologies. Many communities are today organized around a governance contract that allows users to vote. Among these communities, some represent the voting power using transferable tokens ([EIP-20](./eip-20.md), [EIP-721](./eip-721.md), other). In this context, the more tokens you own, the more voting power you have. Governor contracts, such as Compound's GovernorBravo, read from these "voting token" contracts to get the voting power of the users. +Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the most important use cases of blockchain and smart contract technologies. Today, many communities are organized around a governance contract that allows users to vote. Among these communities, some represent voting power using transferable tokens ([EIP-20](./eip-20.md), [EIP-721](./eip-721.md), other). In this context, the more tokens one owns, the more voting power one has. Governor contracts, such as Compound's `GovernorBravo`, read from these "voting token" contracts to get the voting power of the users. Unfortunately, simply using the `balanceOf(address)` function present in most token standards is not good enough: - The values are not checkpointed, so a user can vote, transfer its tokens to a new account, and vote again with the same tokens. From 28be477d2df39b5fc86ec3587d630be98a6d0761 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 24 Oct 2022 17:40:13 +0200 Subject: [PATCH 7/9] fix typo --- EIPS/eip-5805.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index b368e9d762a32..3e0b7881e7e4e 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -139,7 +139,7 @@ This function changes the caller's delegate, updating the vote delegation in the This function MUST be implemented ```yaml -- name: delegates +- name: delegate type: function stateMutability: nonpayable inputs: From 937a4edb3bb4c598d488d44cdaac5a04010aec09 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Jan 2023 16:07:05 +0100 Subject: [PATCH 8/9] =?UTF-8?q?rename=20now=20=E2=86=92=20clock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EIPS/eip-5805.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index 3e0b7881e7e4e..f8f35a08dc500 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -50,7 +50,7 @@ Both timestamps, block numbers, and other possible modes use the same external i ### Methods -#### now +#### clock This function returns the current timepoint. It could be `block.timestamp`, `block.number` (or any other **non-decreasing** function) depending on the mode the contract is operating on. @@ -61,7 +61,7 @@ This function returns the current timepoint. It could be `block.timestamp`, `blo This function is thus optional, and its absence should be considered as a marker of the contract operating using block number. (This makes this EIP compatible with pre-existing voting contracts) ```yaml -- name: now +- name: clock type: function stateMutability: view inputs: [] @@ -92,7 +92,7 @@ This function MUST be implemented #### getPastVotes -This function returns the historical voting weight of an account. This corresponds to all the voting power delegated to it at a specific timepoint. The timepoint parameter should match the operating mode of the contract. This function SHOULD only serve past checkpoints that are immutable. Calling this function with a timepoint that is greater or equal to `now()` SHOULD revert. For any timepoint that is strictly smaller than `now()`, the value returned by `getPastVotes` should be constant. +This function returns the historical voting weight of an account. This corresponds to all the voting power delegated to it at a specific timepoint. The timepoint parameter should match the operating mode of the contract. This function SHOULD only serve past checkpoints that are immutable. Calling this function with a timepoint that is greater or equal to `clock()` SHOULD revert. For any timepoint that is strictly smaller than `clock()`, the value returned by `getPastVotes` should be constant. As tokens delegated to `address(0)` should not be counted/snapshoted, `getPastVotes(0,x)` SHOULD always return `0` (for all values of `x`). @@ -345,7 +345,7 @@ interface IERC_XXXX { event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); - function now() external view returns (uint256); + function clock() external view returns (uint256); function getVotes(address account) external view returns (uint256); function getPastVotes(address account, uint256 timepoint) external view returns (uint256); function delegates(address account) external view returns (address); @@ -358,11 +358,11 @@ interface IERC_XXXX { ### Expected properties -- The `now()` function MUST be non-decreasing. -- For all timepoints `t < now()`, `getVotes(address(0))` and `getPastVotes(address(0), t)` SHOULD return 0. +- The `clock()` function MUST be non-decreasing. +- For all timepoints `t < clock()`, `getVotes(address(0))` and `getPastVotes(address(0), t)` SHOULD return 0. - For all accounts `a != 0`, `getVotes(a)` SHOULD be the sum of the "balances" of all the accounts that delegate to `a`. -- For all accounts `a != 0` and all timestamp `t < now()`, `getPastVotes(a, t)` SHOULD be the sum of the "balances" of all the accounts that delegated to `a` when `now()` overtook `t`. -- For all accounts `a`, `getPastVotes(a, t)` MUST be constant after `t < now()` is reached. +- For all accounts `a != 0` and all timestamp `t < clock()`, `getPastVotes(a, t)` SHOULD be the sum of the "balances" of all the accounts that delegated to `a` when `clock()` overtook `t`. +- For all accounts `a`, `getPastVotes(a, t)` MUST be constant after `t < clock()` is reached. - For all accounts `a`, the action of changing the delegate from `b` to `c` MUST not increase the current voting power of `b` (`getVotes(b)`) and MUST not decrease the current voting power of `c` (`getVotes(c)`). ## Rationale @@ -381,13 +381,13 @@ EIP-712 typed messages are included because of its widespread adoption in many w Compound and OpenZeppelin already provide implementations of voting tokens. The delegation-related methods are shared between the two implementations and this EIP. For the vote lookup, this EIP uses OpenZeppelin's implementation (with return type uint256) as Compound's implementation causes significant restrictions of the acceptable values (return type is uint96). -Both implementations use `block.number` for their checkpoints and do not implement the `now()` method, which is compatible with this EIP. +Both implementations use `block.number` for their checkpoints and do not implement the `clock()` method, which is compatible with this EIP. Existing governors, that are currently compatible with OpenZeppelin's implementation will be compatible with the "block number mode" of this EIP. ## Security Considerations -Before doing a lookup, one should check the return value of `now()` and make sure that the parameters of the lookup are consistent. Performing a lookup using a timestamp argument on a contract that uses block numbers will very likely cause a revert. On the other end, performing a lookup using a block number argument on a contract that uses timestamps will likely return 0. +Before doing a lookup, one should check the return value of `clock()` and make sure that the parameters of the lookup are consistent. Performing a lookup using a timestamp argument on a contract that uses block numbers will very likely cause a revert. On the other end, performing a lookup using a block number argument on a contract that uses timestamps will likely return 0. Though the signer of a `Delegation` may have a certain party in mind to submit their transaction, another party can always front-run this transaction and call `delegateBySig` before the intended party. The result is the same for the `Delegation` signer, however. From 250c8424922afc4e0b77b40ca57f13bef176e4a2 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Jan 2023 16:10:53 +0100 Subject: [PATCH 9/9] fix markdown linter --- EIPS/eip-5805.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index f8f35a08dc500..657216340d39d 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -20,10 +20,12 @@ Many DAOs (decentralized autonomous organizations) rely on tokens to represent o Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the most important use cases of blockchain and smart contract technologies. Today, many communities are organized around a governance contract that allows users to vote. Among these communities, some represent voting power using transferable tokens ([EIP-20](./eip-20.md), [EIP-721](./eip-721.md), other). In this context, the more tokens one owns, the more voting power one has. Governor contracts, such as Compound's `GovernorBravo`, read from these "voting token" contracts to get the voting power of the users. Unfortunately, simply using the `balanceOf(address)` function present in most token standards is not good enough: + - The values are not checkpointed, so a user can vote, transfer its tokens to a new account, and vote again with the same tokens. - A user cannot delegate their voting power to someone else without transferring full ownership of the tokens. These constraints have led to the emergence of voting tokens with delegation that the following logic: + - Users can delegate the voting power of their tokens to themselves or a third party. This creates a distinction between balance and voting weight. - The voting weights of accounts are checkpointed, allowing lookups for past values at different points in time. - The balances are not checkpointed. @@ -206,6 +208,7 @@ where `DOMAIN_SEPARATOR` is defined according to [EIP-712](./eip-712.md). The `D and satisfy the requirements of EIP-712, but is otherwise unconstrained. A common choice for `DOMAIN_SEPARATOR` is: + ```solidity DOMAIN_SEPARATOR = keccak256( abi.encode( @@ -320,6 +323,7 @@ MUST be emitted when the delegate for an account is modified by `delegate(addres `delegate` available voting power changes from `previousBalance` to `newBalance`. This MUST be emitted when: + - an account (that holds more than 0 assets) updates its delegation from or to `delegate`, - an asset transfer from or to an account that is delegated to `delegate`. @@ -398,4 +402,5 @@ Signed `Delegation` messages are censorable. The relaying party can always choos If the `DOMAIN_SEPARATOR` contains the `chainId` and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split. ## Copyright + Copyright and related rights waived via [CC0](../LICENSE.md).