├── .env.example ├── .gitignore ├── CURVE-VOTING-SMART-CONTRACT Certificate.pdf ├── README.md ├── SPECS.md ├── contracts ├── .npmignore ├── Voting.sol └── test │ ├── ExecutionTarget.sol │ └── TestImports.sol ├── hardhat.config.ts ├── helpers └── rpc.ts ├── manifest.json ├── out └── Voting_flat.sol ├── package.json ├── test ├── helpers │ ├── acl.ts │ ├── addresses.ts │ ├── apps.ts │ ├── dao.ts │ ├── events.ts │ ├── evmscript.ts │ ├── index.ts │ ├── numbers.ts │ └── time.ts └── voting.test.ts ├── tsconfig.json ├── utils └── network.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | # network specific node uri : `"ETH_NODE_URI_" + networkName.toUpperCase()` 2 | ETH_NODE_URI_MAINNET=https://mainnet.eth.aragon.network 3 | ETH_NODE_URI_RINKEBY=https://rinkeby.eth.aragon.network 4 | 5 | # generic node uri (if no specific found) : 6 | ETH_NODE_URI=https://{{networkName}}.infura.io/v3/ 7 | 8 | # network specific mnemonic : `"MNEMONIC_ " + networkName.toUpperCase()` 9 | MNEMONIC_MAINNET= 10 | # generic mnemonic (if no specific found): 11 | MNEMONIC= 12 | 13 | # pinata pinning service keys 14 | PINATA_KEY= 15 | PINATA_SECRET_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # coverage 5 | /coverage 6 | /coverageEnv 7 | /coverage.json 8 | 9 | # build files 10 | dist 11 | cache 12 | artifacts 13 | typechain 14 | 15 | # yarn 16 | yarn-error.log 17 | 18 | # Ignore package-lock files (only use yarn.lock) 19 | package-lock.json 20 | 21 | # Misc 22 | .DS_Store 23 | .cache 24 | .env -------------------------------------------------------------------------------- /CURVE-VOTING-SMART-CONTRACT Certificate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curvefi/curve-aragon-voting/141d3470edad98603eb43bfca70127571729330a/CURVE-VOTING-SMART-CONTRACT Certificate.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Curve Voting(Aragon Voting fork) 2 | 3 | [Voting.sol](https://github.com/curvefi/curve-aragon-voting/blob/master/contracts/Voting.sol) 4 | [SPECS](https://github.com/curvefi/curve-aragon-voting/blob/master/SPECS.md) 5 | 6 | [Audit](https://github.com/curvefi/curve-aragon-voting/blob/master/CURVE-VOTING-SMART-CONTRACT%20Certificate.pdf) 7 | 8 | ## Description 9 | 10 | Curve Voting app is a fork of Aragon Voting App with the following additions: 11 | 12 | * Added minimum balance `minBalance` a user must have in order to create a vote 13 | 14 | * Added minimum time between one user creating a new vote 15 | 16 | * Removed possibility to change votes 17 | 18 | * Vote's time must end before a vote can be enacted 19 | 20 | * There's a switch to enable/disable creating votes controlled by roles `ENABLE_VOTE_CREATION` and `DISABLE_VOTE_CREATION`. 21 | 22 | Once testing votes is done, the `DISABLE_VOTE_CREATION` role should be set to `0x0000000000000000000000000000000000000000` - noone 23 | 24 | Voting app will have `CREATE_VOTES_ROLE` permissions set to anyone(`0xffffffffffffffffffffffffffffffffffffffff`) because the balance check will be done inside the Voting app and not through a forwarder 25 | 26 | To incentivize people to vote early and not wait and see what's the outcome of the vote and vote near the end of vote, 27 | the voting power they had for the snapshot of for that vote is getting smaller proportional to the time left for that vote(with a cutoff period of `voteTime` / 2 between creation of vote and when decrease starts to happen) 28 | 29 | To prevent DAO to set unrealistic limits for `minBalance` and `minTime`, `minBalance` is required to be the equivalent of at least `10000` tokens locked for `one year` (1e18 precision) (which equals to `2500000000000000000000`) 30 | and `minTime` is required to be between half a day and 2 weeks 31 | 32 | Those limits are set when initializing the app 33 | 34 | 35 | # How to deploy and initialize the app 36 | 37 | ## Edit [arapp.json](./arapp.json) specifying the app name `appName`, which you'll then use to install the app 38 | 39 | ## Deploying the app to aragonPM 40 | 41 | [Install Aragon CLI](https://github.com/aragon/aragon-cli) 42 | Preferably, for easier usage, install [frame.sh](https://frame.sh/) 43 | 44 | Run `aragon apm publish major --environment mainnet --use-frame` 45 | 46 | ## Installing the app in a DAO 47 | 48 | `dao install $DAO_ADDRESS $APP_ADDRESS --app-init-args $VOTING_ESCROW $ACCEPTANCE_PCT $QUORUM_PCT $VOTE_TIME $MIN_BALANCE $MIN_TIME $MIN_BALANCE_LOWER_LIMIT $MIN_BALANCE_UPPER_LIMIT $MIN_TIME_LOWER_LIMIT $MIN_TIME_UPPER_LIMIT --use-frame --env aragon:mainnet` 49 | 50 | where 51 | 52 | `$DAO_ADDRESS` = Address of DAO to install the app to 53 | 54 | `$APP_ADDRESS` = the `appName` you specified in `arapp.json` 55 | 56 | `$VOTING_ESCROW` - The VotingEscrow address deployed from [Curve DAO contracts deployment guide](https://github.com/curvefi/curve-dao-contracts/blob/master/scripts/README.md) 57 | 58 | `$ACCEPTANCE_PCT` = Percentage of yeas in casted votes for a vote to succeed (expressed as a percentage of 10^18; eg. 10^16 = 1%, 10^18 = 100%) 59 | 60 | i.e 510000000000000000 = 51% 61 | 62 | `$QUORUM_PCT` = Percentage of yeas in total possible votes for a vote to succeed (expressed as a percentage of 10^18; eg. 10^16 = 1%, 10^18 = 100%) 63 | 64 | 65 | `$VOTE_TIME` = Seconds that a vote will be open for token holders to vote (unless enough yeas or nays have been cast to make an early decision) 66 | 67 | `MIN_TIME` = The minumum time that has to pass between a user's last creating of vote and him creating new vote 68 | 69 | `$MIN_BALANCE` = The minimum balance a user can have to create a new vote - `2500` means `2500e18` 70 | 71 | `$MIN_BALANCE_LOWER_LIMIT` = The lowest `MIN_BALANCE` can be set to 72 | 73 | `$MIN_BALANCE_UPPER_LIMIT` = The highest `MIN_BALANCE` can be set to 74 | 75 | `$MIN_TIME_LOWER_LIMIT` = The lowest `MIN_TIME` can be set to 76 | 77 | `$MIN_TIME_UPPER_LIMIT` = The highest `MIN_TIME` can be set to 78 | 79 | 80 | ## Roles added in addition to standard Voting App roles 81 | 82 | `SET_MIN_BALANCE_ROLE` = Role to set `MIN_BALANCE`, should be set to and managed by Voting app itself 83 | `SET_MIN_TIME_ROLE` = Role to set `MIN_TIME`, should be set to 84 | `ENABLE_VOTE_CREATION` = Role to enable vote creation 85 | `DISABLE_VOTE_CREATION` = Role to disable vote creation, should be set to and managed by `0x0000000000000000000000000000000000000000` to give up control to DAO 86 | -------------------------------------------------------------------------------- /SPECS.md: -------------------------------------------------------------------------------- 1 | ## Voting fork SPECS 2 | 3 | Allow creating a new vote only when user has at least `minBalance` and 4 | at least `minTime` has passed between user's last vote and time now when they want to create a new vote 5 | 6 | * Min balance 7 | Should allow a token holder to create a new Aragon Vote 8 | given that the token holder has the required `minBalance` voting power 9 | 10 | The voting power of a token holder is calculated by `tokens locked` * `lock time` 11 | as specified in [Curve DAO Paper](https://github.com/curvefi/curve-dao-contracts/blob/master/doc/readme.pdf) 12 | * Min time 13 | Should allow a token holder to create a new vote only if there is at least `minTime` passed between their last vote and time now when they want to create a new vote 14 | 15 | To prevent DAO to set unrealistic limits for `minBalance` and `minTime`, `minBalance` is required to be the equivalent of at least `10000` tokens locked for `one year` (1e18 precision) (which equals to `2500000000000000000000`) 16 | and `minTime` is required to be between half a day and 2 weeks 17 | 18 | Changing votes is not allowed. 19 | 20 | To incentivize people to vote early and not wait and see what's the outcome of the vote and vote near the end of vote, 21 | the voting power they had for the snapshot of for that vote is getting smaller proportional to the time left for that vote(with a cutoff period of `voteTime` / 2 between creation of vote and when decrease starts to happen) 22 | 23 | Voting app will have `CREATE_VOTES_ROLE` permissions set to anyone(`0xffffffffffffffffffffffffffffffffffffffff`) because the balance check will be done inside the Voting app and not through a forwarder -------------------------------------------------------------------------------- /contracts/.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | -------------------------------------------------------------------------------- /contracts/Voting.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identitifer: GPL-3.0-or-later 3 | */ 4 | 5 | pragma solidity 0.4.24; 6 | 7 | import "@aragon/os/contracts/apps/AragonApp.sol"; 8 | import "@aragon/os/contracts/common/IForwarder.sol"; 9 | 10 | import "@aragon/os/contracts/lib/math/SafeMath.sol"; 11 | import "@aragon/os/contracts/lib/math/SafeMath64.sol"; 12 | 13 | import "@aragon/apps-shared-minime/contracts/MiniMeToken.sol"; 14 | 15 | contract Voting is IForwarder, AragonApp { 16 | using SafeMath for uint256; 17 | using SafeMath64 for uint64; 18 | 19 | uint128 private constant MAX_UINT_128 = 2 ** 128 - 1; 20 | uint128 private constant MAX_UINT_64 = 2 ** 64 - 1; 21 | 22 | bytes32 public constant CREATE_VOTES_ROLE = 0xe7dcd7275292e064d090fbc5f3bd7995be23b502c1fed5cd94cfddbbdcd32bbc; //keccak256("CREATE_VOTES_ROLE"); 23 | bytes32 public constant MODIFY_SUPPORT_ROLE = 0xda3972983e62bdf826c4b807c4c9c2b8a941e1f83dfa76d53d6aeac11e1be650; //keccak256("MODIFY_SUPPORT_ROLE"); 24 | bytes32 public constant MODIFY_QUORUM_ROLE = 0xad15e7261800b4bb73f1b69d3864565ffb1fd00cb93cf14fe48da8f1f2149f39; //keccak256("MODIFY_QUORUM_ROLE"); 25 | 26 | bytes32 public constant SET_MIN_BALANCE_ROLE = 0xb1f3f26f63ad27cd630737a426f990492f5c674208299d6fb23bb2b0733d3d66; //keccak256("SET_MIN_BALANCE_ROLE") 27 | bytes32 public constant SET_MIN_TIME_ROLE = 0xe7ab0252519cd959720b328191bed7fe61b8e25f77613877be7070646d12daf0; //keccak256("SET_MIN_TIME_ROLE") 28 | 29 | bytes32 public constant ENABLE_VOTE_CREATION = 0xecb50dc3e77ba8a59697a3cc090a29b4cbd3c1f2b6b3aea524e0d166969592b9; //keccak256("ENABLE_VOTE_CREATION") 30 | 31 | bytes32 public constant DISABLE_VOTE_CREATION = 0x40b01f8b31b51596de2eeab8c325ff77cc3695c1c1875d66ff31176e7148d2a1; //keccack256("DISABLE_VOTE_CREATION") 32 | 33 | uint64 public constant PCT_BASE = 10 ** 18; // 0% = 0; 1% = 10^16; 100% = 10^18 34 | 35 | string private constant ERROR_NO_VOTE = "VOTING_NO_VOTE"; 36 | string private constant ERROR_INIT_PCTS = "VOTING_INIT_PCTS"; 37 | string private constant ERROR_CHANGE_SUPPORT_PCTS = "VOTING_CHANGE_SUPPORT_PCTS"; 38 | string private constant ERROR_CHANGE_QUORUM_PCTS = "VOTING_CHANGE_QUORUM_PCTS"; 39 | string private constant ERROR_INIT_SUPPORT_TOO_BIG = "VOTING_INIT_SUPPORT_TOO_BIG"; 40 | string private constant ERROR_CHANGE_SUPPORT_TOO_BIG = "VOTING_CHANGE_SUPP_TOO_BIG"; 41 | string private constant ERROR_CAN_NOT_VOTE = "VOTING_CAN_NOT_VOTE"; 42 | string private constant ERROR_MALFORMED_CONTINUOUS_VOTE = "MALFORMED_CONTINUOUS_VOTE"; 43 | string private constant ERROR_CAN_NOT_EXECUTE = "VOTING_CAN_NOT_EXECUTE"; 44 | string private constant ERROR_CAN_NOT_FORWARD = "VOTING_CAN_NOT_FORWARD"; 45 | string private constant ERROR_NO_VOTING_POWER = "VOTING_NO_VOTING_POWER"; 46 | 47 | enum VoterState { Absent, Yea, Nay, Even } 48 | 49 | struct Vote { 50 | bool executed; 51 | uint64 startDate; 52 | uint64 snapshotBlock; 53 | uint64 supportRequiredPct; 54 | uint64 minAcceptQuorumPct; 55 | uint256 yea; 56 | uint256 nay; 57 | uint256 votingPower; 58 | bytes executionScript; 59 | mapping (address => VoterState) voters; 60 | } 61 | 62 | MiniMeToken public token; 63 | uint64 public supportRequiredPct; 64 | uint64 public minAcceptQuorumPct; 65 | uint64 public voteTime; 66 | 67 | //2500000000000000000000 68 | uint256 public minBalanceLowerLimit; 69 | uint256 public minBalanceUpperLimit; 70 | //43200 71 | uint256 public minTimeLowerLimit; 72 | //1209600 73 | uint256 public minTimeUpperLimit; 74 | 75 | uint256 public minBalance; 76 | uint256 public minTime; 77 | 78 | bool public enableVoteCreation; 79 | 80 | // We are mimicing an array, we use a mapping instead to make app upgrade more graceful 81 | mapping (uint256 => Vote) internal votes; 82 | uint256 public votesLength; 83 | 84 | mapping(address => uint256) public lastCreateVoteTimes; 85 | 86 | event StartVote(uint256 indexed voteId, address indexed creator, string metadata, uint256 minBalance, uint256 minTime, uint256 totalSupply, uint256 creatorVotingPower); 87 | event CastVote(uint256 indexed voteId, address indexed voter, bool supports, uint256 stake); 88 | event ExecuteVote(uint256 indexed voteId); 89 | event ChangeSupportRequired(uint64 supportRequiredPct); 90 | event ChangeMinQuorum(uint64 minAcceptQuorumPct); 91 | 92 | event MinimumBalanceSet(uint256 minBalance); 93 | event MinimumTimeSet(uint256 minTime); 94 | 95 | modifier voteExists(uint256 _voteId) { 96 | require(_voteId < votesLength, ERROR_NO_VOTE); 97 | _; 98 | } 99 | 100 | modifier minBalanceCheck(uint256 _minBalance) { 101 | //_minBalance to be at least the equivalent of 10k locked for a year (1e18 precision) 102 | require(_minBalance >= minBalanceLowerLimit && _minBalance <= minBalanceUpperLimit, "Min balance should be within initialization hardcoded limits"); 103 | _; 104 | } 105 | 106 | modifier minTimeCheck(uint256 _minTime) { 107 | require(_minTime >= minTimeLowerLimit && _minTime <= minTimeUpperLimit, "Min time should be within initialization hardcoded limits"); 108 | _; 109 | } 110 | 111 | /** 112 | * @notice Initialize Voting app with `_token.symbol(): string` for governance, minimum support of `@formatPct(_supportRequiredPct)`%, minimum acceptance quorum of `@formatPct(_minAcceptQuorumPct)`%, and a voting duration of `@transformTime(_voteTime)` 113 | * @param _token MiniMeToken Address that will be used as governance token 114 | * @param _supportRequiredPct Percentage of yeas in casted votes for a vote to succeed (expressed as a percentage of 10^18; eg. 10^16 = 1%, 10^18 = 100%) 115 | * @param _minAcceptQuorumPct Percentage of yeas in total possible votes for a vote to succeed (expressed as a percentage of 10^18; eg. 10^16 = 1%, 10^18 = 100%) 116 | * @param _voteTime Seconds that a vote will be open for token holders to vote (unless enough yeas or nays have been cast to make an early decision) 117 | * @param _minBalance Minumum balance that a token holder should have to create a new vote 118 | * @param _minTime Minimum time between a user's previous vote and creating a new vote 119 | * @param _minBalanceLowerLimit Hardcoded lower limit for _minBalance on initialization 120 | * @param _minTimeLowerLimit Hardcoded lower limit for _minTime on initialization 121 | * @param _minTimeUpperLimit Hardcoded upper limit for _minTime on initialization 122 | */ 123 | function initialize(MiniMeToken _token, 124 | uint64 _supportRequiredPct, 125 | uint64 _minAcceptQuorumPct, 126 | uint64 _voteTime, 127 | uint256 _minBalance, 128 | uint256 _minTime, 129 | uint256 _minBalanceLowerLimit, 130 | uint256 _minBalanceUpperLimit, 131 | uint256 _minTimeLowerLimit, 132 | uint256 _minTimeUpperLimit 133 | ) external onlyInit { 134 | assert(CREATE_VOTES_ROLE == keccak256("CREATE_VOTES_ROLE")); 135 | assert(MODIFY_SUPPORT_ROLE == keccak256("MODIFY_SUPPORT_ROLE")); 136 | assert(MODIFY_QUORUM_ROLE == keccak256("MODIFY_QUORUM_ROLE")); 137 | assert(SET_MIN_BALANCE_ROLE == keccak256("SET_MIN_BALANCE_ROLE")); 138 | assert(SET_MIN_TIME_ROLE == keccak256("SET_MIN_TIME_ROLE")); 139 | assert(DISABLE_VOTE_CREATION == keccak256("DISABLE_VOTE_CREATION")); 140 | assert(ENABLE_VOTE_CREATION == keccak256("ENABLE_VOTE_CREATION")); 141 | 142 | initialized(); 143 | 144 | require(_minAcceptQuorumPct <= _supportRequiredPct, ERROR_INIT_PCTS); 145 | require(_supportRequiredPct < PCT_BASE, ERROR_INIT_SUPPORT_TOO_BIG); 146 | 147 | require(_minBalance >= _minBalanceLowerLimit && _minBalance <= _minBalanceUpperLimit); 148 | require(_minTime >= _minTimeLowerLimit && _minTime <= _minTimeUpperLimit); 149 | 150 | token = _token; 151 | supportRequiredPct = _supportRequiredPct; 152 | minAcceptQuorumPct = _minAcceptQuorumPct; 153 | voteTime = _voteTime; 154 | 155 | uint256 decimalsMul = uint256(10) ** token.decimals(); 156 | 157 | minBalance = _minBalance.mul(decimalsMul); 158 | minTime = _minTime; 159 | 160 | minBalanceLowerLimit = _minBalanceLowerLimit.mul(decimalsMul); 161 | minBalanceUpperLimit = _minBalanceUpperLimit.mul(decimalsMul); 162 | minTimeLowerLimit = _minTimeLowerLimit; 163 | minTimeUpperLimit = _minTimeUpperLimit; 164 | 165 | emit MinimumBalanceSet(minBalance); 166 | emit MinimumTimeSet(minTime); 167 | 168 | enableVoteCreation = true; 169 | } 170 | 171 | /** 172 | * @notice Change required support to `@formatPct(_supportRequiredPct)`% 173 | * @param _supportRequiredPct New required support 174 | */ 175 | function changeSupportRequiredPct(uint64 _supportRequiredPct) 176 | external 177 | authP(MODIFY_SUPPORT_ROLE, arr(uint256(_supportRequiredPct), uint256(supportRequiredPct))) 178 | { 179 | require(minAcceptQuorumPct <= _supportRequiredPct, ERROR_CHANGE_SUPPORT_PCTS); 180 | require(_supportRequiredPct < PCT_BASE, ERROR_CHANGE_SUPPORT_TOO_BIG); 181 | supportRequiredPct = _supportRequiredPct; 182 | 183 | emit ChangeSupportRequired(_supportRequiredPct); 184 | } 185 | 186 | /** 187 | * @notice Change minimum acceptance quorum to `@formatPct(_minAcceptQuorumPct)`% 188 | * @param _minAcceptQuorumPct New acceptance quorum 189 | */ 190 | function changeMinAcceptQuorumPct(uint64 _minAcceptQuorumPct) 191 | external 192 | authP(MODIFY_QUORUM_ROLE, arr(uint256(_minAcceptQuorumPct), uint256(minAcceptQuorumPct))) 193 | { 194 | require(_minAcceptQuorumPct <= supportRequiredPct, ERROR_CHANGE_QUORUM_PCTS); 195 | minAcceptQuorumPct = _minAcceptQuorumPct; 196 | 197 | emit ChangeMinQuorum(_minAcceptQuorumPct); 198 | } 199 | 200 | /** 201 | * @notice Change minimum balance needed to create a vote to `_minBalance` 202 | * @param _minBalance New minimum balance 203 | */ 204 | 205 | function setMinBalance(uint256 _minBalance) external auth(SET_MIN_BALANCE_ROLE) minBalanceCheck(_minBalance) { 206 | //min balance can't be set to lower than 10k * 1 year 207 | minBalance = _minBalance; 208 | 209 | emit MinimumBalanceSet(_minBalance); 210 | } 211 | 212 | /** 213 | * @notice Change minimum time needed to pass between user's previous vote and a user creating a new vote 214 | * @param _minTime New minumum time 215 | */ 216 | 217 | function setMinTime(uint256 _minTime) external auth(SET_MIN_TIME_ROLE) minTimeCheck(_minTime) { 218 | //min time should be within initialized hardcoded limits 219 | minTime = _minTime; 220 | 221 | emit MinimumTimeSet(_minTime); 222 | } 223 | 224 | //later role will be set to 0x0 - noone 225 | function disableVoteCreationOnce() external auth(DISABLE_VOTE_CREATION) { 226 | enableVoteCreation = false; 227 | } 228 | 229 | function enableVoteCreationOnce() external auth(ENABLE_VOTE_CREATION) { 230 | enableVoteCreation = true; 231 | } 232 | 233 | /** 234 | * @notice Create a new vote about "`_metadata`" 235 | * @param _executionScript EVM script to be executed on approval 236 | * @param _metadata Vote metadata 237 | * @return voteId Id for newly created vote 238 | */ 239 | function newVote(bytes _executionScript, string _metadata) external auth(CREATE_VOTES_ROLE) returns (uint256 voteId) { 240 | return _newVote(_executionScript, _metadata, true, true); 241 | } 242 | 243 | /** 244 | * @notice Create a new vote about "`_metadata`" 245 | * @param _executionScript EVM script to be executed on approval 246 | * @param _metadata Vote metadata 247 | * @param _castVote Whether to also cast newly created vote 248 | * @param _executesIfDecided Whether to also immediately execute newly created vote if decided 249 | * @return voteId id for newly created vote 250 | */ 251 | function newVote(bytes _executionScript, string _metadata, bool _castVote, bool _executesIfDecided) 252 | external 253 | auth(CREATE_VOTES_ROLE) 254 | returns (uint256 voteId) 255 | { 256 | return _newVote(_executionScript, _metadata, _castVote, _executesIfDecided); 257 | } 258 | 259 | /** 260 | * @notice Vote a percentage value in favor of a vote 261 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 262 | * created via `newVote(),` which requires initialization 263 | * @param _voteData Packed vote data containing both voteId and the vote in favor percentage (where 0 is no, and 1e18 is yes) 264 | * Vote data packing 265 | * | yeaPct | nayPct | voteId | 266 | * | 64b | 64b | 128b | 267 | * @param _supports Whether voter supports the vote (preserved for backward compatibility purposes) 268 | * @param _executesIfDecided Whether the vote should execute its action if it becomes decided 269 | */ 270 | function vote(uint256 _voteData, bool _supports, bool _executesIfDecided) external voteExists(_decodeData(_voteData, 0, MAX_UINT_128)) { 271 | uint256 voteId = _decodeData(_voteData, 0, MAX_UINT_128); 272 | uint256 nayPct = _decodeData(_voteData, 128, MAX_UINT_64); 273 | uint256 yeaPct = _decodeData(_voteData, 192, MAX_UINT_64); 274 | 275 | require(_canVote(voteId, msg.sender), ERROR_CAN_NOT_VOTE); 276 | 277 | if (yeaPct == 0 && nayPct == 0) { 278 | // Keep backwards compatibility 279 | if (_supports) { 280 | yeaPct = PCT_BASE; 281 | } else { 282 | nayPct = PCT_BASE; 283 | } 284 | } else { 285 | require(!_supports && yeaPct.add(nayPct) <= PCT_BASE, ERROR_MALFORMED_CONTINUOUS_VOTE); 286 | } 287 | _vote(voteId, yeaPct, nayPct, msg.sender, _executesIfDecided); 288 | } 289 | 290 | /** 291 | * @notice Vote `@formatPct(_yeaPct)`% in favor and `@formatPct(_nayPct)`% against of vote #`_voteId` 292 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 293 | * created via `newVote(),` which requires initialization 294 | * @param _voteId Id for vote 295 | * @param _yeaPct Percentage of support, where 0 is no support, and 1e18 is total support 296 | * @param _nayPct Percentage of opposition, where 0 is no oposition, and 1e18 is total oposition 297 | * @param _executesIfDecided Whether the vote should execute its action if it becomes decided 298 | */ 299 | function votePct(uint256 _voteId, uint256 _yeaPct, uint256 _nayPct, bool _executesIfDecided) external voteExists(_voteId) { 300 | require(_canVote(_voteId, msg.sender), ERROR_CAN_NOT_VOTE); 301 | require(_yeaPct.add(_nayPct) <= PCT_BASE, ERROR_MALFORMED_CONTINUOUS_VOTE); 302 | _vote(_voteId, _yeaPct, _nayPct, msg.sender, _executesIfDecided); 303 | } 304 | 305 | /** 306 | * @notice Execute vote #`_voteId` 307 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 308 | * created via `newVote(),` which requires initialization 309 | * @param _voteId Id for vote 310 | */ 311 | function executeVote(uint256 _voteId) external voteExists(_voteId) { 312 | _executeVote(_voteId); 313 | } 314 | 315 | // Forwarding fns 316 | 317 | /** 318 | * @notice Tells whether the Voting app is a forwarder or not 319 | * @dev IForwarder interface conformance 320 | * @return Always true 321 | */ 322 | function isForwarder() external pure returns (bool) { 323 | return true; 324 | } 325 | 326 | /** 327 | * @notice Creates a vote to execute the desired action, and casts a support vote if possible 328 | * @dev IForwarder interface conformance 329 | * @param _evmScript Start vote with script 330 | */ 331 | function forward(bytes _evmScript) public { 332 | require(canForward(msg.sender, _evmScript), ERROR_CAN_NOT_FORWARD); 333 | _newVote(_evmScript, "", true, true); 334 | } 335 | 336 | /** 337 | * @notice Tells whether `_sender` can forward actions or not 338 | * @dev IForwarder interface conformance 339 | * @param _sender Address of the account intending to forward an action 340 | * @return True if the given address can create votes, false otherwise 341 | */ 342 | function canForward(address _sender, bytes) public view returns (bool) { 343 | // Note that `canPerform()` implicitly does an initialization check itself 344 | return canPerform(_sender, CREATE_VOTES_ROLE, arr()) && canCreateNewVote(_sender); 345 | } 346 | 347 | // Getter fns 348 | 349 | /** 350 | * @notice Tells whether a vote #`_voteId` can be executed or not 351 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 352 | * created via `newVote(),` which requires initialization 353 | * @return True if the given vote can be executed, false otherwise 354 | */ 355 | function canExecute(uint256 _voteId) public view voteExists(_voteId) returns (bool) { 356 | return _canExecute(_voteId); 357 | } 358 | 359 | /** 360 | * @notice Tells whether `_sender` can participate in the vote #`_voteId` or not 361 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 362 | * created via `newVote(),` which requires initialization 363 | * @return True if the given voter can participate a certain vote, false otherwise 364 | */ 365 | function canVote(uint256 _voteId, address _voter) public view voteExists(_voteId) returns (bool) { 366 | return _canVote(_voteId, _voter); 367 | } 368 | 369 | function canCreateNewVote(address _sender) public view returns(bool) { 370 | return enableVoteCreation && token.balanceOf(_sender) >= minBalance && block.timestamp.sub(minTime) >= lastCreateVoteTimes[_sender]; 371 | } 372 | 373 | /** 374 | * @dev Return all information for a vote by its ID 375 | * @param _voteId Vote identifier 376 | * @return Vote open status 377 | * @return Vote executed status 378 | * @return Vote start date 379 | * @return Vote snapshot block 380 | * @return Vote support required 381 | * @return Vote minimum acceptance quorum 382 | * @return Vote yeas amount 383 | * @return Vote nays amount 384 | * @return Vote power 385 | * @return Vote script 386 | */ 387 | function getVote(uint256 _voteId) 388 | public 389 | view 390 | voteExists(_voteId) 391 | returns ( 392 | bool open, 393 | bool executed, 394 | uint64 startDate, 395 | uint64 snapshotBlock, 396 | uint64 supportRequired, 397 | uint64 minAcceptQuorum, 398 | uint256 yea, 399 | uint256 nay, 400 | uint256 votingPower, 401 | bytes script 402 | ) 403 | { 404 | Vote storage vote_ = votes[_voteId]; 405 | 406 | open = _isVoteOpen(vote_); 407 | executed = vote_.executed; 408 | startDate = vote_.startDate; 409 | snapshotBlock = vote_.snapshotBlock; 410 | supportRequired = vote_.supportRequiredPct; 411 | minAcceptQuorum = vote_.minAcceptQuorumPct; 412 | yea = vote_.yea; 413 | nay = vote_.nay; 414 | votingPower = vote_.votingPower; 415 | script = vote_.executionScript; 416 | } 417 | 418 | /** 419 | * @dev Return the state of a voter for a given vote by its ID 420 | * @param _voteId Vote identifier 421 | * @return VoterState of the requested voter for a certain vote 422 | */ 423 | function getVoterState(uint256 _voteId, address _voter) public view voteExists(_voteId) returns (VoterState) { 424 | return votes[_voteId].voters[_voter]; 425 | } 426 | 427 | // Internal fns 428 | 429 | /** 430 | * @dev Internal function to create a new vote 431 | * @return voteId id for newly created vote 432 | */ 433 | function _newVote(bytes _executionScript, string _metadata, bool _castVote, bool _executesIfDecided) internal returns (uint256 voteId) { 434 | require(canCreateNewVote(msg.sender)); 435 | uint64 snapshotBlock = getBlockNumber64() - 1; // avoid double voting in this very block 436 | uint256 votingPower = token.totalSupplyAt(snapshotBlock); 437 | require(votingPower > 0, ERROR_NO_VOTING_POWER); 438 | 439 | voteId = votesLength++; 440 | 441 | Vote storage vote_ = votes[voteId]; 442 | vote_.startDate = getTimestamp64(); 443 | vote_.snapshotBlock = snapshotBlock; 444 | vote_.supportRequiredPct = supportRequiredPct; 445 | vote_.minAcceptQuorumPct = minAcceptQuorumPct; 446 | vote_.votingPower = votingPower; 447 | vote_.executionScript = _executionScript; 448 | 449 | emit StartVote(voteId, msg.sender, _metadata, minBalance, minTime, token.totalSupply(), token.balanceOfAt(msg.sender, snapshotBlock)); 450 | 451 | lastCreateVoteTimes[msg.sender] = getTimestamp64(); 452 | 453 | if (_castVote && _canVote(voteId, msg.sender)) { 454 | _vote(voteId, PCT_BASE, 0, msg.sender, _executesIfDecided); 455 | } 456 | } 457 | 458 | /** 459 | * @dev Internal function to cast a vote. It assumes the queried vote exists. 460 | */ 461 | function _vote(uint256 _voteId, uint256 _yeaPct, uint256 _nayPct, address _voter, bool _executesIfDecided) internal { 462 | Vote storage vote_ = votes[_voteId]; 463 | 464 | VoterState state = vote_.voters[_voter]; 465 | require(state == VoterState.Absent, "Can't change votes"); 466 | // This could re-enter, though we can assume the governance token is not malicious 467 | uint256 balance = token.balanceOfAt(_voter, vote_.snapshotBlock); 468 | uint256 voterStake = uint256(2).mul(balance).mul(vote_.startDate.add(voteTime).sub(getTimestamp64())).div(voteTime); 469 | if(voterStake > balance) { 470 | voterStake = balance; 471 | } 472 | 473 | uint256 yea = voterStake.mul(_yeaPct).div(PCT_BASE); 474 | uint256 nay = voterStake.mul(_nayPct).div(PCT_BASE); 475 | 476 | if (yea > 0) { 477 | vote_.yea = vote_.yea.add(yea); 478 | } 479 | if (nay > 0) { 480 | vote_.nay = vote_.nay.add(nay); 481 | } 482 | 483 | vote_.voters[_voter] = yea == nay ? VoterState.Even : yea > nay ? VoterState.Yea : VoterState.Nay; 484 | 485 | if (yea > 0) { 486 | emit CastVote(_voteId, _voter, true, yea); 487 | } 488 | if (nay > 0) { 489 | emit CastVote(_voteId, _voter, false, nay); 490 | } 491 | 492 | if (_executesIfDecided && _canExecute(_voteId)) { 493 | // We've already checked if the vote can be executed with `_canExecute()` 494 | _unsafeExecuteVote(_voteId); 495 | } 496 | } 497 | 498 | /** 499 | * @dev Internal function to execute a vote. It assumes the queried vote exists. 500 | */ 501 | function _executeVote(uint256 _voteId) internal { 502 | require(_canExecute(_voteId), ERROR_CAN_NOT_EXECUTE); 503 | _unsafeExecuteVote(_voteId); 504 | } 505 | 506 | /** 507 | * @dev Unsafe version of _executeVote that assumes you have already checked if the vote can be executed and exists 508 | */ 509 | function _unsafeExecuteVote(uint256 _voteId) internal { 510 | Vote storage vote_ = votes[_voteId]; 511 | 512 | vote_.executed = true; 513 | 514 | bytes memory input = new bytes(0); // TODO: Consider input for voting scripts 515 | runScript(vote_.executionScript, input, new address[](0)); 516 | 517 | emit ExecuteVote(_voteId); 518 | } 519 | 520 | /** 521 | * @dev Internal function to check if a vote can be executed. It assumes the queried vote exists. 522 | * @return True if the given vote can be executed, false otherwise 523 | */ 524 | function _canExecute(uint256 _voteId) internal view returns (bool) { 525 | Vote storage vote_ = votes[_voteId]; 526 | 527 | if(_isVoteOpen(vote_)) { 528 | return false; 529 | } 530 | 531 | if (vote_.executed) { 532 | return false; 533 | } 534 | 535 | // Voting is already decided 536 | if (_isValuePct(vote_.yea, vote_.votingPower, vote_.supportRequiredPct)) { 537 | return true; 538 | } 539 | 540 | // Vote ended? 541 | if (_isVoteOpen(vote_)) { 542 | return false; 543 | } 544 | // Has enough support? 545 | uint256 totalVotes = vote_.yea.add(vote_.nay); 546 | if (!_isValuePct(vote_.yea, totalVotes, vote_.supportRequiredPct)) { 547 | return false; 548 | } 549 | // Has min quorum? 550 | if (!_isValuePct(vote_.yea, vote_.votingPower, vote_.minAcceptQuorumPct)) { 551 | return false; 552 | } 553 | 554 | return true; 555 | } 556 | 557 | /** 558 | * @dev Internal function to check if a voter can participate on a vote. It assumes the queried vote exists. 559 | * @return True if the given voter can participate a certain vote, false otherwise 560 | */ 561 | function _canVote(uint256 _voteId, address _voter) internal view returns (bool) { 562 | Vote storage vote_ = votes[_voteId]; 563 | return _isVoteOpen(vote_) && token.balanceOfAt(_voter, vote_.snapshotBlock) > 0; 564 | } 565 | 566 | /** 567 | * @dev Internal function to check if a vote is still open 568 | * @return True if the given vote is open, false otherwise 569 | */ 570 | function _isVoteOpen(Vote storage vote_) internal view returns (bool) { 571 | return getTimestamp64() < vote_.startDate.add(voteTime) && !vote_.executed; 572 | } 573 | 574 | /** 575 | * @dev Calculates whether `_value` is more than a percentage `_pct` of `_total` 576 | */ 577 | function _isValuePct(uint256 _value, uint256 _total, uint256 _pct) internal pure returns (bool) { 578 | if (_total == 0) { 579 | return false; 580 | } 581 | 582 | uint256 computedPct = _value.mul(PCT_BASE) / _total; 583 | return computedPct > _pct; 584 | } 585 | 586 | /** 587 | * @dev Decodes data by performing bitwise operations. 588 | * @param _value Value containing the data 589 | * @param _shiftValue Number of bits to shift to the right 590 | * @param _maskValue Number of bits to apply as a mask to the value 591 | */ 592 | function _decodeData(uint256 _value, uint256 _shiftValue, uint256 _maskValue) internal pure returns(uint256) { 593 | return uint256((_value >> _shiftValue) & _maskValue); 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /contracts/test/ExecutionTarget.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | contract ExecutionTarget { 5 | uint256 public counter; 6 | 7 | event TargetExecuted(uint256 counter); 8 | 9 | function execute() external { 10 | counter += 1; 11 | emit TargetExecuted(counter); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/test/TestImports.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "@aragon/os/contracts/acl/ACL.sol"; 4 | import "@aragon/os/contracts/kernel/Kernel.sol"; 5 | import "@aragon/os/contracts/factory/DAOFactory.sol"; 6 | import "@aragon/os/contracts/factory/EVMScriptRegistryFactory.sol"; 7 | 8 | // You might think this file is a bit odd, but let me explain. 9 | // We only use some contracts in our tests, which means Hardhat 10 | // will not compile it for us, because it is from an external 11 | // dependency. 12 | // 13 | // We are now left with three options: 14 | // - Copy/paste these contracts 15 | // - Or trick Hardhat by claiming we use it in a Solidity test 16 | // 17 | // You know which one I went for. 18 | 19 | 20 | contract TestImports { 21 | constructor() public { 22 | // solium-disable-previous-line no-empty-blocks 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { HardhatUserConfig } from "hardhat/types"; 3 | 4 | import "@1hive/hardhat-aragon"; 5 | import "@nomiclabs/hardhat-ethers"; 6 | import "@nomiclabs/hardhat-waffle"; 7 | import "@typechain/hardhat"; 8 | import "hardhat-deploy"; 9 | 10 | import { node_url, accounts } from "./utils/network"; 11 | 12 | // While waiting for hardhat PR: https://github.com/nomiclabs/hardhat/pull/1542 13 | if (process.env.HARDHAT_FORK) { 14 | process.env["HARDHAT_DEPLOY_FORK"] = process.env.HARDHAT_FORK; 15 | } 16 | 17 | const config: HardhatUserConfig = { 18 | solidity: { 19 | compilers: [ 20 | { 21 | version: "0.4.24", 22 | settings: { 23 | optimizer: { 24 | enabled: true, 25 | runs: 10000, 26 | }, 27 | }, 28 | }, 29 | ], 30 | }, 31 | aragon: { 32 | appEnsName: "crv-voting.open.aragonpm.eth", 33 | appContractName: "Voting", 34 | appRoles: [ 35 | { 36 | name: "Create new votes", 37 | id: "CREATE_VOTES_ROLE", 38 | params: [], 39 | }, 40 | { 41 | name: "Modify support", 42 | id: "MODIFY_SUPPORT_ROLE", 43 | params: ["New support", "Current support"], 44 | }, 45 | { 46 | name: "Modify quorum", 47 | id: "MODIFY_QUORUM_ROLE", 48 | params: ["New quorum", "Current quorum"], 49 | }, 50 | { 51 | name: "Set the minimum required balance", 52 | id: "SET_MIN_BALANCE_ROLE", 53 | params: [], 54 | }, 55 | { 56 | name: "Set the minimum required time between one user creating new votes", 57 | id: "SET_MIN_TIME_ROLE", 58 | params: [], 59 | }, 60 | { 61 | name: "Enable vote creation", 62 | id: "ENABLE_VOTE_CREATION", 63 | params: [], 64 | }, 65 | { 66 | name: "Disable vote creation", 67 | id: "DISABLE_VOTE_CREATION", 68 | params: [], 69 | }, 70 | ], 71 | }, 72 | networks: { 73 | hardhat: { 74 | // process.env.HARDHAT_FORK will specify the network that the fork is made from. 75 | // this line ensure the use of the corresponding accounts 76 | accounts: accounts(process.env.HARDHAT_FORK), 77 | forking: process.env.HARDHAT_FORK 78 | ? { 79 | url: node_url(process.env.HARDHAT_FORK), 80 | blockNumber: process.env.HARDHAT_FORK_NUMBER 81 | ? parseInt(process.env.HARDHAT_FORK_NUMBER) 82 | : undefined, 83 | } 84 | : undefined, 85 | }, 86 | localhost: { 87 | url: node_url("localhost"), 88 | accounts: accounts(), 89 | ensRegistry: "0x4E065c622d584Fbe5D9078C3081840155FA69581", 90 | }, 91 | mainnet: { 92 | url: node_url("mainnet"), 93 | accounts: accounts("mainnet"), 94 | ensRegistry: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" 95 | }, 96 | rinkeby: { 97 | url: node_url("rinkeby"), 98 | accounts: accounts("rinkeby"), 99 | ensRegistry: "0x98Df287B6C145399Aaa709692c8D308357bC085D", 100 | }, 101 | xdai: { 102 | url: node_url("xdai"), 103 | accounts: accounts("xdai"), 104 | ensRegistry: "0xaafca6b0c89521752e559650206d7c925fd0e530", 105 | }, 106 | polygon: { 107 | url: node_url("polygon"), 108 | accounts: accounts("polygon"), 109 | ensRegistry: "0x7EdE100965B1E870d726cD480dD41F2af1Ca0130", 110 | }, 111 | mumbai: { 112 | url: node_url("mumbai"), 113 | accounts: accounts("mumbai"), 114 | ensRegistry: "0xB1576a9bE5EC445368740161174f3Dd1034fF8be", 115 | }, 116 | arbitrum: { 117 | url: node_url("arbitrum"), 118 | accounts: accounts("arbitrum"), 119 | ensRegistry: "0xB1576a9bE5EC445368740161174f3Dd1034fF8be", 120 | }, 121 | arbtest: { 122 | url: node_url("arbtest"), 123 | accounts: accounts("arbtest"), 124 | ensRegistry: "0x73ddD4B38982aB515daCf43289B41706f9A39199", 125 | }, 126 | frame: { 127 | url: "http://localhost:1248", 128 | httpHeaders: { origin: "hardhat" }, 129 | timeout: 0, 130 | gas: 0, 131 | }, 132 | }, 133 | ipfs: { 134 | pinata: { 135 | key: process.env.PINATA_KEY || "", 136 | secret: process.env.PINATA_SECRET_KEY || "", 137 | }, 138 | }, 139 | typechain: { 140 | outDir: "typechain", 141 | target: "ethers-v5", 142 | }, 143 | mocha: { 144 | timeout: 0, 145 | }, 146 | external: process.env.HARDHAT_FORK 147 | ? { 148 | deployments: { 149 | // process.env.HARDHAT_FORK will specify the network that the fork is made from. 150 | // these lines allow it to fetch the deployments from the network being forked from both for node and deploy task 151 | hardhat: ["deployments/" + process.env.HARDHAT_FORK], 152 | localhost: ["deployments/" + process.env.HARDHAT_FORK], 153 | }, 154 | } 155 | : undefined, 156 | }; 157 | 158 | export default config; 159 | -------------------------------------------------------------------------------- /helpers/rpc.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, Signer } from "ethers"; 2 | import hre, { ethers } from "hardhat"; 3 | 4 | export const duration = { 5 | seconds: function (val) { 6 | return ethers.BigNumber.from(val); 7 | }, 8 | minutes: function (val) { 9 | return ethers.BigNumber.from(val).mul(this.seconds("60")); 10 | }, 11 | hours: function (val) { 12 | return ethers.BigNumber.from(val).mul(this.minutes("60")); 13 | }, 14 | days: function (val) { 15 | return ethers.BigNumber.from(val).mul(this.hours("24")); 16 | }, 17 | weeks: function (val) { 18 | return ethers.BigNumber.from(val).mul(this.days("7")); 19 | }, 20 | years: function (val) { 21 | return ethers.BigNumber.from(val).mul(this.days("365")); 22 | }, 23 | }; 24 | 25 | export const setBalance = async ( 26 | account: string, 27 | balance: string 28 | ): Promise => { 29 | await hre.network.provider.send("hardhat_setBalance", [account, balance]); 30 | }; 31 | 32 | export const impersonateAddress = async ( 33 | address: string, 34 | setInitialBalance = true 35 | ): Promise => { 36 | await hre.network.provider.request({ 37 | method: "hardhat_impersonateAccount", 38 | params: [address], 39 | }); 40 | 41 | const signer = await ethers.provider.getSigner(address); 42 | 43 | if (setInitialBalance) { 44 | await setBalance( 45 | address, 46 | ethers.utils.hexStripZeros(ethers.constants.WeiPerEther.toHexString()) 47 | ); 48 | } 49 | return signer; 50 | }; 51 | 52 | export const restoreSnapshot = async (id: string): Promise => { 53 | await hre.network.provider.request({ 54 | method: "evm_revert", 55 | params: [id], 56 | }); 57 | }; 58 | 59 | export const takeSnapshot = async (): Promise => { 60 | return (await hre.network.provider.request({ 61 | method: "evm_snapshot", 62 | params: [], 63 | })) as Promise; 64 | }; 65 | 66 | export const increase = async (duration: string | BigNumber) => { 67 | if (!ethers.BigNumber.isBigNumber(duration)) { 68 | duration = ethers.BigNumber.from(duration); 69 | } 70 | // Get rid of typescript errors 71 | duration = duration as BigNumber; 72 | 73 | if (duration.isNegative()) 74 | throw Error(`Cannot increase time by a negative amount (${duration})`); 75 | 76 | await hre.network.provider.request({ 77 | method: "evm_increaseTime", 78 | params: [duration.toNumber()], 79 | }); 80 | 81 | await hre.network.provider.request({ 82 | method: "evm_mine", 83 | }); 84 | }; 85 | 86 | export const setBytecode = async (address: string, bytecode: string) => { 87 | await hre.network.provider.send("hardhat_setCode", [address, bytecode]); 88 | }; 89 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Curve Aragon Voting fork", 3 | "author": "Curve fork of Aragon Association", 4 | "description": "Create and participate in votes with min balance check, min time between creating new votes check, disallowing changing votes and incentiviting early voting", 5 | "source_url": "https://github.com/pengiundev/curve-aragon-voting" 6 | } -------------------------------------------------------------------------------- /out/Voting_flat.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identitifer: GPL-3.0-or-later 3 | */ 4 | 5 | pragma solidity 0.4.24; 6 | 7 | 8 | library Uint256Helpers { 9 | uint256 private constant MAX_UINT64 = uint64(-1); 10 | 11 | string private constant ERROR_NUMBER_TOO_BIG = "UINT64_NUMBER_TOO_BIG"; 12 | 13 | function toUint64(uint256 a) internal pure returns (uint64) { 14 | require(a <= MAX_UINT64, ERROR_NUMBER_TOO_BIG); 15 | return uint64(a); 16 | } 17 | } 18 | /* 19 | * SPDX-License-Identifier: MIT 20 | */ 21 | 22 | 23 | 24 | 25 | interface IVaultRecoverable { 26 | event RecoverToVault(address indexed vault, address indexed token, uint256 amount); 27 | 28 | function transferToVault(address token) external; 29 | 30 | function allowRecoverability(address token) external view returns (bool); 31 | function getRecoveryVault() external view returns (address); 32 | } 33 | /* 34 | * SPDX-License-Identifier: MIT 35 | */ 36 | 37 | 38 | 39 | 40 | library UnstructuredStorage { 41 | function getStorageBool(bytes32 position) internal view returns (bool data) { 42 | assembly { data := sload(position) } 43 | } 44 | 45 | function getStorageAddress(bytes32 position) internal view returns (address data) { 46 | assembly { data := sload(position) } 47 | } 48 | 49 | function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { 50 | assembly { data := sload(position) } 51 | } 52 | 53 | function getStorageUint256(bytes32 position) internal view returns (uint256 data) { 54 | assembly { data := sload(position) } 55 | } 56 | 57 | function setStorageBool(bytes32 position, bool data) internal { 58 | assembly { sstore(position, data) } 59 | } 60 | 61 | function setStorageAddress(bytes32 position, address data) internal { 62 | assembly { sstore(position, data) } 63 | } 64 | 65 | function setStorageBytes32(bytes32 position, bytes32 data) internal { 66 | assembly { sstore(position, data) } 67 | } 68 | 69 | function setStorageUint256(bytes32 position, uint256 data) internal { 70 | assembly { sstore(position, data) } 71 | } 72 | } 73 | /* 74 | * SPDX-License-Identifier: MIT 75 | */ 76 | 77 | 78 | 79 | 80 | 81 | 82 | contract TimeHelpers { 83 | using Uint256Helpers for uint256; 84 | 85 | /** 86 | * @dev Returns the current block number. 87 | * Using a function rather than `block.number` allows us to easily mock the block number in 88 | * tests. 89 | */ 90 | function getBlockNumber() internal view returns (uint256) { 91 | return block.number; 92 | } 93 | 94 | /** 95 | * @dev Returns the current block number, converted to uint64. 96 | * Using a function rather than `block.number` allows us to easily mock the block number in 97 | * tests. 98 | */ 99 | function getBlockNumber64() internal view returns (uint64) { 100 | return getBlockNumber().toUint64(); 101 | } 102 | 103 | /** 104 | * @dev Returns the current timestamp. 105 | * Using a function rather than `block.timestamp` allows us to easily mock it in 106 | * tests. 107 | */ 108 | function getTimestamp() internal view returns (uint256) { 109 | return block.timestamp; // solium-disable-line security/no-block-members 110 | } 111 | 112 | /** 113 | * @dev Returns the current timestamp, converted to uint64. 114 | * Using a function rather than `block.timestamp` allows us to easily mock it in 115 | * tests. 116 | */ 117 | function getTimestamp64() internal view returns (uint64) { 118 | return getTimestamp().toUint64(); 119 | } 120 | } 121 | /* 122 | * SPDX-License-Identifier: MIT 123 | */ 124 | 125 | 126 | 127 | 128 | interface IACL { 129 | function initialize(address permissionsCreator) external; 130 | 131 | // TODO: this should be external 132 | // See https://github.com/ethereum/solidity/issues/4832 133 | function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); 134 | } 135 | // See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/a9f910d34f0ab33a1ae5e714f69f9596a02b4d91/contracts/token/ERC20/ERC20.sol 136 | 137 | 138 | 139 | 140 | /** 141 | * @title ERC20 interface 142 | * @dev see https://github.com/ethereum/EIPs/issues/20 143 | */ 144 | contract ERC20 { 145 | function totalSupply() public view returns (uint256); 146 | 147 | function balanceOf(address _who) public view returns (uint256); 148 | 149 | function allowance(address _owner, address _spender) 150 | public view returns (uint256); 151 | 152 | function transfer(address _to, uint256 _value) public returns (bool); 153 | 154 | function approve(address _spender, uint256 _value) 155 | public returns (bool); 156 | 157 | function transferFrom(address _from, address _to, uint256 _value) 158 | public returns (bool); 159 | 160 | event Transfer( 161 | address indexed from, 162 | address indexed to, 163 | uint256 value 164 | ); 165 | 166 | event Approval( 167 | address indexed owner, 168 | address indexed spender, 169 | uint256 value 170 | ); 171 | } 172 | /* 173 | * SPDX-License-Identifier: MIT 174 | */ 175 | 176 | 177 | 178 | 179 | interface IEVMScriptExecutor { 180 | function execScript(bytes script, bytes input, address[] blacklist) external returns (bytes); 181 | function executorType() external pure returns (bytes32); 182 | } 183 | /* 184 | * SPDX-License-Identifier: MIT 185 | */ 186 | 187 | 188 | 189 | 190 | contract KernelAppIds { 191 | /* Hardcoded constants to save gas 192 | bytes32 internal constant KERNEL_CORE_APP_ID = apmNamehash("kernel"); 193 | bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = apmNamehash("acl"); 194 | bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = apmNamehash("vault"); 195 | */ 196 | bytes32 internal constant KERNEL_CORE_APP_ID = 0x3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c; 197 | bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = 0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a; 198 | bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1; 199 | } 200 | 201 | 202 | contract KernelNamespaceConstants { 203 | /* Hardcoded constants to save gas 204 | bytes32 internal constant KERNEL_CORE_NAMESPACE = keccak256("core"); 205 | bytes32 internal constant KERNEL_APP_BASES_NAMESPACE = keccak256("base"); 206 | bytes32 internal constant KERNEL_APP_ADDR_NAMESPACE = keccak256("app"); 207 | */ 208 | bytes32 internal constant KERNEL_CORE_NAMESPACE = 0xc681a85306374a5ab27f0bbc385296a54bcd314a1948b6cf61c4ea1bc44bb9f8; 209 | bytes32 internal constant KERNEL_APP_BASES_NAMESPACE = 0xf1f3eb40f5bc1ad1344716ced8b8a0431d840b5783aea1fd01786bc26f35ac0f; 210 | bytes32 internal constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb; 211 | } 212 | /* 213 | * SPDX-License-Identifier: MIT 214 | */ 215 | 216 | 217 | 218 | 219 | 220 | 221 | contract EVMScriptRegistryConstants { 222 | /* Hardcoded constants to save gas 223 | bytes32 internal constant EVMSCRIPT_REGISTRY_APP_ID = apmNamehash("evmreg"); 224 | */ 225 | bytes32 internal constant EVMSCRIPT_REGISTRY_APP_ID = 0xddbcfd564f642ab5627cf68b9b7d374fb4f8a36e941a75d89c87998cef03bd61; 226 | } 227 | 228 | 229 | interface IEVMScriptRegistry { 230 | function addScriptExecutor(IEVMScriptExecutor executor) external returns (uint id); 231 | function disableScriptExecutor(uint256 executorId) external; 232 | 233 | // TODO: this should be external 234 | // See https://github.com/ethereum/solidity/issues/4832 235 | function getScriptExecutor(bytes script) public view returns (IEVMScriptExecutor); 236 | } 237 | // Inspired by AdEx (https://github.com/AdExNetwork/adex-protocol-eth/blob/b9df617829661a7518ee10f4cb6c4108659dd6d5/contracts/libs/SafeERC20.sol) 238 | // and 0x (https://github.com/0xProject/0x-monorepo/blob/737d1dc54d72872e24abce5a1dbe1b66d35fa21a/contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol#L143) 239 | 240 | 241 | 242 | 243 | 244 | 245 | library SafeERC20 { 246 | // Before 0.5, solidity has a mismatch between `address.transfer()` and `token.transfer()`: 247 | // https://github.com/ethereum/solidity/issues/3544 248 | bytes4 private constant TRANSFER_SELECTOR = 0xa9059cbb; 249 | 250 | string private constant ERROR_TOKEN_BALANCE_REVERTED = "SAFE_ERC_20_BALANCE_REVERTED"; 251 | string private constant ERROR_TOKEN_ALLOWANCE_REVERTED = "SAFE_ERC_20_ALLOWANCE_REVERTED"; 252 | 253 | function invokeAndCheckSuccess(address _addr, bytes memory _calldata) 254 | private 255 | returns (bool) 256 | { 257 | bool ret; 258 | assembly { 259 | let ptr := mload(0x40) // free memory pointer 260 | 261 | let success := call( 262 | gas, // forward all gas 263 | _addr, // address 264 | 0, // no value 265 | add(_calldata, 0x20), // calldata start 266 | mload(_calldata), // calldata length 267 | ptr, // write output over free memory 268 | 0x20 // uint256 return 269 | ) 270 | 271 | if gt(success, 0) { 272 | // Check number of bytes returned from last function call 273 | switch returndatasize 274 | 275 | // No bytes returned: assume success 276 | case 0 { 277 | ret := 1 278 | } 279 | 280 | // 32 bytes returned: check if non-zero 281 | case 0x20 { 282 | // Only return success if returned data was true 283 | // Already have output in ptr 284 | ret := eq(mload(ptr), 1) 285 | } 286 | 287 | // Not sure what was returned: don't mark as success 288 | default { } 289 | } 290 | } 291 | return ret; 292 | } 293 | 294 | function staticInvoke(address _addr, bytes memory _calldata) 295 | private 296 | view 297 | returns (bool, uint256) 298 | { 299 | bool success; 300 | uint256 ret; 301 | assembly { 302 | let ptr := mload(0x40) // free memory pointer 303 | 304 | success := staticcall( 305 | gas, // forward all gas 306 | _addr, // address 307 | add(_calldata, 0x20), // calldata start 308 | mload(_calldata), // calldata length 309 | ptr, // write output over free memory 310 | 0x20 // uint256 return 311 | ) 312 | 313 | if gt(success, 0) { 314 | ret := mload(ptr) 315 | } 316 | } 317 | return (success, ret); 318 | } 319 | 320 | /** 321 | * @dev Same as a standards-compliant ERC20.transfer() that never reverts (returns false). 322 | * Note that this makes an external call to the token. 323 | */ 324 | function safeTransfer(ERC20 _token, address _to, uint256 _amount) internal returns (bool) { 325 | bytes memory transferCallData = abi.encodeWithSelector( 326 | TRANSFER_SELECTOR, 327 | _to, 328 | _amount 329 | ); 330 | return invokeAndCheckSuccess(_token, transferCallData); 331 | } 332 | 333 | /** 334 | * @dev Same as a standards-compliant ERC20.transferFrom() that never reverts (returns false). 335 | * Note that this makes an external call to the token. 336 | */ 337 | function safeTransferFrom(ERC20 _token, address _from, address _to, uint256 _amount) internal returns (bool) { 338 | bytes memory transferFromCallData = abi.encodeWithSelector( 339 | _token.transferFrom.selector, 340 | _from, 341 | _to, 342 | _amount 343 | ); 344 | return invokeAndCheckSuccess(_token, transferFromCallData); 345 | } 346 | 347 | /** 348 | * @dev Same as a standards-compliant ERC20.approve() that never reverts (returns false). 349 | * Note that this makes an external call to the token. 350 | */ 351 | function safeApprove(ERC20 _token, address _spender, uint256 _amount) internal returns (bool) { 352 | bytes memory approveCallData = abi.encodeWithSelector( 353 | _token.approve.selector, 354 | _spender, 355 | _amount 356 | ); 357 | return invokeAndCheckSuccess(_token, approveCallData); 358 | } 359 | 360 | /** 361 | * @dev Static call into ERC20.balanceOf(). 362 | * Reverts if the call fails for some reason (should never fail). 363 | */ 364 | function staticBalanceOf(ERC20 _token, address _owner) internal view returns (uint256) { 365 | bytes memory balanceOfCallData = abi.encodeWithSelector( 366 | _token.balanceOf.selector, 367 | _owner 368 | ); 369 | 370 | (bool success, uint256 tokenBalance) = staticInvoke(_token, balanceOfCallData); 371 | require(success, ERROR_TOKEN_BALANCE_REVERTED); 372 | 373 | return tokenBalance; 374 | } 375 | 376 | /** 377 | * @dev Static call into ERC20.allowance(). 378 | * Reverts if the call fails for some reason (should never fail). 379 | */ 380 | function staticAllowance(ERC20 _token, address _owner, address _spender) internal view returns (uint256) { 381 | bytes memory allowanceCallData = abi.encodeWithSelector( 382 | _token.allowance.selector, 383 | _owner, 384 | _spender 385 | ); 386 | 387 | (bool success, uint256 allowance) = staticInvoke(_token, allowanceCallData); 388 | require(success, ERROR_TOKEN_ALLOWANCE_REVERTED); 389 | 390 | return allowance; 391 | } 392 | 393 | /** 394 | * @dev Static call into ERC20.totalSupply(). 395 | * Reverts if the call fails for some reason (should never fail). 396 | */ 397 | function staticTotalSupply(ERC20 _token) internal view returns (uint256) { 398 | bytes memory totalSupplyCallData = abi.encodeWithSelector(_token.totalSupply.selector); 399 | 400 | (bool success, uint256 totalSupply) = staticInvoke(_token, totalSupplyCallData); 401 | require(success, ERROR_TOKEN_ALLOWANCE_REVERTED); 402 | 403 | return totalSupply; 404 | } 405 | } 406 | /* 407 | * SPDX-License-Identifier: MIT 408 | */ 409 | 410 | 411 | 412 | 413 | // aragonOS and aragon-apps rely on address(0) to denote native ETH, in 414 | // contracts where both tokens and ETH are accepted 415 | contract EtherTokenConstant { 416 | address internal constant ETH = address(0); 417 | } 418 | 419 | 420 | 421 | library ConversionHelpers { 422 | string private constant ERROR_IMPROPER_LENGTH = "CONVERSION_IMPROPER_LENGTH"; 423 | 424 | function dangerouslyCastUintArrayToBytes(uint256[] memory _input) internal pure returns (bytes memory output) { 425 | // Force cast the uint256[] into a bytes array, by overwriting its length 426 | // Note that the bytes array doesn't need to be initialized as we immediately overwrite it 427 | // with the input and a new length. The input becomes invalid from this point forward. 428 | uint256 byteLength = _input.length * 32; 429 | assembly { 430 | output := _input 431 | mstore(output, byteLength) 432 | } 433 | } 434 | 435 | function dangerouslyCastBytesToUintArray(bytes memory _input) internal pure returns (uint256[] memory output) { 436 | // Force cast the bytes array into a uint256[], by overwriting its length 437 | // Note that the uint256[] doesn't need to be initialized as we immediately overwrite it 438 | // with the input and a new length. The input becomes invalid from this point forward. 439 | uint256 intsLength = _input.length / 32; 440 | require(_input.length == intsLength * 32, ERROR_IMPROPER_LENGTH); 441 | 442 | assembly { 443 | output := _input 444 | mstore(output, intsLength) 445 | } 446 | } 447 | } 448 | /* 449 | * SPDX-License-Identifier: MIT 450 | */ 451 | 452 | 453 | 454 | 455 | contract ACLSyntaxSugar { 456 | function arr() internal pure returns (uint256[]) { 457 | return new uint256[](0); 458 | } 459 | 460 | function arr(bytes32 _a) internal pure returns (uint256[] r) { 461 | return arr(uint256(_a)); 462 | } 463 | 464 | function arr(bytes32 _a, bytes32 _b) internal pure returns (uint256[] r) { 465 | return arr(uint256(_a), uint256(_b)); 466 | } 467 | 468 | function arr(address _a) internal pure returns (uint256[] r) { 469 | return arr(uint256(_a)); 470 | } 471 | 472 | function arr(address _a, address _b) internal pure returns (uint256[] r) { 473 | return arr(uint256(_a), uint256(_b)); 474 | } 475 | 476 | function arr(address _a, uint256 _b, uint256 _c) internal pure returns (uint256[] r) { 477 | return arr(uint256(_a), _b, _c); 478 | } 479 | 480 | function arr(address _a, uint256 _b, uint256 _c, uint256 _d) internal pure returns (uint256[] r) { 481 | return arr(uint256(_a), _b, _c, _d); 482 | } 483 | 484 | function arr(address _a, uint256 _b) internal pure returns (uint256[] r) { 485 | return arr(uint256(_a), uint256(_b)); 486 | } 487 | 488 | function arr(address _a, address _b, uint256 _c, uint256 _d, uint256 _e) internal pure returns (uint256[] r) { 489 | return arr(uint256(_a), uint256(_b), _c, _d, _e); 490 | } 491 | 492 | function arr(address _a, address _b, address _c) internal pure returns (uint256[] r) { 493 | return arr(uint256(_a), uint256(_b), uint256(_c)); 494 | } 495 | 496 | function arr(address _a, address _b, uint256 _c) internal pure returns (uint256[] r) { 497 | return arr(uint256(_a), uint256(_b), uint256(_c)); 498 | } 499 | 500 | function arr(uint256 _a) internal pure returns (uint256[] r) { 501 | r = new uint256[](1); 502 | r[0] = _a; 503 | } 504 | 505 | function arr(uint256 _a, uint256 _b) internal pure returns (uint256[] r) { 506 | r = new uint256[](2); 507 | r[0] = _a; 508 | r[1] = _b; 509 | } 510 | 511 | function arr(uint256 _a, uint256 _b, uint256 _c) internal pure returns (uint256[] r) { 512 | r = new uint256[](3); 513 | r[0] = _a; 514 | r[1] = _b; 515 | r[2] = _c; 516 | } 517 | 518 | function arr(uint256 _a, uint256 _b, uint256 _c, uint256 _d) internal pure returns (uint256[] r) { 519 | r = new uint256[](4); 520 | r[0] = _a; 521 | r[1] = _b; 522 | r[2] = _c; 523 | r[3] = _d; 524 | } 525 | 526 | function arr(uint256 _a, uint256 _b, uint256 _c, uint256 _d, uint256 _e) internal pure returns (uint256[] r) { 527 | r = new uint256[](5); 528 | r[0] = _a; 529 | r[1] = _b; 530 | r[2] = _c; 531 | r[3] = _d; 532 | r[4] = _e; 533 | } 534 | } 535 | 536 | 537 | contract ACLHelpers { 538 | function decodeParamOp(uint256 _x) internal pure returns (uint8 b) { 539 | return uint8(_x >> (8 * 30)); 540 | } 541 | 542 | function decodeParamId(uint256 _x) internal pure returns (uint8 b) { 543 | return uint8(_x >> (8 * 31)); 544 | } 545 | 546 | function decodeParamsList(uint256 _x) internal pure returns (uint32 a, uint32 b, uint32 c) { 547 | a = uint32(_x); 548 | b = uint32(_x >> (8 * 4)); 549 | c = uint32(_x >> (8 * 8)); 550 | } 551 | } 552 | /* 553 | * SPDX-License-Identifier: MIT 554 | */ 555 | 556 | 557 | 558 | 559 | /* 560 | * SPDX-License-Identifier: MIT 561 | */ 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | interface IKernelEvents { 570 | event SetApp(bytes32 indexed namespace, bytes32 indexed appId, address app); 571 | } 572 | 573 | 574 | // This should be an interface, but interfaces can't inherit yet :( 575 | contract IKernel is IKernelEvents, IVaultRecoverable { 576 | function acl() public view returns (IACL); 577 | function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); 578 | 579 | function setApp(bytes32 namespace, bytes32 appId, address app) public; 580 | function getApp(bytes32 namespace, bytes32 appId) public view returns (address); 581 | } 582 | 583 | 584 | 585 | contract AppStorage { 586 | using UnstructuredStorage for bytes32; 587 | 588 | /* Hardcoded constants to save gas 589 | bytes32 internal constant KERNEL_POSITION = keccak256("aragonOS.appStorage.kernel"); 590 | bytes32 internal constant APP_ID_POSITION = keccak256("aragonOS.appStorage.appId"); 591 | */ 592 | bytes32 internal constant KERNEL_POSITION = 0x4172f0f7d2289153072b0a6ca36959e0cbe2efc3afe50fc81636caa96338137b; 593 | bytes32 internal constant APP_ID_POSITION = 0xd625496217aa6a3453eecb9c3489dc5a53e6c67b444329ea2b2cbc9ff547639b; 594 | 595 | function kernel() public view returns (IKernel) { 596 | return IKernel(KERNEL_POSITION.getStorageAddress()); 597 | } 598 | 599 | function appId() public view returns (bytes32) { 600 | return APP_ID_POSITION.getStorageBytes32(); 601 | } 602 | 603 | function setKernel(IKernel _kernel) internal { 604 | KERNEL_POSITION.setStorageAddress(address(_kernel)); 605 | } 606 | 607 | function setAppId(bytes32 _appId) internal { 608 | APP_ID_POSITION.setStorageBytes32(_appId); 609 | } 610 | } 611 | /* 612 | * SPDX-License-Identifier: MIT 613 | */ 614 | 615 | 616 | 617 | 618 | interface IForwarder { 619 | function isForwarder() external pure returns (bool); 620 | 621 | // TODO: this should be external 622 | // See https://github.com/ethereum/solidity/issues/4832 623 | function canForward(address sender, bytes evmCallScript) public view returns (bool); 624 | 625 | // TODO: this should be external 626 | // See https://github.com/ethereum/solidity/issues/4832 627 | function forward(bytes evmCallScript) public; 628 | } 629 | /* 630 | * SPDX-License-Identitifer: GPL-3.0-or-later 631 | */ 632 | 633 | 634 | 635 | /* 636 | * SPDX-License-Identifier: MIT 637 | */ 638 | 639 | 640 | 641 | 642 | 643 | /* 644 | * SPDX-License-Identifier: MIT 645 | */ 646 | 647 | 648 | 649 | /* 650 | * SPDX-License-Identifier: MIT 651 | */ 652 | 653 | 654 | 655 | /* 656 | * SPDX-License-Identifier: MIT 657 | */ 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | contract Initializable is TimeHelpers { 666 | using UnstructuredStorage for bytes32; 667 | 668 | // keccak256("aragonOS.initializable.initializationBlock") 669 | bytes32 internal constant INITIALIZATION_BLOCK_POSITION = 0xebb05b386a8d34882b8711d156f463690983dc47815980fb82aeeff1aa43579e; 670 | 671 | string private constant ERROR_ALREADY_INITIALIZED = "INIT_ALREADY_INITIALIZED"; 672 | string private constant ERROR_NOT_INITIALIZED = "INIT_NOT_INITIALIZED"; 673 | 674 | modifier onlyInit { 675 | require(getInitializationBlock() == 0, ERROR_ALREADY_INITIALIZED); 676 | _; 677 | } 678 | 679 | modifier isInitialized { 680 | require(hasInitialized(), ERROR_NOT_INITIALIZED); 681 | _; 682 | } 683 | 684 | /** 685 | * @return Block number in which the contract was initialized 686 | */ 687 | function getInitializationBlock() public view returns (uint256) { 688 | return INITIALIZATION_BLOCK_POSITION.getStorageUint256(); 689 | } 690 | 691 | /** 692 | * @return Whether the contract has been initialized by the time of the current block 693 | */ 694 | function hasInitialized() public view returns (bool) { 695 | uint256 initializationBlock = getInitializationBlock(); 696 | return initializationBlock != 0 && getBlockNumber() >= initializationBlock; 697 | } 698 | 699 | /** 700 | * @dev Function to be called by top level contract after initialization has finished. 701 | */ 702 | function initialized() internal onlyInit { 703 | INITIALIZATION_BLOCK_POSITION.setStorageUint256(getBlockNumber()); 704 | } 705 | 706 | /** 707 | * @dev Function to be called by top level contract after initialization to enable the contract 708 | * at a future block number rather than immediately. 709 | */ 710 | function initializedAt(uint256 _blockNumber) internal onlyInit { 711 | INITIALIZATION_BLOCK_POSITION.setStorageUint256(_blockNumber); 712 | } 713 | } 714 | 715 | 716 | 717 | contract Petrifiable is Initializable { 718 | // Use block UINT256_MAX (which should be never) as the initializable date 719 | uint256 internal constant PETRIFIED_BLOCK = uint256(-1); 720 | 721 | function isPetrified() public view returns (bool) { 722 | return getInitializationBlock() == PETRIFIED_BLOCK; 723 | } 724 | 725 | /** 726 | * @dev Function to be called by top level contract to prevent being initialized. 727 | * Useful for freezing base contracts when they're used behind proxies. 728 | */ 729 | function petrify() internal onlyInit { 730 | initializedAt(PETRIFIED_BLOCK); 731 | } 732 | } 733 | 734 | 735 | 736 | contract Autopetrified is Petrifiable { 737 | constructor() public { 738 | // Immediately petrify base (non-proxy) instances of inherited contracts on deploy. 739 | // This renders them uninitializable (and unusable without a proxy). 740 | petrify(); 741 | } 742 | } 743 | 744 | 745 | /* 746 | * SPDX-License-Identifier: MIT 747 | */ 748 | 749 | 750 | 751 | 752 | 753 | 754 | contract ReentrancyGuard { 755 | using UnstructuredStorage for bytes32; 756 | 757 | /* Hardcoded constants to save gas 758 | bytes32 internal constant REENTRANCY_MUTEX_POSITION = keccak256("aragonOS.reentrancyGuard.mutex"); 759 | */ 760 | bytes32 private constant REENTRANCY_MUTEX_POSITION = 0xe855346402235fdd185c890e68d2c4ecad599b88587635ee285bce2fda58dacb; 761 | 762 | string private constant ERROR_REENTRANT = "REENTRANCY_REENTRANT_CALL"; 763 | 764 | modifier nonReentrant() { 765 | // Ensure mutex is unlocked 766 | require(!REENTRANCY_MUTEX_POSITION.getStorageBool(), ERROR_REENTRANT); 767 | 768 | // Lock mutex before function call 769 | REENTRANCY_MUTEX_POSITION.setStorageBool(true); 770 | 771 | // Perform function call 772 | _; 773 | 774 | // Unlock mutex after function call 775 | REENTRANCY_MUTEX_POSITION.setStorageBool(false); 776 | } 777 | } 778 | 779 | /* 780 | * SPDX-License-Identifier: MIT 781 | */ 782 | 783 | 784 | 785 | 786 | 787 | /* 788 | * SPDX-License-Identifier: MIT 789 | */ 790 | 791 | 792 | 793 | 794 | contract IsContract { 795 | /* 796 | * NOTE: this should NEVER be used for authentication 797 | * (see pitfalls: https://github.com/fergarrui/ethereum-security/tree/master/contracts/extcodesize). 798 | * 799 | * This is only intended to be used as a sanity check that an address is actually a contract, 800 | * RATHER THAN an address not being a contract. 801 | */ 802 | function isContract(address _target) internal view returns (bool) { 803 | if (_target == address(0)) { 804 | return false; 805 | } 806 | 807 | uint256 size; 808 | assembly { size := extcodesize(_target) } 809 | return size > 0; 810 | } 811 | } 812 | 813 | 814 | 815 | 816 | 817 | contract VaultRecoverable is IVaultRecoverable, EtherTokenConstant, IsContract { 818 | using SafeERC20 for ERC20; 819 | 820 | string private constant ERROR_DISALLOWED = "RECOVER_DISALLOWED"; 821 | string private constant ERROR_VAULT_NOT_CONTRACT = "RECOVER_VAULT_NOT_CONTRACT"; 822 | string private constant ERROR_TOKEN_TRANSFER_FAILED = "RECOVER_TOKEN_TRANSFER_FAILED"; 823 | 824 | /** 825 | * @notice Send funds to recovery Vault. This contract should never receive funds, 826 | * but in case it does, this function allows one to recover them. 827 | * @param _token Token balance to be sent to recovery vault. 828 | */ 829 | function transferToVault(address _token) external { 830 | require(allowRecoverability(_token), ERROR_DISALLOWED); 831 | address vault = getRecoveryVault(); 832 | require(isContract(vault), ERROR_VAULT_NOT_CONTRACT); 833 | 834 | uint256 balance; 835 | if (_token == ETH) { 836 | balance = address(this).balance; 837 | vault.transfer(balance); 838 | } else { 839 | ERC20 token = ERC20(_token); 840 | balance = token.staticBalanceOf(this); 841 | require(token.safeTransfer(vault, balance), ERROR_TOKEN_TRANSFER_FAILED); 842 | } 843 | 844 | emit RecoverToVault(vault, _token, balance); 845 | } 846 | 847 | /** 848 | * @dev By default deriving from AragonApp makes it recoverable 849 | * @param token Token address that would be recovered 850 | * @return bool whether the app allows the recovery 851 | */ 852 | function allowRecoverability(address token) public view returns (bool) { 853 | return true; 854 | } 855 | 856 | // Cast non-implemented interface to be public so we can use it internally 857 | function getRecoveryVault() public view returns (address); 858 | } 859 | 860 | /* 861 | * SPDX-License-Identifier: MIT 862 | */ 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | contract EVMScriptRunner is AppStorage, Initializable, EVMScriptRegistryConstants, KernelNamespaceConstants { 875 | string private constant ERROR_EXECUTOR_UNAVAILABLE = "EVMRUN_EXECUTOR_UNAVAILABLE"; 876 | string private constant ERROR_PROTECTED_STATE_MODIFIED = "EVMRUN_PROTECTED_STATE_MODIFIED"; 877 | 878 | /* This is manually crafted in assembly 879 | string private constant ERROR_EXECUTOR_INVALID_RETURN = "EVMRUN_EXECUTOR_INVALID_RETURN"; 880 | */ 881 | 882 | event ScriptResult(address indexed executor, bytes script, bytes input, bytes returnData); 883 | 884 | function getEVMScriptExecutor(bytes _script) public view returns (IEVMScriptExecutor) { 885 | return IEVMScriptExecutor(getEVMScriptRegistry().getScriptExecutor(_script)); 886 | } 887 | 888 | function getEVMScriptRegistry() public view returns (IEVMScriptRegistry) { 889 | address registryAddr = kernel().getApp(KERNEL_APP_ADDR_NAMESPACE, EVMSCRIPT_REGISTRY_APP_ID); 890 | return IEVMScriptRegistry(registryAddr); 891 | } 892 | 893 | function runScript(bytes _script, bytes _input, address[] _blacklist) 894 | internal 895 | isInitialized 896 | protectState 897 | returns (bytes) 898 | { 899 | IEVMScriptExecutor executor = getEVMScriptExecutor(_script); 900 | require(address(executor) != address(0), ERROR_EXECUTOR_UNAVAILABLE); 901 | 902 | bytes4 sig = executor.execScript.selector; 903 | bytes memory data = abi.encodeWithSelector(sig, _script, _input, _blacklist); 904 | 905 | bytes memory output; 906 | assembly { 907 | let success := delegatecall( 908 | gas, // forward all gas 909 | executor, // address 910 | add(data, 0x20), // calldata start 911 | mload(data), // calldata length 912 | 0, // don't write output (we'll handle this ourselves) 913 | 0 // don't write output 914 | ) 915 | 916 | output := mload(0x40) // free mem ptr get 917 | 918 | switch success 919 | case 0 { 920 | // If the call errored, forward its full error data 921 | returndatacopy(output, 0, returndatasize) 922 | revert(output, returndatasize) 923 | } 924 | default { 925 | switch gt(returndatasize, 0x3f) 926 | case 0 { 927 | // Need at least 0x40 bytes returned for properly ABI-encoded bytes values, 928 | // revert with "EVMRUN_EXECUTOR_INVALID_RETURN" 929 | // See remix: doing a `revert("EVMRUN_EXECUTOR_INVALID_RETURN")` always results in 930 | // this memory layout 931 | mstore(output, 0x08c379a000000000000000000000000000000000000000000000000000000000) // error identifier 932 | mstore(add(output, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset 933 | mstore(add(output, 0x24), 0x000000000000000000000000000000000000000000000000000000000000001e) // reason length 934 | mstore(add(output, 0x44), 0x45564d52554e5f4558454355544f525f494e56414c49445f52455455524e0000) // reason 935 | 936 | revert(output, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error) 937 | } 938 | default { 939 | // Copy result 940 | // 941 | // Needs to perform an ABI decode for the expected `bytes` return type of 942 | // `executor.execScript()` as solidity will automatically ABI encode the returned bytes as: 943 | // [ position of the first dynamic length return value = 0x20 (32 bytes) ] 944 | // [ output length (32 bytes) ] 945 | // [ output content (N bytes) ] 946 | // 947 | // Perform the ABI decode by ignoring the first 32 bytes of the return data 948 | let copysize := sub(returndatasize, 0x20) 949 | returndatacopy(output, 0x20, copysize) 950 | 951 | mstore(0x40, add(output, copysize)) // free mem ptr set 952 | } 953 | } 954 | } 955 | 956 | emit ScriptResult(address(executor), _script, _input, output); 957 | 958 | return output; 959 | } 960 | 961 | modifier protectState { 962 | address preKernel = address(kernel()); 963 | bytes32 preAppId = appId(); 964 | _; // exec 965 | require(address(kernel()) == preKernel, ERROR_PROTECTED_STATE_MODIFIED); 966 | require(appId() == preAppId, ERROR_PROTECTED_STATE_MODIFIED); 967 | } 968 | } 969 | 970 | 971 | 972 | // Contracts inheriting from AragonApp are, by default, immediately petrified upon deployment so 973 | // that they can never be initialized. 974 | // Unless overriden, this behaviour enforces those contracts to be usable only behind an AppProxy. 975 | // ReentrancyGuard, EVMScriptRunner, and ACLSyntaxSugar are not directly used by this contract, but 976 | // are included so that they are automatically usable by subclassing contracts 977 | contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGuard, EVMScriptRunner, ACLSyntaxSugar { 978 | string private constant ERROR_AUTH_FAILED = "APP_AUTH_FAILED"; 979 | 980 | modifier auth(bytes32 _role) { 981 | require(canPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED); 982 | _; 983 | } 984 | 985 | modifier authP(bytes32 _role, uint256[] _params) { 986 | require(canPerform(msg.sender, _role, _params), ERROR_AUTH_FAILED); 987 | _; 988 | } 989 | 990 | /** 991 | * @dev Check whether an action can be performed by a sender for a particular role on this app 992 | * @param _sender Sender of the call 993 | * @param _role Role on this app 994 | * @param _params Permission params for the role 995 | * @return Boolean indicating whether the sender has the permissions to perform the action. 996 | * Always returns false if the app hasn't been initialized yet. 997 | */ 998 | function canPerform(address _sender, bytes32 _role, uint256[] _params) public view returns (bool) { 999 | if (!hasInitialized()) { 1000 | return false; 1001 | } 1002 | 1003 | IKernel linkedKernel = kernel(); 1004 | if (address(linkedKernel) == address(0)) { 1005 | return false; 1006 | } 1007 | 1008 | return linkedKernel.hasPermission( 1009 | _sender, 1010 | address(this), 1011 | _role, 1012 | ConversionHelpers.dangerouslyCastUintArrayToBytes(_params) 1013 | ); 1014 | } 1015 | 1016 | /** 1017 | * @dev Get the recovery vault for the app 1018 | * @return Recovery vault address for the app 1019 | */ 1020 | function getRecoveryVault() public view returns (address) { 1021 | // Funds recovery via a vault is only available when used with a kernel 1022 | return kernel().getRecoveryVault(); // if kernel is not set, it will revert 1023 | } 1024 | } 1025 | 1026 | 1027 | 1028 | // See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/d51e38758e1d985661534534d5c61e27bece5042/contracts/math/SafeMath.sol 1029 | // Adapted to use pragma ^0.4.24 and satisfy our linter rules 1030 | 1031 | 1032 | 1033 | 1034 | /** 1035 | * @title SafeMath 1036 | * @dev Math operations with safety checks that revert on error 1037 | */ 1038 | library SafeMath { 1039 | string private constant ERROR_ADD_OVERFLOW = "MATH_ADD_OVERFLOW"; 1040 | string private constant ERROR_SUB_UNDERFLOW = "MATH_SUB_UNDERFLOW"; 1041 | string private constant ERROR_MUL_OVERFLOW = "MATH_MUL_OVERFLOW"; 1042 | string private constant ERROR_DIV_ZERO = "MATH_DIV_ZERO"; 1043 | 1044 | /** 1045 | * @dev Multiplies two numbers, reverts on overflow. 1046 | */ 1047 | function mul(uint256 _a, uint256 _b) internal pure returns (uint256) { 1048 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 1049 | // benefit is lost if 'b' is also tested. 1050 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 1051 | if (_a == 0) { 1052 | return 0; 1053 | } 1054 | 1055 | uint256 c = _a * _b; 1056 | require(c / _a == _b, ERROR_MUL_OVERFLOW); 1057 | 1058 | return c; 1059 | } 1060 | 1061 | /** 1062 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 1063 | */ 1064 | function div(uint256 _a, uint256 _b) internal pure returns (uint256) { 1065 | require(_b > 0, ERROR_DIV_ZERO); // Solidity only automatically asserts when dividing by 0 1066 | uint256 c = _a / _b; 1067 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 1068 | 1069 | return c; 1070 | } 1071 | 1072 | /** 1073 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 1074 | */ 1075 | function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { 1076 | require(_b <= _a, ERROR_SUB_UNDERFLOW); 1077 | uint256 c = _a - _b; 1078 | 1079 | return c; 1080 | } 1081 | 1082 | /** 1083 | * @dev Adds two numbers, reverts on overflow. 1084 | */ 1085 | function add(uint256 _a, uint256 _b) internal pure returns (uint256) { 1086 | uint256 c = _a + _b; 1087 | require(c >= _a, ERROR_ADD_OVERFLOW); 1088 | 1089 | return c; 1090 | } 1091 | 1092 | /** 1093 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 1094 | * reverts when dividing by zero. 1095 | */ 1096 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 1097 | require(b != 0, ERROR_DIV_ZERO); 1098 | return a % b; 1099 | } 1100 | } 1101 | 1102 | // See https://github.com/OpenZeppelin/openzeppelin-solidity/blob/d51e38758e1d985661534534d5c61e27bece5042/contracts/math/SafeMath.sol 1103 | // Adapted for uint64, pragma ^0.4.24, and satisfying our linter rules 1104 | // Also optimized the mul() implementation, see https://github.com/aragon/aragonOS/pull/417 1105 | 1106 | 1107 | 1108 | 1109 | /** 1110 | * @title SafeMath64 1111 | * @dev Math operations for uint64 with safety checks that revert on error 1112 | */ 1113 | library SafeMath64 { 1114 | string private constant ERROR_ADD_OVERFLOW = "MATH64_ADD_OVERFLOW"; 1115 | string private constant ERROR_SUB_UNDERFLOW = "MATH64_SUB_UNDERFLOW"; 1116 | string private constant ERROR_MUL_OVERFLOW = "MATH64_MUL_OVERFLOW"; 1117 | string private constant ERROR_DIV_ZERO = "MATH64_DIV_ZERO"; 1118 | 1119 | /** 1120 | * @dev Multiplies two numbers, reverts on overflow. 1121 | */ 1122 | function mul(uint64 _a, uint64 _b) internal pure returns (uint64) { 1123 | uint256 c = uint256(_a) * uint256(_b); 1124 | require(c < 0x010000000000000000, ERROR_MUL_OVERFLOW); // 2**64 (less gas this way) 1125 | 1126 | return uint64(c); 1127 | } 1128 | 1129 | /** 1130 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 1131 | */ 1132 | function div(uint64 _a, uint64 _b) internal pure returns (uint64) { 1133 | require(_b > 0, ERROR_DIV_ZERO); // Solidity only automatically asserts when dividing by 0 1134 | uint64 c = _a / _b; 1135 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 1136 | 1137 | return c; 1138 | } 1139 | 1140 | /** 1141 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 1142 | */ 1143 | function sub(uint64 _a, uint64 _b) internal pure returns (uint64) { 1144 | require(_b <= _a, ERROR_SUB_UNDERFLOW); 1145 | uint64 c = _a - _b; 1146 | 1147 | return c; 1148 | } 1149 | 1150 | /** 1151 | * @dev Adds two numbers, reverts on overflow. 1152 | */ 1153 | function add(uint64 _a, uint64 _b) internal pure returns (uint64) { 1154 | uint64 c = _a + _b; 1155 | require(c >= _a, ERROR_ADD_OVERFLOW); 1156 | 1157 | return c; 1158 | } 1159 | 1160 | /** 1161 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 1162 | * reverts when dividing by zero. 1163 | */ 1164 | function mod(uint64 a, uint64 b) internal pure returns (uint64) { 1165 | require(b != 0, ERROR_DIV_ZERO); 1166 | return a % b; 1167 | } 1168 | } 1169 | 1170 | 1171 | 1172 | 1173 | /* 1174 | Copyright 2016, Jordi Baylina 1175 | This program is free software: you can redistribute it and/or modify 1176 | it under the terms of the GNU General Public License as published by 1177 | the Free Software Foundation, either version 3 of the License, or 1178 | (at your option) any later version. 1179 | This program is distributed in the hope that it will be useful, 1180 | but WITHOUT ANY WARRANTY; without even the implied warranty of 1181 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1182 | GNU General Public License for more details. 1183 | You should have received a copy of the GNU General Public License 1184 | along with this program. If not, see . 1185 | */ 1186 | 1187 | /// @title MiniMeToken Contract 1188 | /// @author Jordi Baylina 1189 | /// @dev This token contract's goal is to make it easy for anyone to clone this 1190 | /// token using the token distribution at a given block, this will allow DAO's 1191 | /// and DApps to upgrade their features in a decentralized manner without 1192 | /// affecting the original token 1193 | /// @dev It is ERC20 compliant, but still needs to under go further testing. 1194 | 1195 | 1196 | 1197 | /// @dev The token controller contract must implement these functions 1198 | 1199 | 1200 | interface ITokenController { 1201 | /// @notice Called when `_owner` sends ether to the MiniMe Token contract 1202 | /// @param _owner The address that sent the ether to create tokens 1203 | /// @return True if the ether is accepted, false if it throws 1204 | function proxyPayment(address _owner) external payable returns(bool); 1205 | 1206 | /// @notice Notifies the controller about a token transfer allowing the 1207 | /// controller to react if desired 1208 | /// @param _from The origin of the transfer 1209 | /// @param _to The destination of the transfer 1210 | /// @param _amount The amount of the transfer 1211 | /// @return False if the controller does not authorize the transfer 1212 | function onTransfer(address _from, address _to, uint _amount) external returns(bool); 1213 | 1214 | /// @notice Notifies the controller about an approval allowing the 1215 | /// controller to react if desired 1216 | /// @param _owner The address that calls `approve()` 1217 | /// @param _spender The spender in the `approve()` call 1218 | /// @param _amount The amount in the `approve()` call 1219 | /// @return False if the controller does not authorize the approval 1220 | function onApprove(address _owner, address _spender, uint _amount) external returns(bool); 1221 | } 1222 | 1223 | 1224 | contract Controlled { 1225 | /// @notice The address of the controller is the only address that can call 1226 | /// a function with this modifier 1227 | modifier onlyController { 1228 | require(msg.sender == controller); 1229 | _; 1230 | } 1231 | 1232 | address public controller; 1233 | 1234 | function Controlled() public { controller = msg.sender;} 1235 | 1236 | /// @notice Changes the controller of the contract 1237 | /// @param _newController The new controller of the contract 1238 | function changeController(address _newController) onlyController public { 1239 | controller = _newController; 1240 | } 1241 | } 1242 | 1243 | contract ApproveAndCallFallBack { 1244 | function receiveApproval( 1245 | address from, 1246 | uint256 _amount, 1247 | address _token, 1248 | bytes _data 1249 | ) public; 1250 | } 1251 | 1252 | /// @dev The actual token contract, the default controller is the msg.sender 1253 | /// that deploys the contract, so usually this token will be deployed by a 1254 | /// token controller contract, which Giveth will call a "Campaign" 1255 | contract MiniMeToken is Controlled { 1256 | 1257 | string public name; //The Token's name: e.g. DigixDAO Tokens 1258 | uint8 public decimals; //Number of decimals of the smallest unit 1259 | string public symbol; //An identifier: e.g. REP 1260 | string public version = "MMT_0.1"; //An arbitrary versioning scheme 1261 | 1262 | 1263 | /// @dev `Checkpoint` is the structure that attaches a block number to a 1264 | /// given value, the block number attached is the one that last changed the 1265 | /// value 1266 | struct Checkpoint { 1267 | 1268 | // `fromBlock` is the block number that the value was generated from 1269 | uint128 fromBlock; 1270 | 1271 | // `value` is the amount of tokens at a specific block number 1272 | uint128 value; 1273 | } 1274 | 1275 | // `parentToken` is the Token address that was cloned to produce this token; 1276 | // it will be 0x0 for a token that was not cloned 1277 | MiniMeToken public parentToken; 1278 | 1279 | // `parentSnapShotBlock` is the block number from the Parent Token that was 1280 | // used to determine the initial distribution of the Clone Token 1281 | uint public parentSnapShotBlock; 1282 | 1283 | // `creationBlock` is the block number that the Clone Token was created 1284 | uint public creationBlock; 1285 | 1286 | // `balances` is the map that tracks the balance of each address, in this 1287 | // contract when the balance changes the block number that the change 1288 | // occurred is also included in the map 1289 | mapping (address => Checkpoint[]) balances; 1290 | 1291 | // `allowed` tracks any extra transfer rights as in all ERC20 tokens 1292 | mapping (address => mapping (address => uint256)) allowed; 1293 | 1294 | // Tracks the history of the `totalSupply` of the token 1295 | Checkpoint[] totalSupplyHistory; 1296 | 1297 | // Flag that determines if the token is transferable or not. 1298 | bool public transfersEnabled; 1299 | 1300 | // The factory used to create new clone tokens 1301 | MiniMeTokenFactory public tokenFactory; 1302 | 1303 | //////////////// 1304 | // Constructor 1305 | //////////////// 1306 | 1307 | /// @notice Constructor to create a MiniMeToken 1308 | /// @param _tokenFactory The address of the MiniMeTokenFactory contract that 1309 | /// will create the Clone token contracts, the token factory needs to be 1310 | /// deployed first 1311 | /// @param _parentToken Address of the parent token, set to 0x0 if it is a 1312 | /// new token 1313 | /// @param _parentSnapShotBlock Block of the parent token that will 1314 | /// determine the initial distribution of the clone token, set to 0 if it 1315 | /// is a new token 1316 | /// @param _tokenName Name of the new token 1317 | /// @param _decimalUnits Number of decimals of the new token 1318 | /// @param _tokenSymbol Token Symbol for the new token 1319 | /// @param _transfersEnabled If true, tokens will be able to be transferred 1320 | function MiniMeToken( 1321 | MiniMeTokenFactory _tokenFactory, 1322 | MiniMeToken _parentToken, 1323 | uint _parentSnapShotBlock, 1324 | string _tokenName, 1325 | uint8 _decimalUnits, 1326 | string _tokenSymbol, 1327 | bool _transfersEnabled 1328 | ) public 1329 | { 1330 | tokenFactory = _tokenFactory; 1331 | name = _tokenName; // Set the name 1332 | decimals = _decimalUnits; // Set the decimals 1333 | symbol = _tokenSymbol; // Set the symbol 1334 | parentToken = _parentToken; 1335 | parentSnapShotBlock = _parentSnapShotBlock; 1336 | transfersEnabled = _transfersEnabled; 1337 | creationBlock = block.number; 1338 | } 1339 | 1340 | 1341 | /////////////////// 1342 | // ERC20 Methods 1343 | /////////////////// 1344 | 1345 | /// @notice Send `_amount` tokens to `_to` from `msg.sender` 1346 | /// @param _to The address of the recipient 1347 | /// @param _amount The amount of tokens to be transferred 1348 | /// @return Whether the transfer was successful or not 1349 | function transfer(address _to, uint256 _amount) public returns (bool success) { 1350 | require(transfersEnabled); 1351 | return doTransfer(msg.sender, _to, _amount); 1352 | } 1353 | 1354 | /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it 1355 | /// is approved by `_from` 1356 | /// @param _from The address holding the tokens being transferred 1357 | /// @param _to The address of the recipient 1358 | /// @param _amount The amount of tokens to be transferred 1359 | /// @return True if the transfer was successful 1360 | function transferFrom(address _from, address _to, uint256 _amount) public returns (bool success) { 1361 | 1362 | // The controller of this contract can move tokens around at will, 1363 | // this is important to recognize! Confirm that you trust the 1364 | // controller of this contract, which in most situations should be 1365 | // another open source smart contract or 0x0 1366 | if (msg.sender != controller) { 1367 | require(transfersEnabled); 1368 | 1369 | // The standard ERC 20 transferFrom functionality 1370 | if (allowed[_from][msg.sender] < _amount) 1371 | return false; 1372 | allowed[_from][msg.sender] -= _amount; 1373 | } 1374 | return doTransfer(_from, _to, _amount); 1375 | } 1376 | 1377 | /// @dev This is the actual transfer function in the token contract, it can 1378 | /// only be called by other functions in this contract. 1379 | /// @param _from The address holding the tokens being transferred 1380 | /// @param _to The address of the recipient 1381 | /// @param _amount The amount of tokens to be transferred 1382 | /// @return True if the transfer was successful 1383 | function doTransfer(address _from, address _to, uint _amount) internal returns(bool) { 1384 | if (_amount == 0) { 1385 | return true; 1386 | } 1387 | require(parentSnapShotBlock < block.number); 1388 | // Do not allow transfer to 0x0 or the token contract itself 1389 | require((_to != 0) && (_to != address(this))); 1390 | // If the amount being transfered is more than the balance of the 1391 | // account the transfer returns false 1392 | var previousBalanceFrom = balanceOfAt(_from, block.number); 1393 | if (previousBalanceFrom < _amount) { 1394 | return false; 1395 | } 1396 | // Alerts the token controller of the transfer 1397 | if (isContract(controller)) { 1398 | // Adding the ` == true` makes the linter shut up so... 1399 | require(ITokenController(controller).onTransfer(_from, _to, _amount) == true); 1400 | } 1401 | // First update the balance array with the new value for the address 1402 | // sending the tokens 1403 | updateValueAtNow(balances[_from], previousBalanceFrom - _amount); 1404 | // Then update the balance array with the new value for the address 1405 | // receiving the tokens 1406 | var previousBalanceTo = balanceOfAt(_to, block.number); 1407 | require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow 1408 | updateValueAtNow(balances[_to], previousBalanceTo + _amount); 1409 | // An event to make the transfer easy to find on the blockchain 1410 | Transfer(_from, _to, _amount); 1411 | return true; 1412 | } 1413 | 1414 | /// @param _owner The address that's balance is being requested 1415 | /// @return The balance of `_owner` at the current block 1416 | function balanceOf(address _owner) public constant returns (uint256 balance) { 1417 | return balanceOfAt(_owner, block.number); 1418 | } 1419 | 1420 | /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on 1421 | /// its behalf. This is a modified version of the ERC20 approve function 1422 | /// to be a little bit safer 1423 | /// @param _spender The address of the account able to transfer the tokens 1424 | /// @param _amount The amount of tokens to be approved for transfer 1425 | /// @return True if the approval was successful 1426 | function approve(address _spender, uint256 _amount) public returns (bool success) { 1427 | require(transfersEnabled); 1428 | 1429 | // To change the approve amount you first have to reduce the addresses` 1430 | // allowance to zero by calling `approve(_spender,0)` if it is not 1431 | // already 0 to mitigate the race condition described here: 1432 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 1433 | require((_amount == 0) || (allowed[msg.sender][_spender] == 0)); 1434 | 1435 | // Alerts the token controller of the approve function call 1436 | if (isContract(controller)) { 1437 | // Adding the ` == true` makes the linter shut up so... 1438 | require(ITokenController(controller).onApprove(msg.sender, _spender, _amount) == true); 1439 | } 1440 | 1441 | allowed[msg.sender][_spender] = _amount; 1442 | Approval(msg.sender, _spender, _amount); 1443 | return true; 1444 | } 1445 | 1446 | /// @dev This function makes it easy to read the `allowed[]` map 1447 | /// @param _owner The address of the account that owns the token 1448 | /// @param _spender The address of the account able to transfer the tokens 1449 | /// @return Amount of remaining tokens of _owner that _spender is allowed 1450 | /// to spend 1451 | function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { 1452 | return allowed[_owner][_spender]; 1453 | } 1454 | 1455 | /// @notice `msg.sender` approves `_spender` to send `_amount` tokens on 1456 | /// its behalf, and then a function is triggered in the contract that is 1457 | /// being approved, `_spender`. This allows users to use their tokens to 1458 | /// interact with contracts in one function call instead of two 1459 | /// @param _spender The address of the contract able to transfer the tokens 1460 | /// @param _amount The amount of tokens to be approved for transfer 1461 | /// @return True if the function call was successful 1462 | function approveAndCall(ApproveAndCallFallBack _spender, uint256 _amount, bytes _extraData) public returns (bool success) { 1463 | require(approve(_spender, _amount)); 1464 | 1465 | _spender.receiveApproval( 1466 | msg.sender, 1467 | _amount, 1468 | this, 1469 | _extraData 1470 | ); 1471 | 1472 | return true; 1473 | } 1474 | 1475 | /// @dev This function makes it easy to get the total number of tokens 1476 | /// @return The total number of tokens 1477 | function totalSupply() public constant returns (uint) { 1478 | return totalSupplyAt(block.number); 1479 | } 1480 | 1481 | 1482 | //////////////// 1483 | // Query balance and totalSupply in History 1484 | //////////////// 1485 | 1486 | /// @dev Queries the balance of `_owner` at a specific `_blockNumber` 1487 | /// @param _owner The address from which the balance will be retrieved 1488 | /// @param _blockNumber The block number when the balance is queried 1489 | /// @return The balance at `_blockNumber` 1490 | function balanceOfAt(address _owner, uint _blockNumber) public constant returns (uint) { 1491 | 1492 | // These next few lines are used when the balance of the token is 1493 | // requested before a check point was ever created for this token, it 1494 | // requires that the `parentToken.balanceOfAt` be queried at the 1495 | // genesis block for that token as this contains initial balance of 1496 | // this token 1497 | if ((balances[_owner].length == 0) || (balances[_owner][0].fromBlock > _blockNumber)) { 1498 | if (address(parentToken) != 0) { 1499 | return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); 1500 | } else { 1501 | // Has no parent 1502 | return 0; 1503 | } 1504 | 1505 | // This will return the expected balance during normal situations 1506 | } else { 1507 | return getValueAt(balances[_owner], _blockNumber); 1508 | } 1509 | } 1510 | 1511 | /// @notice Total amount of tokens at a specific `_blockNumber`. 1512 | /// @param _blockNumber The block number when the totalSupply is queried 1513 | /// @return The total amount of tokens at `_blockNumber` 1514 | function totalSupplyAt(uint _blockNumber) public constant returns(uint) { 1515 | 1516 | // These next few lines are used when the totalSupply of the token is 1517 | // requested before a check point was ever created for this token, it 1518 | // requires that the `parentToken.totalSupplyAt` be queried at the 1519 | // genesis block for this token as that contains totalSupply of this 1520 | // token at this block number. 1521 | if ((totalSupplyHistory.length == 0) || (totalSupplyHistory[0].fromBlock > _blockNumber)) { 1522 | if (address(parentToken) != 0) { 1523 | return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); 1524 | } else { 1525 | return 0; 1526 | } 1527 | 1528 | // This will return the expected totalSupply during normal situations 1529 | } else { 1530 | return getValueAt(totalSupplyHistory, _blockNumber); 1531 | } 1532 | } 1533 | 1534 | //////////////// 1535 | // Clone Token Method 1536 | //////////////// 1537 | 1538 | /// @notice Creates a new clone token with the initial distribution being 1539 | /// this token at `_snapshotBlock` 1540 | /// @param _cloneTokenName Name of the clone token 1541 | /// @param _cloneDecimalUnits Number of decimals of the smallest unit 1542 | /// @param _cloneTokenSymbol Symbol of the clone token 1543 | /// @param _snapshotBlock Block when the distribution of the parent token is 1544 | /// copied to set the initial distribution of the new clone token; 1545 | /// if the block is zero than the actual block, the current block is used 1546 | /// @param _transfersEnabled True if transfers are allowed in the clone 1547 | /// @return The address of the new MiniMeToken Contract 1548 | function createCloneToken( 1549 | string _cloneTokenName, 1550 | uint8 _cloneDecimalUnits, 1551 | string _cloneTokenSymbol, 1552 | uint _snapshotBlock, 1553 | bool _transfersEnabled 1554 | ) public returns(MiniMeToken) 1555 | { 1556 | uint256 snapshot = _snapshotBlock == 0 ? block.number - 1 : _snapshotBlock; 1557 | 1558 | MiniMeToken cloneToken = tokenFactory.createCloneToken( 1559 | this, 1560 | snapshot, 1561 | _cloneTokenName, 1562 | _cloneDecimalUnits, 1563 | _cloneTokenSymbol, 1564 | _transfersEnabled 1565 | ); 1566 | 1567 | cloneToken.changeController(msg.sender); 1568 | 1569 | // An event to make the token easy to find on the blockchain 1570 | NewCloneToken(address(cloneToken), snapshot); 1571 | return cloneToken; 1572 | } 1573 | 1574 | //////////////// 1575 | // Generate and destroy tokens 1576 | //////////////// 1577 | 1578 | /// @notice Generates `_amount` tokens that are assigned to `_owner` 1579 | /// @param _owner The address that will be assigned the new tokens 1580 | /// @param _amount The quantity of tokens generated 1581 | /// @return True if the tokens are generated correctly 1582 | function generateTokens(address _owner, uint _amount) onlyController public returns (bool) { 1583 | uint curTotalSupply = totalSupply(); 1584 | require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow 1585 | uint previousBalanceTo = balanceOf(_owner); 1586 | require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow 1587 | updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); 1588 | updateValueAtNow(balances[_owner], previousBalanceTo + _amount); 1589 | Transfer(0, _owner, _amount); 1590 | return true; 1591 | } 1592 | 1593 | 1594 | /// @notice Burns `_amount` tokens from `_owner` 1595 | /// @param _owner The address that will lose the tokens 1596 | /// @param _amount The quantity of tokens to burn 1597 | /// @return True if the tokens are burned correctly 1598 | function destroyTokens(address _owner, uint _amount) onlyController public returns (bool) { 1599 | uint curTotalSupply = totalSupply(); 1600 | require(curTotalSupply >= _amount); 1601 | uint previousBalanceFrom = balanceOf(_owner); 1602 | require(previousBalanceFrom >= _amount); 1603 | updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); 1604 | updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); 1605 | Transfer(_owner, 0, _amount); 1606 | return true; 1607 | } 1608 | 1609 | //////////////// 1610 | // Enable tokens transfers 1611 | //////////////// 1612 | 1613 | 1614 | /// @notice Enables token holders to transfer their tokens freely if true 1615 | /// @param _transfersEnabled True if transfers are allowed in the clone 1616 | function enableTransfers(bool _transfersEnabled) onlyController public { 1617 | transfersEnabled = _transfersEnabled; 1618 | } 1619 | 1620 | //////////////// 1621 | // Internal helper functions to query and set a value in a snapshot array 1622 | //////////////// 1623 | 1624 | /// @dev `getValueAt` retrieves the number of tokens at a given block number 1625 | /// @param checkpoints The history of values being queried 1626 | /// @param _block The block number to retrieve the value at 1627 | /// @return The number of tokens being queried 1628 | function getValueAt(Checkpoint[] storage checkpoints, uint _block) constant internal returns (uint) { 1629 | if (checkpoints.length == 0) 1630 | return 0; 1631 | 1632 | // Shortcut for the actual value 1633 | if (_block >= checkpoints[checkpoints.length-1].fromBlock) 1634 | return checkpoints[checkpoints.length-1].value; 1635 | if (_block < checkpoints[0].fromBlock) 1636 | return 0; 1637 | 1638 | // Binary search of the value in the array 1639 | uint min = 0; 1640 | uint max = checkpoints.length-1; 1641 | while (max > min) { 1642 | uint mid = (max + min + 1) / 2; 1643 | if (checkpoints[mid].fromBlock<=_block) { 1644 | min = mid; 1645 | } else { 1646 | max = mid-1; 1647 | } 1648 | } 1649 | return checkpoints[min].value; 1650 | } 1651 | 1652 | /// @dev `updateValueAtNow` used to update the `balances` map and the 1653 | /// `totalSupplyHistory` 1654 | /// @param checkpoints The history of data being updated 1655 | /// @param _value The new number of tokens 1656 | function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value) internal { 1657 | if ((checkpoints.length == 0) || (checkpoints[checkpoints.length - 1].fromBlock < block.number)) { 1658 | Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++]; 1659 | newCheckPoint.fromBlock = uint128(block.number); 1660 | newCheckPoint.value = uint128(_value); 1661 | } else { 1662 | Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length - 1]; 1663 | oldCheckPoint.value = uint128(_value); 1664 | } 1665 | } 1666 | 1667 | /// @dev Internal function to determine if an address is a contract 1668 | /// @param _addr The address being queried 1669 | /// @return True if `_addr` is a contract 1670 | function isContract(address _addr) constant internal returns(bool) { 1671 | uint size; 1672 | if (_addr == 0) 1673 | return false; 1674 | 1675 | assembly { 1676 | size := extcodesize(_addr) 1677 | } 1678 | 1679 | return size>0; 1680 | } 1681 | 1682 | /// @dev Helper function to return a min betwen the two uints 1683 | function min(uint a, uint b) pure internal returns (uint) { 1684 | return a < b ? a : b; 1685 | } 1686 | 1687 | /// @notice The fallback function: If the contract's controller has not been 1688 | /// set to 0, then the `proxyPayment` method is called which relays the 1689 | /// ether and creates tokens as described in the token controller contract 1690 | function () external payable { 1691 | require(isContract(controller)); 1692 | // Adding the ` == true` makes the linter shut up so... 1693 | require(ITokenController(controller).proxyPayment.value(msg.value)(msg.sender) == true); 1694 | } 1695 | 1696 | ////////// 1697 | // Safety Methods 1698 | ////////// 1699 | 1700 | /// @notice This method can be used by the controller to extract mistakenly 1701 | /// sent tokens to this contract. 1702 | /// @param _token The address of the token contract that you want to recover 1703 | /// set to 0 in case you want to extract ether. 1704 | function claimTokens(address _token) onlyController public { 1705 | if (_token == 0x0) { 1706 | controller.transfer(this.balance); 1707 | return; 1708 | } 1709 | 1710 | MiniMeToken token = MiniMeToken(_token); 1711 | uint balance = token.balanceOf(this); 1712 | token.transfer(controller, balance); 1713 | ClaimedTokens(_token, controller, balance); 1714 | } 1715 | 1716 | //////////////// 1717 | // Events 1718 | //////////////// 1719 | event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount); 1720 | event Transfer(address indexed _from, address indexed _to, uint256 _amount); 1721 | event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock); 1722 | event Approval( 1723 | address indexed _owner, 1724 | address indexed _spender, 1725 | uint256 _amount 1726 | ); 1727 | 1728 | } 1729 | 1730 | 1731 | //////////////// 1732 | // MiniMeTokenFactory 1733 | //////////////// 1734 | 1735 | /// @dev This contract is used to generate clone contracts from a contract. 1736 | /// In solidity this is the way to create a contract from a contract of the 1737 | /// same class 1738 | contract MiniMeTokenFactory { 1739 | 1740 | /// @notice Update the DApp by creating a new token with new functionalities 1741 | /// the msg.sender becomes the controller of this clone token 1742 | /// @param _parentToken Address of the token being cloned 1743 | /// @param _snapshotBlock Block of the parent token that will 1744 | /// determine the initial distribution of the clone token 1745 | /// @param _tokenName Name of the new token 1746 | /// @param _decimalUnits Number of decimals of the new token 1747 | /// @param _tokenSymbol Token Symbol for the new token 1748 | /// @param _transfersEnabled If true, tokens will be able to be transferred 1749 | /// @return The address of the new token contract 1750 | function createCloneToken( 1751 | MiniMeToken _parentToken, 1752 | uint _snapshotBlock, 1753 | string _tokenName, 1754 | uint8 _decimalUnits, 1755 | string _tokenSymbol, 1756 | bool _transfersEnabled 1757 | ) public returns (MiniMeToken) 1758 | { 1759 | MiniMeToken newToken = new MiniMeToken( 1760 | this, 1761 | _parentToken, 1762 | _snapshotBlock, 1763 | _tokenName, 1764 | _decimalUnits, 1765 | _tokenSymbol, 1766 | _transfersEnabled 1767 | ); 1768 | 1769 | newToken.changeController(msg.sender); 1770 | return newToken; 1771 | } 1772 | } 1773 | 1774 | 1775 | contract Voting is IForwarder, AragonApp { 1776 | using SafeMath for uint256; 1777 | using SafeMath64 for uint64; 1778 | 1779 | bytes32 public constant CREATE_VOTES_ROLE = keccak256("CREATE_VOTES_ROLE"); 1780 | bytes32 public constant MODIFY_SUPPORT_ROLE = keccak256("MODIFY_SUPPORT_ROLE"); 1781 | bytes32 public constant MODIFY_QUORUM_ROLE = keccak256("MODIFY_QUORUM_ROLE"); 1782 | 1783 | bytes32 public constant SET_MIN_BALANCE_ROLE = keccak256("SET_MIN_BALANCE_ROLE"); //keccak256("SET_MIN_BALANCE_ROLE") 1784 | bytes32 public constant SET_MIN_TIME_ROLE = keccak256("SET_MIN_TIME_ROLE"); //keccak256("SET_MIN_TIME_ROLE") 1785 | 1786 | uint64 public constant PCT_BASE = 10 ** 18; // 0% = 0; 1% = 10^16; 100% = 10^18 1787 | 1788 | string private constant ERROR_NO_VOTE = "VOTING_NO_VOTE"; 1789 | string private constant ERROR_INIT_PCTS = "VOTING_INIT_PCTS"; 1790 | string private constant ERROR_CHANGE_SUPPORT_PCTS = "VOTING_CHANGE_SUPPORT_PCTS"; 1791 | string private constant ERROR_CHANGE_QUORUM_PCTS = "VOTING_CHANGE_QUORUM_PCTS"; 1792 | string private constant ERROR_INIT_SUPPORT_TOO_BIG = "VOTING_INIT_SUPPORT_TOO_BIG"; 1793 | string private constant ERROR_CHANGE_SUPPORT_TOO_BIG = "VOTING_CHANGE_SUPP_TOO_BIG"; 1794 | string private constant ERROR_CAN_NOT_VOTE = "VOTING_CAN_NOT_VOTE"; 1795 | string private constant ERROR_CAN_NOT_EXECUTE = "VOTING_CAN_NOT_EXECUTE"; 1796 | string private constant ERROR_CAN_NOT_FORWARD = "VOTING_CAN_NOT_FORWARD"; 1797 | string private constant ERROR_NO_VOTING_POWER = "VOTING_NO_VOTING_POWER"; 1798 | 1799 | enum VoterState { Absent, Yea, Nay } 1800 | 1801 | struct Vote { 1802 | bool executed; 1803 | uint64 startDate; 1804 | uint64 snapshotBlock; 1805 | uint64 supportRequiredPct; 1806 | uint64 minAcceptQuorumPct; 1807 | uint256 yea; 1808 | uint256 nay; 1809 | uint256 votingPower; 1810 | bytes executionScript; 1811 | mapping (address => VoterState) voters; 1812 | } 1813 | 1814 | MiniMeToken public token; 1815 | uint64 public supportRequiredPct; 1816 | uint64 public minAcceptQuorumPct; 1817 | uint64 public voteTime; 1818 | 1819 | uint256 public minBalanceLowerLimit = 2500000000000000000000; 1820 | uint256 public minTimeLowerLimit = 43200; 1821 | uint256 public minTimeUpperLimit = 1209600; 1822 | 1823 | uint256 public minBalance; 1824 | uint256 public minTime; 1825 | 1826 | // We are mimicing an array, we use a mapping instead to make app upgrade more graceful 1827 | mapping (uint256 => Vote) internal votes; 1828 | uint256 public votesLength; 1829 | 1830 | mapping(address => uint256) public lastCreateVoteTimes; 1831 | 1832 | event StartVote(uint256 indexed voteId, address indexed creator, string metadata); 1833 | event CastVote(uint256 indexed voteId, address indexed voter, bool supports, uint256 stake); 1834 | event ExecuteVote(uint256 indexed voteId); 1835 | event ChangeSupportRequired(uint64 supportRequiredPct); 1836 | event ChangeMinQuorum(uint64 minAcceptQuorumPct); 1837 | 1838 | event MinimumBalanceSet(uint256 minBalance); 1839 | event MinimumTimeSet(uint256 minTime); 1840 | 1841 | modifier voteExists(uint256 _voteId) { 1842 | require(_voteId < votesLength, ERROR_NO_VOTE); 1843 | _; 1844 | } 1845 | 1846 | modifier minBalanceCheck(uint256 _minBalance) { 1847 | //_minBalance to be at least the equivalent of 10k locked for a year (1e18 precision) 1848 | require(_minBalance >= minBalanceLowerLimit, "Not enough min balance"); 1849 | _; 1850 | } 1851 | 1852 | modifier minTimeCheck(uint256 _minTime) { 1853 | require(_minTime >= minTimeLowerLimit && _minTime <= minTimeUpperLimit, "Min time can't be less than half a day and more than 2 weeks"); 1854 | _; 1855 | } 1856 | 1857 | /** 1858 | * @notice Initialize Voting app with `_token.symbol(): string` for governance, minimum support of `@formatPct(_supportRequiredPct)`%, minimum acceptance quorum of `@formatPct(_minAcceptQuorumPct)`%, and a voting duration of `@transformTime(_voteTime)` 1859 | * @param _token MiniMeToken Address that will be used as governance token 1860 | * @param _supportRequiredPct Percentage of yeas in casted votes for a vote to succeed (expressed as a percentage of 10^18; eg. 10^16 = 1%, 10^18 = 100%) 1861 | * @param _minAcceptQuorumPct Percentage of yeas in total possible votes for a vote to succeed (expressed as a percentage of 10^18; eg. 10^16 = 1%, 10^18 = 100%) 1862 | * @param _voteTime Seconds that a vote will be open for token holders to vote (unless enough yeas or nays have been cast to make an early decision) 1863 | */ 1864 | function initialize(MiniMeToken _token, 1865 | uint64 _supportRequiredPct, 1866 | uint64 _minAcceptQuorumPct, 1867 | uint64 _voteTime, 1868 | uint256 _minBalance, 1869 | uint256 _minTime, 1870 | uint256 _minBalanceLowerLimit, 1871 | uint256 _minTimeLowerLimit, 1872 | uint256 _minTimeUpperLimit 1873 | ) external onlyInit { 1874 | initialized(); 1875 | 1876 | require(_minAcceptQuorumPct <= _supportRequiredPct, ERROR_INIT_PCTS); 1877 | require(_supportRequiredPct < PCT_BASE, ERROR_INIT_SUPPORT_TOO_BIG); 1878 | 1879 | token = _token; 1880 | supportRequiredPct = _supportRequiredPct; 1881 | minAcceptQuorumPct = _minAcceptQuorumPct; 1882 | voteTime = _voteTime; 1883 | 1884 | minBalance = _minBalance; 1885 | minTime = _minTime; 1886 | 1887 | minBalanceLowerLimit = _minBalanceLowerLimit; 1888 | minTimeLowerLimit = _minTimeLowerLimit; 1889 | minTimeUpperLimit = _minTimeUpperLimit; 1890 | } 1891 | 1892 | /** 1893 | * @notice Change required support to `@formatPct(_supportRequiredPct)`% 1894 | * @param _supportRequiredPct New required support 1895 | */ 1896 | function changeSupportRequiredPct(uint64 _supportRequiredPct) 1897 | external 1898 | authP(MODIFY_SUPPORT_ROLE, arr(uint256(_supportRequiredPct), uint256(supportRequiredPct))) 1899 | { 1900 | require(minAcceptQuorumPct <= _supportRequiredPct, ERROR_CHANGE_SUPPORT_PCTS); 1901 | require(_supportRequiredPct < PCT_BASE, ERROR_CHANGE_SUPPORT_TOO_BIG); 1902 | supportRequiredPct = _supportRequiredPct; 1903 | 1904 | emit ChangeSupportRequired(_supportRequiredPct); 1905 | } 1906 | 1907 | /** 1908 | * @notice Change minimum acceptance quorum to `@formatPct(_minAcceptQuorumPct)`% 1909 | * @param _minAcceptQuorumPct New acceptance quorum 1910 | */ 1911 | function changeMinAcceptQuorumPct(uint64 _minAcceptQuorumPct) 1912 | external 1913 | authP(MODIFY_QUORUM_ROLE, arr(uint256(_minAcceptQuorumPct), uint256(minAcceptQuorumPct))) 1914 | { 1915 | require(_minAcceptQuorumPct <= supportRequiredPct, ERROR_CHANGE_QUORUM_PCTS); 1916 | minAcceptQuorumPct = _minAcceptQuorumPct; 1917 | 1918 | emit ChangeMinQuorum(_minAcceptQuorumPct); 1919 | } 1920 | 1921 | function setMinBalance(uint256 _minBalance) external auth(SET_MIN_BALANCE_ROLE) minBalanceCheck(_minBalance) { 1922 | //min balance can't be set to lower than 10k * 1 year 1923 | minBalance = _minBalance; 1924 | 1925 | emit MinimumBalanceSet(_minBalance); 1926 | } 1927 | 1928 | function setMinTime(uint256 _minTime) external auth(SET_MIN_TIME_ROLE) minTimeCheck(_minTime) { 1929 | //min time can't be less than half a day and more than 2 weeks 1930 | minTime = _minTime; 1931 | 1932 | emit MinimumTimeSet(_minTime); 1933 | } 1934 | 1935 | /** 1936 | * @notice Create a new vote about "`_metadata`" 1937 | * @param _executionScript EVM script to be executed on approval 1938 | * @param _metadata Vote metadata 1939 | * @return voteId Id for newly created vote 1940 | */ 1941 | function newVote(bytes _executionScript, string _metadata) external auth(CREATE_VOTES_ROLE) returns (uint256 voteId) { 1942 | return _newVote(_executionScript, _metadata, true, true); 1943 | } 1944 | 1945 | /** 1946 | * @notice Create a new vote about "`_metadata`" 1947 | * @param _executionScript EVM script to be executed on approval 1948 | * @param _metadata Vote metadata 1949 | * @param _castVote Whether to also cast newly created vote 1950 | * @param _executesIfDecided Whether to also immediately execute newly created vote if decided 1951 | * @return voteId id for newly created vote 1952 | */ 1953 | function newVote(bytes _executionScript, string _metadata, bool _castVote, bool _executesIfDecided) 1954 | external 1955 | auth(CREATE_VOTES_ROLE) 1956 | returns (uint256 voteId) 1957 | { 1958 | return _newVote(_executionScript, _metadata, _castVote, _executesIfDecided); 1959 | } 1960 | 1961 | /** 1962 | * @notice Vote `_supports ? 'yes' : 'no'` in vote #`_voteId` 1963 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 1964 | * created via `newVote(),` which requires initialization 1965 | * @param _voteId Id for vote 1966 | * @param _supports Whether voter supports the vote 1967 | * @param _executesIfDecided Whether the vote should execute its action if it becomes decided 1968 | */ 1969 | function vote(uint256 _voteId, bool _supports, bool _executesIfDecided) external voteExists(_voteId) { 1970 | require(_canVote(_voteId, msg.sender), ERROR_CAN_NOT_VOTE); 1971 | _vote(_voteId, _supports, msg.sender, _executesIfDecided); 1972 | } 1973 | 1974 | /** 1975 | * @notice Execute vote #`_voteId` 1976 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 1977 | * created via `newVote(),` which requires initialization 1978 | * @param _voteId Id for vote 1979 | */ 1980 | function executeVote(uint256 _voteId) external voteExists(_voteId) { 1981 | _executeVote(_voteId); 1982 | } 1983 | 1984 | // Forwarding fns 1985 | 1986 | /** 1987 | * @notice Tells whether the Voting app is a forwarder or not 1988 | * @dev IForwarder interface conformance 1989 | * @return Always true 1990 | */ 1991 | function isForwarder() external pure returns (bool) { 1992 | return true; 1993 | } 1994 | 1995 | /** 1996 | * @notice Creates a vote to execute the desired action, and casts a support vote if possible 1997 | * @dev IForwarder interface conformance 1998 | * @param _evmScript Start vote with script 1999 | */ 2000 | function forward(bytes _evmScript) public { 2001 | require(canForward(msg.sender, _evmScript), ERROR_CAN_NOT_FORWARD); 2002 | _newVote(_evmScript, "", true, true); 2003 | } 2004 | 2005 | /** 2006 | * @notice Tells whether `_sender` can forward actions or not 2007 | * @dev IForwarder interface conformance 2008 | * @param _sender Address of the account intending to forward an action 2009 | * @return True if the given address can create votes, false otherwise 2010 | */ 2011 | function canForward(address _sender, bytes) public view returns (bool) { 2012 | // Note that `canPerform()` implicitly does an initialization check itself 2013 | return canPerform(_sender, CREATE_VOTES_ROLE, arr()); 2014 | } 2015 | 2016 | // Getter fns 2017 | 2018 | /** 2019 | * @notice Tells whether a vote #`_voteId` can be executed or not 2020 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 2021 | * created via `newVote(),` which requires initialization 2022 | * @return True if the given vote can be executed, false otherwise 2023 | */ 2024 | function canExecute(uint256 _voteId) public view voteExists(_voteId) returns (bool) { 2025 | return _canExecute(_voteId); 2026 | } 2027 | 2028 | /** 2029 | * @notice Tells whether `_sender` can participate in the vote #`_voteId` or not 2030 | * @dev Initialization check is implicitly provided by `voteExists()` as new votes can only be 2031 | * created via `newVote(),` which requires initialization 2032 | * @return True if the given voter can participate a certain vote, false otherwise 2033 | */ 2034 | function canVote(uint256 _voteId, address _voter) public view voteExists(_voteId) returns (bool) { 2035 | return _canVote(_voteId, _voter); 2036 | } 2037 | 2038 | function canCreateNewVote() public returns(bool) { 2039 | return token.balanceOf(msg.sender) >= minBalance && block.timestamp.sub(minTime) >= lastCreateVoteTimes[msg.sender]; 2040 | } 2041 | 2042 | /** 2043 | * @dev Return all information for a vote by its ID 2044 | * @param _voteId Vote identifier 2045 | * @return Vote open status 2046 | * @return Vote executed status 2047 | * @return Vote start date 2048 | * @return Vote snapshot block 2049 | * @return Vote support required 2050 | * @return Vote minimum acceptance quorum 2051 | * @return Vote yeas amount 2052 | * @return Vote nays amount 2053 | * @return Vote power 2054 | * @return Vote script 2055 | */ 2056 | function getVote(uint256 _voteId) 2057 | public 2058 | view 2059 | voteExists(_voteId) 2060 | returns ( 2061 | bool open, 2062 | bool executed, 2063 | uint64 startDate, 2064 | uint64 snapshotBlock, 2065 | uint64 supportRequired, 2066 | uint64 minAcceptQuorum, 2067 | uint256 yea, 2068 | uint256 nay, 2069 | uint256 votingPower, 2070 | bytes script 2071 | ) 2072 | { 2073 | Vote storage vote_ = votes[_voteId]; 2074 | 2075 | open = _isVoteOpen(vote_); 2076 | executed = vote_.executed; 2077 | startDate = vote_.startDate; 2078 | snapshotBlock = vote_.snapshotBlock; 2079 | supportRequired = vote_.supportRequiredPct; 2080 | minAcceptQuorum = vote_.minAcceptQuorumPct; 2081 | yea = vote_.yea; 2082 | nay = vote_.nay; 2083 | votingPower = vote_.votingPower; 2084 | script = vote_.executionScript; 2085 | } 2086 | 2087 | /** 2088 | * @dev Return the state of a voter for a given vote by its ID 2089 | * @param _voteId Vote identifier 2090 | * @return VoterState of the requested voter for a certain vote 2091 | */ 2092 | function getVoterState(uint256 _voteId, address _voter) public view voteExists(_voteId) returns (VoterState) { 2093 | return votes[_voteId].voters[_voter]; 2094 | } 2095 | 2096 | // Internal fns 2097 | 2098 | /** 2099 | * @dev Internal function to create a new vote 2100 | * @return voteId id for newly created vote 2101 | */ 2102 | function _newVote(bytes _executionScript, string _metadata, bool _castVote, bool _executesIfDecided) internal returns (uint256 voteId) { 2103 | require(canCreateNewVote()); 2104 | uint64 snapshotBlock = getBlockNumber64() - 1; // avoid double voting in this very block 2105 | uint256 votingPower = token.totalSupplyAt(snapshotBlock); 2106 | require(votingPower > 0, ERROR_NO_VOTING_POWER); 2107 | 2108 | voteId = votesLength++; 2109 | 2110 | Vote storage vote_ = votes[voteId]; 2111 | vote_.startDate = getTimestamp64(); 2112 | vote_.snapshotBlock = snapshotBlock; 2113 | vote_.supportRequiredPct = supportRequiredPct; 2114 | vote_.minAcceptQuorumPct = minAcceptQuorumPct; 2115 | vote_.votingPower = votingPower; 2116 | vote_.executionScript = _executionScript; 2117 | 2118 | emit StartVote(voteId, msg.sender, _metadata); 2119 | 2120 | lastCreateVoteTimes[msg.sender] = getTimestamp64(); 2121 | 2122 | if (_castVote && _canVote(voteId, msg.sender)) { 2123 | _vote(voteId, true, msg.sender, _executesIfDecided); 2124 | } 2125 | } 2126 | 2127 | /** 2128 | * @dev Internal function to cast a vote. It assumes the queried vote exists. 2129 | */ 2130 | function _vote(uint256 _voteId, bool _supports, address _voter, bool _executesIfDecided) internal { 2131 | Vote storage vote_ = votes[_voteId]; 2132 | 2133 | VoterState state = vote_.voters[_voter]; 2134 | require(state == VoterState.Absent, "Can't change votes"); 2135 | // This could re-enter, though we can assume the governance token is not malicious 2136 | uint256 balance = token.balanceOfAt(_voter, vote_.snapshotBlock); 2137 | uint256 voterStake = uint256(2).mul(balance).mul(vote_.startDate.add(voteTime).sub(getTimestamp64())).div(voteTime); 2138 | if(voterStake > balance) { 2139 | voterStake = balance; 2140 | } 2141 | 2142 | if (_supports) { 2143 | vote_.yea = vote_.yea.add(voterStake); 2144 | } else { 2145 | vote_.nay = vote_.nay.add(voterStake); 2146 | } 2147 | 2148 | vote_.voters[_voter] = _supports ? VoterState.Yea : VoterState.Nay; 2149 | 2150 | emit CastVote(_voteId, _voter, _supports, voterStake); 2151 | 2152 | if (_executesIfDecided && _canExecute(_voteId)) { 2153 | // We've already checked if the vote can be executed with `_canExecute()` 2154 | _unsafeExecuteVote(_voteId); 2155 | } 2156 | } 2157 | 2158 | /** 2159 | * @dev Internal function to execute a vote. It assumes the queried vote exists. 2160 | */ 2161 | function _executeVote(uint256 _voteId) internal { 2162 | require(_canExecute(_voteId), ERROR_CAN_NOT_EXECUTE); 2163 | _unsafeExecuteVote(_voteId); 2164 | } 2165 | 2166 | /** 2167 | * @dev Unsafe version of _executeVote that assumes you have already checked if the vote can be executed and exists 2168 | */ 2169 | function _unsafeExecuteVote(uint256 _voteId) internal { 2170 | Vote storage vote_ = votes[_voteId]; 2171 | 2172 | vote_.executed = true; 2173 | 2174 | bytes memory input = new bytes(0); // TODO: Consider input for voting scripts 2175 | runScript(vote_.executionScript, input, new address[](0)); 2176 | 2177 | emit ExecuteVote(_voteId); 2178 | } 2179 | 2180 | /** 2181 | * @dev Internal function to check if a vote can be executed. It assumes the queried vote exists. 2182 | * @return True if the given vote can be executed, false otherwise 2183 | */ 2184 | function _canExecute(uint256 _voteId) internal view returns (bool) { 2185 | Vote storage vote_ = votes[_voteId]; 2186 | 2187 | if (vote_.executed) { 2188 | return false; 2189 | } 2190 | 2191 | // Voting is already decided 2192 | if (_isValuePct(vote_.yea, vote_.votingPower, vote_.supportRequiredPct)) { 2193 | return true; 2194 | } 2195 | 2196 | // Vote ended? 2197 | if (_isVoteOpen(vote_)) { 2198 | return false; 2199 | } 2200 | // Has enough support? 2201 | uint256 totalVotes = vote_.yea.add(vote_.nay); 2202 | if (!_isValuePct(vote_.yea, totalVotes, vote_.supportRequiredPct)) { 2203 | return false; 2204 | } 2205 | // Has min quorum? 2206 | if (!_isValuePct(vote_.yea, vote_.votingPower, vote_.minAcceptQuorumPct)) { 2207 | return false; 2208 | } 2209 | 2210 | return true; 2211 | } 2212 | 2213 | /** 2214 | * @dev Internal function to check if a voter can participate on a vote. It assumes the queried vote exists. 2215 | * @return True if the given voter can participate a certain vote, false otherwise 2216 | */ 2217 | function _canVote(uint256 _voteId, address _voter) internal view returns (bool) { 2218 | Vote storage vote_ = votes[_voteId]; 2219 | return _isVoteOpen(vote_) && token.balanceOfAt(_voter, vote_.snapshotBlock) > 0; 2220 | } 2221 | 2222 | /** 2223 | * @dev Internal function to check if a vote is still open 2224 | * @return True if the given vote is open, false otherwise 2225 | */ 2226 | function _isVoteOpen(Vote storage vote_) internal view returns (bool) { 2227 | return getTimestamp64() < vote_.startDate.add(voteTime) && !vote_.executed; 2228 | } 2229 | 2230 | /** 2231 | * @dev Calculates whether `_value` is more than a percentage `_pct` of `_total` 2232 | */ 2233 | function _isValuePct(uint256 _value, uint256 _total, uint256 _pct) internal pure returns (bool) { 2234 | if (_total == 0) { 2235 | return false; 2236 | } 2237 | 2238 | uint256 computedPct = _value.mul(PCT_BASE) / _total; 2239 | return computedPct > _pct; 2240 | } 2241 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curve-aragon-voting", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "compile": "hardhat compile", 11 | "chain": "hardhat node", 12 | "test": "hardhat test", 13 | "publish:major": "hardhat publish major", 14 | "publish:minor": "hardhat publish minor", 15 | "publish:patch": "hardhat publish patch" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/pengiundev/curve-aragon-voting.git" 20 | }, 21 | "author": "", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/pengiundev/curve-aragon-voting/issues" 25 | }, 26 | "homepage": "https://github.com/pengiundev/curve-aragon-voting#readme", 27 | "dependencies": { 28 | "@aragon/apps-shared-minime": "^1.0.2", 29 | "@aragon/os": "^4.4.0", 30 | "@openzeppelin/contracts": "^3.2.0" 31 | }, 32 | "devDependencies": { 33 | "@1hive/hardhat-aragon": "^0.1.0", 34 | "@nomiclabs/hardhat-ethers": "^2.0.2", 35 | "@nomiclabs/hardhat-waffle": "^2.0.1", 36 | "@typechain/ethers-v5": "^8.0.2", 37 | "@typechain/hardhat": "^3.0.0", 38 | "@types/chai": "^4.2.22", 39 | "@types/mocha": "^9.0.0", 40 | "@types/node": "^16.11.7", 41 | "chai": "^4.2.0", 42 | "dotenv": "^9.0.2", 43 | "ethereum-waffle": "^3.0.0", 44 | "ethers": "^5.1.4", 45 | "hardhat": "^2.6.5", 46 | "hardhat-deploy": "^0.9.3", 47 | "ts-generator": "^0.1.1", 48 | "ts-node": "^10.4.0", 49 | "typechain": "^6.0.2", 50 | "typescript": "^4.4.4" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/helpers/acl.ts: -------------------------------------------------------------------------------- 1 | export const ANY_ENTITY = "0x" + "f".repeat(40); // 0xffff...ffff 2 | -------------------------------------------------------------------------------- /test/helpers/addresses.ts: -------------------------------------------------------------------------------- 1 | export const ZERO_ADDRESS = "0x" + "0".repeat(40); // 0x0000...0000 2 | -------------------------------------------------------------------------------- /test/helpers/apps.ts: -------------------------------------------------------------------------------- 1 | import { Kernel } from "../../typechain"; 2 | import { getEventArgument } from "./events"; 3 | 4 | export const installNewApp = async ( 5 | dao: Kernel, 6 | appId: string, 7 | baseAppAddress: string 8 | ): Promise => { 9 | const receipt = await dao["newAppInstance(bytes32,address,bytes,bool)"]( 10 | appId, 11 | baseAppAddress, 12 | "0x", 13 | false 14 | ); 15 | const proxyAddress = await getEventArgument( 16 | dao, 17 | receipt.hash, 18 | "NewAppProxy", 19 | "proxy" 20 | ); 21 | 22 | return proxyAddress; 23 | }; 24 | -------------------------------------------------------------------------------- /test/helpers/dao.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ethers } from "hardhat"; 3 | import { ACL, DAOFactory, Kernel } from "../../typechain"; 4 | import { getEventArgument } from "./events"; 5 | 6 | export const newDaoFactory = async (): Promise => { 7 | const Kernel = await ethers.getContractFactory("Kernel"); 8 | const ACL = await ethers.getContractFactory("ACL"); 9 | const EVMScriptRegistryFactory = await ethers.getContractFactory( 10 | "EVMScriptRegistryFactory" 11 | ); 12 | const DAOFactory = await ethers.getContractFactory("DAOFactory"); 13 | 14 | const kernelBase = await Kernel.deploy(true); 15 | const aclBase = await ACL.deploy(); 16 | const registryFactory = await EVMScriptRegistryFactory.deploy(); 17 | 18 | return (await DAOFactory.deploy( 19 | kernelBase.address, 20 | aclBase.address, 21 | registryFactory.address 22 | )) as DAOFactory; 23 | }; 24 | 25 | export const newDao = async ( 26 | root: SignerWithAddress 27 | ): Promise<[Kernel, ACL]> => { 28 | const daoFactory = await newDaoFactory(); 29 | const rootAddress = await root.getAddress(); 30 | 31 | const daoReceipt = await (await daoFactory.newDAO(rootAddress)).wait(); 32 | const daoAddress = await getEventArgument( 33 | daoFactory, 34 | daoReceipt.transactionHash, 35 | "DeployDAO", 36 | "dao" 37 | ); 38 | 39 | const kernel = (await ethers.getContractAt( 40 | "Kernel", 41 | daoAddress, 42 | root 43 | )) as Kernel; 44 | const APP_MANAGER_ROLE = await kernel.APP_MANAGER_ROLE(); 45 | const acl = (await ethers.getContractAt("ACL", await kernel.acl())) as ACL; 46 | 47 | // Grant the root account permission to install apps in the DAO 48 | await acl.createPermission( 49 | rootAddress, 50 | kernel.address, 51 | APP_MANAGER_ROLE, 52 | rootAddress 53 | ); 54 | 55 | return [kernel, acl]; 56 | }; 57 | -------------------------------------------------------------------------------- /test/helpers/events.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "@ethersproject/contracts"; 2 | 3 | export const getEventArgument = async ( 4 | contract: Contract, 5 | txHash: string, 6 | eventName: string, 7 | eventArg?: string 8 | ): Promise => { 9 | const filterFn = contract.filters[eventName]; 10 | 11 | if (!filterFn) { 12 | throw new Error(`Event ${eventName} not found in contract`); 13 | } 14 | 15 | const filter = filterFn(); 16 | const events = await contract.queryFilter(filter); 17 | // Filter both by tx hash and event signature hash 18 | const [event] = events.filter( 19 | (event) => 20 | event.transactionHash === txHash && event.topics[0] === filter.topics[0] 21 | ); 22 | 23 | if (eventArg) { 24 | const argValue = event.args[eventArg]; 25 | 26 | if (!argValue) { 27 | throw new Error(`Argument ${eventArg} not found in event ${eventName}`); 28 | } 29 | 30 | return argValue; 31 | } else { 32 | return event.args; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /test/helpers/evmscript.ts: -------------------------------------------------------------------------------- 1 | import { utils } from "ethers"; 2 | 3 | const { defaultAbiCoder } = utils; 4 | 5 | export const CALLSCRIPT_ID = "0x00000001"; 6 | 7 | export const encodeCallScript = ( 8 | actions: { to: string; data: string }[] 9 | ): string => { 10 | return actions.reduce((script: string, { to, data }) => { 11 | const address = defaultAbiCoder.encode(["address"], [to]); 12 | const dataLength = defaultAbiCoder.encode( 13 | ["uint256"], 14 | [(data.length - 2) / 2] 15 | ); 16 | 17 | return script + address.slice(26) + dataLength.slice(58) + data.slice(2); 18 | }, CALLSCRIPT_ID); 19 | }; 20 | -------------------------------------------------------------------------------- /test/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./addresses"; 2 | export * from "./acl"; 3 | export * from "./apps"; 4 | export * from "./dao"; 5 | export * from "./events"; 6 | export * from "./evmscript"; 7 | export * from "./numbers"; 8 | export * from "./time"; 9 | -------------------------------------------------------------------------------- /test/helpers/numbers.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "@ethersproject/bignumber"; 2 | 3 | export const toDecimals = ( 4 | amount: number | string, 5 | decimals = 18 6 | ): BigNumber => { 7 | const [integer, decimal] = String(amount).split("."); 8 | return BigNumber.from( 9 | (integer != "0" ? integer : "") + (decimal || "").padEnd(decimals, "0") || 10 | "0" 11 | ); 12 | }; 13 | 14 | export const pct16 = (x: number | string) => toDecimals(x, 16); 15 | -------------------------------------------------------------------------------- /test/helpers/time.ts: -------------------------------------------------------------------------------- 1 | export const ONE_MINUTE = 60; 2 | export const ONE_HOUR = 60 * ONE_MINUTE; 3 | export const ONE_DAY = 24 ^ ONE_HOUR; 4 | -------------------------------------------------------------------------------- /test/voting.test.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "@ethersproject/bignumber"; 2 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 3 | import { expect } from "chai"; 4 | import { ethers } from "hardhat"; 5 | import { 6 | increase, 7 | restoreSnapshot, 8 | takeSnapshot, 9 | } from "../helpers/rpc"; 10 | import { 11 | ACL, 12 | ExecutionTarget, 13 | Kernel, 14 | MiniMeToken, 15 | Voting, 16 | } from "../typechain"; 17 | import { 18 | ANY_ENTITY, 19 | encodeCallScript, 20 | getEventArgument, 21 | installNewApp, 22 | newDao, 23 | ONE_MINUTE, 24 | pct16, 25 | toDecimals, 26 | ZERO_ADDRESS, 27 | } from "./helpers"; 28 | 29 | const { utils } = ethers; 30 | 31 | describe("Voting", function () { 32 | let signers: SignerWithAddress[]; 33 | let root: SignerWithAddress; 34 | let voteCreator: SignerWithAddress; 35 | let voter: SignerWithAddress; 36 | let dao: Kernel; 37 | let acl: ACL; 38 | let votingBase: Voting; 39 | let voting: Voting; 40 | let token: MiniMeToken; 41 | let executionTarget: ExecutionTarget; 42 | const appId = utils.namehash("crv-voting.open.aragonpm.eth"); 43 | 44 | // Initialization params 45 | const supportRequired = pct16(50); 46 | const minAcceptQuorum = pct16(20); 47 | const voteTime = 10 * ONE_MINUTE; 48 | const minTime = 10 * ONE_MINUTE; 49 | const minBalance = 1; 50 | const minBalanceLowerLimit = 1; 51 | const minBalanceUpperLimit = 10; 52 | const minTimeLowerLimit = 5 * ONE_MINUTE; 53 | const mintTimeUpperLimit = 15 * ONE_MINUTE; 54 | 55 | let snapshotId: string; 56 | 57 | const useSnapshot = async (): Promise => { 58 | await restoreSnapshot(snapshotId); 59 | 60 | snapshotId = await takeSnapshot(); 61 | }; 62 | 63 | before("Set up DAO", async () => { 64 | signers = await ethers.getSigners(); 65 | [root, voteCreator, voter] = signers; 66 | 67 | [dao, acl] = await newDao(root); 68 | }); 69 | 70 | before("Install Voting", async () => { 71 | const Voting = await ethers.getContractFactory("Voting"); 72 | votingBase = (await Voting.deploy()) as Voting; 73 | const votingAddress = await installNewApp(dao, appId, votingBase.address); 74 | voting = (await ethers.getContractAt( 75 | "Voting", 76 | votingAddress, 77 | root 78 | )) as Voting; 79 | 80 | const CREATE_VOTES_ROLE = await votingBase.CREATE_VOTES_ROLE(); 81 | await acl.createPermission( 82 | ANY_ENTITY, 83 | voting.address, 84 | CREATE_VOTES_ROLE, 85 | root.address 86 | ); 87 | }); 88 | 89 | before("Initialize Voting", async () => { 90 | const MiniMeToken = await ethers.getContractFactory("MiniMeToken"); 91 | const tokenDecimals = 18; 92 | token = (await MiniMeToken.deploy( 93 | ZERO_ADDRESS, 94 | ZERO_ADDRESS, 95 | 0, 96 | "Test Token", 97 | tokenDecimals, 98 | "TT", 99 | true 100 | )) as MiniMeToken; 101 | 102 | await token.generateTokens( 103 | voteCreator.address, 104 | toDecimals(10, tokenDecimals) 105 | ); 106 | await token.generateTokens(voter.address, toDecimals(10, tokenDecimals)); 107 | 108 | await voting.initialize( 109 | token.address, 110 | supportRequired, 111 | minAcceptQuorum, 112 | voteTime, 113 | minBalance, 114 | minTime, 115 | minBalanceLowerLimit, 116 | minBalanceUpperLimit, 117 | minTimeLowerLimit, 118 | mintTimeUpperLimit 119 | ); 120 | 121 | const ExecutionTarget = await ethers.getContractFactory("ExecutionTarget"); 122 | executionTarget = (await ExecutionTarget.deploy()) as ExecutionTarget; 123 | 124 | snapshotId = await takeSnapshot(); 125 | }); 126 | 127 | describe("when creating a vote", () => { 128 | let voteCreatorVoting: Voting; 129 | 130 | let voteId: BigNumber; 131 | 132 | beforeEach("Create vote", async () => { 133 | await useSnapshot(); 134 | 135 | voteCreatorVoting = voting.connect(voteCreator); 136 | 137 | const action = { 138 | to: executionTarget.address, 139 | data: executionTarget.interface.encodeFunctionData("execute"), 140 | }; 141 | 142 | // Create a first vote to run following tests with a non-zero voteId 143 | await voteCreatorVoting["newVote(bytes,string)"]( 144 | encodeCallScript([action]), 145 | "" 146 | ); 147 | await increase(voteTime.toString()); 148 | 149 | const receipt = await voteCreatorVoting["newVote(bytes,string)"]( 150 | encodeCallScript([action]), 151 | "" 152 | ); 153 | 154 | voteId = await getEventArgument( 155 | voting, 156 | receipt.hash, 157 | "StartVote", 158 | "voteId" 159 | ); 160 | }); 161 | 162 | describe("when casting a vote", () => { 163 | let voterVoting: Voting; 164 | 165 | let voterStake: BigNumber; 166 | 167 | const votePct = (pct: number | string): BigNumber => 168 | BigNumber.from(pct16(pct)); 169 | 170 | const encodeVoteData = ( 171 | voteId: BigNumber, 172 | yeaPct: BigNumber, 173 | nayPct: BigNumber 174 | ): BigNumber => BigNumber.from(yeaPct).shl(64).or(nayPct).shl(128).or(voteId); 175 | 176 | 177 | const isValidVoteProportions = async (voterYeaPct: BigNumber, voterNayPct: BigNumber) => { 178 | const vote = await voting.getVote(voteId); 179 | // Creator voted yes when created the vote 180 | const voteCreatorStake = await token.balanceOf(voteCreator.address); 181 | const voterYeaStake = voterStake 182 | .mul(voterYeaPct) 183 | .div(pct16(100)); 184 | const voterNayStake = voterStake 185 | .mul(voterNayPct) 186 | .div(pct16(100)); 187 | 188 | expect( 189 | vote.yea, 190 | "Incorrect yea proportion" 191 | ).to.be.equal(voterYeaStake.add(voteCreatorStake)); 192 | expect(vote.nay, "Incorrect nay proportion").to.be.equal(voterNayStake); 193 | }; 194 | 195 | before("Prepare vote casting", async () => { 196 | console.log(" preparing vote casting"); 197 | voterVoting = voting.connect(voter); 198 | voterStake = await token.balanceOf(voter.address); 199 | }); 200 | 201 | describe("when casting a continuous vote", () => { 202 | it("should emit two correct CastVote events", async () => { 203 | const yeaPct = votePct(66); 204 | const nayPct = votePct(34); 205 | const voteData = encodeVoteData(voteId, yeaPct, nayPct); 206 | 207 | const vote = await voterVoting.vote(voteData, false, false); 208 | 209 | expect(vote) 210 | .to.emit(voterVoting, "CastVote") 211 | .withArgs(voteId, voter.address, true, voterStake.mul(yeaPct).div(pct16(100))); 212 | 213 | expect(vote) 214 | .to.emit(voterVoting, "CastVote") 215 | .withArgs(voteId, voter.address, false, voterStake.mul(nayPct).div(pct16(100))); 216 | }); 217 | 218 | it("should cast the correct proportions of yea and nay", async () => { 219 | const voterYeaPct = votePct(73); 220 | const voterNayPct = votePct(100 - 73); 221 | const voteData = encodeVoteData(voteId, voterYeaPct, voterNayPct); 222 | 223 | await voterVoting.vote(voteData, false, false); 224 | 225 | await isValidVoteProportions(voterYeaPct, voterNayPct); 226 | }); 227 | 228 | it("should cast the correct proportions of yea and nay when not all tokens are used", async () => { 229 | const voterYeaPct = votePct(20); 230 | const voterNayPct = votePct(50); 231 | const voteData = encodeVoteData(voteId, voterYeaPct, voterNayPct); 232 | 233 | await voterVoting.vote(voteData, false, false); 234 | 235 | await isValidVoteProportions(voterYeaPct, voterNayPct); 236 | }); 237 | 238 | it("should return even as voter state when trying to cast a 50% vote", async () => { 239 | const voterYeaPct = votePct(50); 240 | const voterNayPct = votePct(50); 241 | const voteData = encodeVoteData(voteId, voterYeaPct, voterNayPct); 242 | await voterVoting.vote(voteData, false, false); 243 | 244 | expect(await voterVoting.getVoterState(voteId, voter.address)).to.be.equal(3); // 3 = Even 245 | }); 246 | 247 | it("should revert when pctYea and pctNay exced 100%", async () => { 248 | const voterYeaPct = votePct(32); 249 | const voterNayPct = votePct(70); 250 | const voteData = encodeVoteData(voteId, voterYeaPct, voterNayPct); 251 | 252 | expect(voterVoting.vote(voteData, false, false)).to.be.revertedWith( 253 | "MALFORMED_CONTINUOUS_VOTE" 254 | ); 255 | }); 256 | }); 257 | 258 | describe("when casting a continuous vote with votePct", () => { 259 | it("should emit two correct CastVote events", async () => { 260 | const yeaPct = votePct(66); 261 | const nayPct = votePct(34); 262 | const voteData = encodeVoteData(voteId, yeaPct, nayPct); 263 | 264 | const vote = await voterVoting.votePct(voteId, yeaPct, nayPct, false); 265 | 266 | expect(vote) 267 | .to.emit(voterVoting, "CastVote") 268 | .withArgs(voteId, voter.address, true, voterStake.mul(yeaPct).div(pct16(100))); 269 | 270 | expect(vote) 271 | .to.emit(voterVoting, "CastVote") 272 | .withArgs(voteId, voter.address, false, voterStake.mul(nayPct).div(pct16(100))); 273 | }); 274 | 275 | it("should cast the correct proportions of yea and nay", async () => { 276 | const voterYeaPct = votePct(73); 277 | const voterNayPct = votePct(100 - 73); 278 | 279 | await voterVoting.votePct(voteId, voterYeaPct, voterNayPct, false); 280 | 281 | await isValidVoteProportions(voterYeaPct, voterNayPct); 282 | }); 283 | 284 | it("should cast the correct proportions of yea and nay when not all tokens are used", async () => { 285 | const voterYeaPct = votePct(20); 286 | const voterNayPct = votePct(50); 287 | 288 | await voterVoting.votePct(voteId, voterYeaPct, voterNayPct, false); 289 | 290 | await isValidVoteProportions(voterYeaPct, voterNayPct); 291 | }); 292 | 293 | it("should return even as voter state when trying to cast a 50% vote", async () => { 294 | const voterYeaPct = votePct(50); 295 | const voterNayPct = votePct(50); 296 | await voterVoting.votePct(voteId, voterYeaPct, voterNayPct, false); 297 | 298 | expect(await voterVoting.getVoterState(voteId, voter.address)).to.be.equal(3); // 3 = Even 299 | }); 300 | 301 | it("should revert when pctYea and pctNay exced 100%", async () => { 302 | const voterYeaPct = votePct(32); 303 | const voterNayPct = votePct(70); 304 | 305 | expect(voterVoting.votePct(voteId, voterYeaPct, voterNayPct, false)).to.be.revertedWith( 306 | "MALFORMED_CONTINUOUS_VOTE" 307 | ); 308 | }); 309 | }); 310 | 311 | // Backward-compatible check 312 | describe("when casting a discrete vote", () => { 313 | const yea = pct16(100); 314 | const nay = pct16(0); 315 | 316 | it("should emit a correct CastVote event when yay", async () => { 317 | expect(await voterVoting.vote(voteId, true, false)) 318 | .to.emit(voterVoting, "CastVote") 319 | .withArgs(voteId, voter.address, true, voterStake); 320 | }); 321 | 322 | it("should emit a correct CastVote event when nay", async () => { 323 | expect(await voterVoting.vote(voteId, false, false)) 324 | .to.emit(voterVoting, "CastVote") 325 | .withArgs(voteId, voter.address, false, voterStake); 326 | }); 327 | 328 | it("should cast a yea vote correctly", async () => { 329 | await voterVoting.vote(voteId, true, false); 330 | 331 | await isValidVoteProportions(yea, nay); 332 | }); 333 | 334 | it("should cast a nay vote correctly", async () => { 335 | await voterVoting.vote(voteId, false, false); 336 | 337 | await isValidVoteProportions(nay, yea); 338 | }); 339 | }); 340 | 341 | it("should revert when trying to cast a vote that is simultaneously discrete and continuous", async () => { 342 | const voterYeaPct = votePct(32); 343 | const voterNayPct = votePct(68); 344 | const voteData = encodeVoteData(voteId, voterYeaPct, voterNayPct); 345 | 346 | expect(voterVoting.vote(voteData, true, false)).to.be.revertedWith( 347 | "MALFORMED_CONTINUOUS_VOTE" 348 | ); 349 | }); 350 | }); 351 | }); 352 | }); 353 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": false, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true 9 | }, 10 | "include": ["./scripts", "./test", "./deploy", "./utils"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /utils/network.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | 3 | export function node_url(networkName: string): string { 4 | if (networkName) { 5 | const uri = process.env["ETH_NODE_URI_" + networkName.toUpperCase()]; 6 | if (uri && uri !== "") { 7 | return uri; 8 | } 9 | } 10 | 11 | if (networkName === "localhost") { 12 | // do not use ETH_NODE_URI 13 | return "http://localhost:8545"; 14 | } 15 | 16 | let uri = process.env.ETH_NODE_URI; 17 | if (uri) { 18 | uri = uri.replace("{{networkName}}", networkName); 19 | } 20 | if (!uri || uri === "") { 21 | // throw new Error(`environment variable "ETH_NODE_URI" not configured `); 22 | return ""; 23 | } 24 | if (uri.indexOf("{{") >= 0) { 25 | throw new Error( 26 | `invalid uri or network not supported by node provider : ${uri}` 27 | ); 28 | } 29 | return uri; 30 | } 31 | 32 | export function getMnemonic(networkName?: string): string { 33 | if (networkName) { 34 | const mnemonic = process.env["MNEMONIC_" + networkName.toUpperCase()]; 35 | if (mnemonic && mnemonic !== "") { 36 | return mnemonic; 37 | } 38 | } 39 | 40 | const mnemonic = process.env.MNEMONIC; 41 | if (!mnemonic || mnemonic === "") { 42 | return "test test test test test test test test test test test junk"; 43 | } 44 | return mnemonic; 45 | } 46 | 47 | export function accounts(networkName?: string): { mnemonic: string } { 48 | return { mnemonic: getMnemonic(networkName) }; 49 | } 50 | --------------------------------------------------------------------------------