├── .gitattributes ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .mocharc.json ├── .prettierrc ├── .yarnrc ├── README.md ├── contracts ├── Forth.sol ├── GovernorBravoDelegate.sol ├── GovernorBravoDelegator.sol ├── GovernorBravoInterfaces.sol ├── SafeMath.sol └── Timelock.sol ├── package.json ├── test ├── Forth.spec.ts ├── fixtures.ts └── utils.ts ├── tsconfig.json ├── waffle.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit Tests 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 12.x 19 | - run: yarn 20 | - run: yarn lint 21 | - run: yarn test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension": ["ts"], 3 | "spec": "./test/**/*.spec.ts", 4 | "require": "ts-node/register", 5 | "timeout": 12000 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | ignore-scripts true 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ampleforth Governance 2 | 3 | ## Mainnet 4 | * Forth: `0x77fba179c79de5b7653f68b5039af940ada60ce0` 5 | * Merkle Claim: `0xF497b83CfBd31E7Ba1Ab646F3B50aE0aF52d03A1` 6 | * Timelock: `0x223592a191ECfC7FDC38a9256c3BD96E771539A9` 7 | * GovernorBravoDelegator: `0x8a994C6F55Be1fD2B4d0dc3B8f8F7D4E3a2dA8F1` 8 | * GovernorBravoDelegate: `0x444133fe9488cbec995Fd28601B3B1aD01b7fb9c` 9 | 10 | 11 | ## Testnet (Rinkeby) 12 | * Forth: `0xe5A678b0602A150812bD414D5b00094F2CE7177e` 13 | * Merkle Claim: `0x4378974a9EA9C1Ad436E1B8A9E5179Ef1b934280` 14 | 15 | 16 | ## Testnet (Kovan) 17 | * Forth: `0xe6e69E7d8057Ef1abA70026f1f42A27F88688B05` 18 | * Timelock: `0xf19Ebe0108A6bceE04205215a0B71F08bf7F58fb` 19 | * GovernorBravoDelegator: `0xCcDC4F7e3261d375f735a07f3420e06a7F8faE95` 20 | * GovernorBravoDelegate: `0x578FDDE36a5cf00a147AD775BA093ebD1630B8b2` 21 | -------------------------------------------------------------------------------- /contracts/Forth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | 3 | pragma solidity 0.6.11; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./SafeMath.sol"; 7 | 8 | contract Forth { 9 | /// @notice EIP-20 token name for this token 10 | string public constant name = "Ampleforth Governance"; 11 | 12 | /// @notice EIP-20 token symbol for this token 13 | string public constant symbol = "FORTH"; 14 | 15 | /// @notice EIP-20 token decimals for this token 16 | uint8 public constant decimals = 18; 17 | 18 | /// @notice Total number of tokens in circulation 19 | uint public totalSupply = 15_000_000e18; // 15 million Forth 20 | 21 | /// @notice Address which may mint new tokens 22 | address public minter; 23 | 24 | /// @notice The timestamp after which minting may occur 25 | uint public mintingAllowedAfter; 26 | 27 | /// @notice Minimum time between mints 28 | uint32 public constant minimumTimeBetweenMints = 1 days * 365; 29 | 30 | /// @notice Cap on the percentage of totalSupply that can be minted at each mint 31 | uint8 public constant mintCap = 2; 32 | 33 | /// @dev Allowance amounts on behalf of others 34 | mapping (address => mapping (address => uint96)) internal allowances; 35 | 36 | /// @dev Official record of token balances for each account 37 | mapping (address => uint96) internal balances; 38 | 39 | /// @notice A record of each accounts delegate 40 | mapping (address => address) public delegates; 41 | 42 | /// @notice A checkpoint for marking number of votes from a given block 43 | struct Checkpoint { 44 | uint32 fromBlock; 45 | uint96 votes; 46 | } 47 | 48 | /// @notice A record of votes checkpoints for each account, by index 49 | mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; 50 | 51 | /// @notice The number of checkpoints for each account 52 | mapping (address => uint32) public numCheckpoints; 53 | 54 | /// @notice The EIP-712 typehash for the contract's domain 55 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 56 | 57 | /// @notice The EIP-712 typehash for the delegation struct used by the contract 58 | bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); 59 | 60 | /// @notice The EIP-712 typehash for the permit struct used by the contract 61 | bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); 62 | 63 | /// @notice A record of states for signing / validating signatures 64 | mapping (address => uint) public nonces; 65 | 66 | /// @notice An event thats emitted when the minter address is changed 67 | event MinterChanged(address minter, address newMinter); 68 | 69 | /// @notice An event thats emitted when an account changes its delegate 70 | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); 71 | 72 | /// @notice An event thats emitted when a delegate account's vote balance changes 73 | event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); 74 | 75 | /// @notice The standard EIP-20 transfer event 76 | event Transfer(address indexed from, address indexed to, uint256 amount); 77 | 78 | /// @notice The standard EIP-20 approval event 79 | event Approval(address indexed owner, address indexed spender, uint256 amount); 80 | 81 | /** 82 | * @notice Construct a new Forth token 83 | * @param account The initial account to grant all the tokens 84 | * @param minter_ The account with minting ability 85 | * @param mintingAllowedAfter_ The timestamp after which minting may occur 86 | */ 87 | constructor(address account, address minter_, uint mintingAllowedAfter_) public { 88 | require(mintingAllowedAfter_ >= block.timestamp, "Forth::constructor: minting can only begin after deployment"); 89 | 90 | balances[account] = uint96(totalSupply); 91 | emit Transfer(address(0), account, totalSupply); 92 | minter = minter_; 93 | emit MinterChanged(address(0), minter); 94 | mintingAllowedAfter = mintingAllowedAfter_; 95 | } 96 | 97 | /** 98 | * @notice Change the minter address 99 | * @param minter_ The address of the new minter 100 | */ 101 | function setMinter(address minter_) external { 102 | require(msg.sender == minter, "Forth::setMinter: only the minter can change the minter address"); 103 | emit MinterChanged(minter, minter_); 104 | minter = minter_; 105 | } 106 | 107 | /** 108 | * @notice Mint new tokens 109 | * @param dst The address of the destination account 110 | * @param rawAmount The number of tokens to be minted 111 | */ 112 | function mint(address dst, uint rawAmount) external { 113 | require(msg.sender == minter, "Forth::mint: only the minter can mint"); 114 | require(block.timestamp >= mintingAllowedAfter, "Forth::mint: minting not allowed yet"); 115 | require(dst != address(0), "Forth::mint: cannot transfer to the zero address"); 116 | 117 | // record the mint 118 | mintingAllowedAfter = SafeMath.add(block.timestamp, minimumTimeBetweenMints); 119 | 120 | // mint the amount 121 | uint96 amount = safe96(rawAmount, "Forth::mint: amount exceeds 96 bits"); 122 | require(amount <= SafeMath.div(SafeMath.mul(totalSupply, mintCap), 100), "Forth::mint: exceeded mint cap"); 123 | uint96 supply = safe96(totalSupply, "Forth::mint old totalSupply exceeds 96 bits"); 124 | totalSupply = add96(supply, amount, "Forth::mint: new totalSupply exceeds 96 bits"); 125 | 126 | // transfer the amount to the recipient 127 | balances[dst] = add96(balances[dst], amount, "Forth::mint: transfer amount overflows"); 128 | emit Transfer(address(0), dst, amount); 129 | 130 | // move delegates 131 | _moveDelegates(address(0), delegates[dst], amount); 132 | } 133 | 134 | /** 135 | * @notice Destroys `amount` tokens from the caller 136 | * @param rawAmount The number of tokens to burn 137 | */ 138 | function burn(uint256 rawAmount) external { 139 | uint96 amount = safe96(rawAmount, "Forth::burn: rawAmount exceeds 96 bits"); 140 | _burn(msg.sender, amount); 141 | } 142 | 143 | /** 144 | * @notice Destroys `amount` tokens from `account`, deducting from the caller's allowance 145 | * @param account The address of the account to burn from 146 | * @param rawAmount The number of tokens to burn 147 | */ 148 | function burnFrom(address account, uint256 rawAmount) external { 149 | uint96 amount = safe96(rawAmount, "Forth::burnFrom: rawAmount exceeds 96 bits"); 150 | 151 | uint96 decreasedAllowance = 152 | sub96(allowances[account][msg.sender], amount, "Forth::burnFrom: amount exceeds allowance"); 153 | allowances[account][msg.sender] = decreasedAllowance; 154 | emit Approval(account, msg.sender, decreasedAllowance); 155 | 156 | _burn(account, amount); 157 | } 158 | 159 | /** 160 | * @notice Destroys `amount` tokens from `account`, reducing the total supply 161 | * @param account The address of the account to burn from 162 | * @param amount The number of tokens to burn 163 | */ 164 | function _burn(address account, uint96 amount) internal { 165 | require(account != address(0), "Forth::_burn: burn from the zero address"); 166 | 167 | uint96 supply = safe96(totalSupply, "Forth::_burn: old supply exceeds 96 bits"); 168 | totalSupply = sub96(supply, amount, "Forth::_burn: amount exceeds totalSupply"); 169 | 170 | balances[account] = sub96(balances[account], amount, "Forth::_burn: amount exceeds balance"); 171 | emit Transfer(account, address(0), amount); 172 | 173 | // move delegates 174 | _moveDelegates(delegates[account], address(0), amount); 175 | } 176 | 177 | /** 178 | * @notice Get the number of tokens `spender` is approved to spend on behalf of `account` 179 | * @param account The address of the account holding the funds 180 | * @param spender The address of the account spending the funds 181 | * @return The number of tokens approved 182 | */ 183 | function allowance(address account, address spender) external view returns (uint) { 184 | return allowances[account][spender]; 185 | } 186 | 187 | /** 188 | * @notice Approve `spender` to transfer up to `amount` from `src` 189 | * @dev This will overwrite the approval amount for `spender` 190 | * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) 191 | * @param spender The address of the account which may transfer tokens 192 | * @param rawAmount The number of tokens that are approved (2^256-1 means infinite) 193 | * @return Whether or not the approval succeeded 194 | */ 195 | function approve(address spender, uint rawAmount) external returns (bool) { 196 | uint96 amount; 197 | if (rawAmount == type(uint).max) { 198 | amount = type(uint96).max; 199 | } else { 200 | amount = safe96(rawAmount, "Forth::approve: amount exceeds 96 bits"); 201 | } 202 | 203 | allowances[msg.sender][spender] = amount; 204 | 205 | emit Approval(msg.sender, spender, amount); 206 | return true; 207 | } 208 | 209 | /** 210 | * @notice Triggers an approval from owner to spends 211 | * @param owner The address to approve from 212 | * @param spender The address to be approved 213 | * @param rawAmount The number of tokens that are approved (2^256-1 means infinite) 214 | * @param deadline The time at which to expire the signature 215 | * @param v The recovery byte of the signature 216 | * @param r Half of the ECDSA signature pair 217 | * @param s Half of the ECDSA signature pair 218 | */ 219 | function permit(address owner, address spender, uint rawAmount, uint deadline, uint8 v, bytes32 r, bytes32 s) external { 220 | uint96 amount; 221 | if (rawAmount == type(uint).max) { 222 | amount = type(uint96).max; 223 | } else { 224 | amount = safe96(rawAmount, "Forth::permit: amount exceeds 96 bits"); 225 | } 226 | 227 | bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this))); 228 | bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, rawAmount, nonces[owner]++, deadline)); 229 | bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); 230 | address signatory = ecrecover(digest, v, r, s); 231 | require(signatory != address(0), "Forth::permit: invalid signature"); 232 | require(signatory == owner, "Forth::permit: unauthorized"); 233 | require(now <= deadline, "Forth::permit: signature expired"); 234 | 235 | allowances[owner][spender] = amount; 236 | 237 | emit Approval(owner, spender, amount); 238 | } 239 | 240 | /** 241 | * @notice Get the number of tokens held by the `account` 242 | * @param account The address of the account to get the balance of 243 | * @return The number of tokens held 244 | */ 245 | function balanceOf(address account) external view returns (uint) { 246 | return balances[account]; 247 | } 248 | 249 | /** 250 | * @notice Transfer `amount` tokens from `msg.sender` to `dst` 251 | * @param dst The address of the destination account 252 | * @param rawAmount The number of tokens to transfer 253 | * @return Whether or not the transfer succeeded 254 | */ 255 | function transfer(address dst, uint rawAmount) external returns (bool) { 256 | uint96 amount = safe96(rawAmount, "Forth::transfer: amount exceeds 96 bits"); 257 | _transferTokens(msg.sender, dst, amount); 258 | return true; 259 | } 260 | 261 | /** 262 | * @notice Transfer `amount` tokens from `src` to `dst` 263 | * @param src The address of the source account 264 | * @param dst The address of the destination account 265 | * @param rawAmount The number of tokens to transfer 266 | * @return Whether or not the transfer succeeded 267 | */ 268 | function transferFrom(address src, address dst, uint rawAmount) external returns (bool) { 269 | address spender = msg.sender; 270 | uint96 spenderAllowance = allowances[src][spender]; 271 | uint96 amount = safe96(rawAmount, "Forth::approve: amount exceeds 96 bits"); 272 | 273 | if (spender != src && spenderAllowance != type(uint96).max) { 274 | uint96 newAllowance = sub96(spenderAllowance, amount, "Forth::transferFrom: transfer amount exceeds spender allowance"); 275 | allowances[src][spender] = newAllowance; 276 | 277 | emit Approval(src, spender, newAllowance); 278 | } 279 | 280 | _transferTokens(src, dst, amount); 281 | return true; 282 | } 283 | 284 | /** 285 | * @notice Delegate votes from `msg.sender` to `delegatee` 286 | * @param delegatee The address to delegate votes to 287 | */ 288 | function delegate(address delegatee) public { 289 | return _delegate(msg.sender, delegatee); 290 | } 291 | 292 | /** 293 | * @notice Delegates votes from signatory to `delegatee` 294 | * @param delegatee The address to delegate votes to 295 | * @param nonce The contract state required to match the signature 296 | * @param expiry The time at which to expire the signature 297 | * @param v The recovery byte of the signature 298 | * @param r Half of the ECDSA signature pair 299 | * @param s Half of the ECDSA signature pair 300 | */ 301 | function delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) public { 302 | bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this))); 303 | bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)); 304 | bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); 305 | address signatory = ecrecover(digest, v, r, s); 306 | require(signatory != address(0), "Forth::delegateBySig: invalid signature"); 307 | require(nonce == nonces[signatory]++, "Forth::delegateBySig: invalid nonce"); 308 | require(now <= expiry, "Forth::delegateBySig: signature expired"); 309 | return _delegate(signatory, delegatee); 310 | } 311 | 312 | /** 313 | * @notice Gets the current votes balance for `account` 314 | * @param account The address to get votes balance 315 | * @return The number of current votes for `account` 316 | */ 317 | function getCurrentVotes(address account) external view returns (uint96) { 318 | uint32 nCheckpoints = numCheckpoints[account]; 319 | return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; 320 | } 321 | 322 | /** 323 | * @notice Determine the prior number of votes for an account as of a block number 324 | * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. 325 | * @param account The address of the account to check 326 | * @param blockNumber The block number to get the vote balance at 327 | * @return The number of votes the account had as of the given block 328 | */ 329 | function getPriorVotes(address account, uint blockNumber) public view returns (uint96) { 330 | require(blockNumber < block.number, "Forth::getPriorVotes: not yet determined"); 331 | 332 | uint32 nCheckpoints = numCheckpoints[account]; 333 | if (nCheckpoints == 0) { 334 | return 0; 335 | } 336 | 337 | // First check most recent balance 338 | if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { 339 | return checkpoints[account][nCheckpoints - 1].votes; 340 | } 341 | 342 | // Next check implicit zero balance 343 | if (checkpoints[account][0].fromBlock > blockNumber) { 344 | return 0; 345 | } 346 | 347 | uint32 lower = 0; 348 | uint32 upper = nCheckpoints - 1; 349 | while (upper > lower) { 350 | uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow 351 | Checkpoint memory cp = checkpoints[account][center]; 352 | if (cp.fromBlock == blockNumber) { 353 | return cp.votes; 354 | } else if (cp.fromBlock < blockNumber) { 355 | lower = center; 356 | } else { 357 | upper = center - 1; 358 | } 359 | } 360 | return checkpoints[account][lower].votes; 361 | } 362 | 363 | function _delegate(address delegator, address delegatee) internal { 364 | address currentDelegate = delegates[delegator]; 365 | uint96 delegatorBalance = balances[delegator]; 366 | delegates[delegator] = delegatee; 367 | 368 | emit DelegateChanged(delegator, currentDelegate, delegatee); 369 | 370 | _moveDelegates(currentDelegate, delegatee, delegatorBalance); 371 | } 372 | 373 | function _transferTokens(address src, address dst, uint96 amount) internal { 374 | require(src != address(0), "Forth::_transferTokens: cannot transfer from the zero address"); 375 | require(dst != address(0), "Forth::_transferTokens: cannot transfer to the zero address"); 376 | 377 | balances[src] = sub96(balances[src], amount, "Forth::_transferTokens: transfer amount exceeds balance"); 378 | balances[dst] = add96(balances[dst], amount, "Forth::_transferTokens: transfer amount overflows"); 379 | emit Transfer(src, dst, amount); 380 | 381 | _moveDelegates(delegates[src], delegates[dst], amount); 382 | } 383 | 384 | function _moveDelegates(address srcRep, address dstRep, uint96 amount) internal { 385 | if (srcRep != dstRep && amount > 0) { 386 | if (srcRep != address(0)) { 387 | uint32 srcRepNum = numCheckpoints[srcRep]; 388 | uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; 389 | uint96 srcRepNew = sub96(srcRepOld, amount, "Forth::_moveVotes: vote amount underflows"); 390 | _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); 391 | } 392 | 393 | if (dstRep != address(0)) { 394 | uint32 dstRepNum = numCheckpoints[dstRep]; 395 | uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; 396 | uint96 dstRepNew = add96(dstRepOld, amount, "Forth::_moveVotes: vote amount overflows"); 397 | _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); 398 | } 399 | } 400 | } 401 | 402 | function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes) internal { 403 | uint32 blockNumber = safe32(block.number, "Forth::_writeCheckpoint: block number exceeds 32 bits"); 404 | 405 | if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) { 406 | checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; 407 | } else { 408 | checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes); 409 | numCheckpoints[delegatee] = nCheckpoints + 1; 410 | } 411 | 412 | emit DelegateVotesChanged(delegatee, oldVotes, newVotes); 413 | } 414 | 415 | function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { 416 | require(n < 2**32, errorMessage); 417 | return uint32(n); 418 | } 419 | 420 | function safe96(uint n, string memory errorMessage) internal pure returns (uint96) { 421 | require(n < 2**96, errorMessage); 422 | return uint96(n); 423 | } 424 | 425 | function add96(uint96 a, uint96 b, string memory errorMessage) internal pure returns (uint96) { 426 | uint96 c = a + b; 427 | require(c >= a, errorMessage); 428 | return c; 429 | } 430 | 431 | function sub96(uint96 a, uint96 b, string memory errorMessage) internal pure returns (uint96) { 432 | require(b <= a, errorMessage); 433 | return a - b; 434 | } 435 | 436 | function getChainId() internal pure returns (uint) { 437 | uint256 chainId; 438 | assembly { chainId := chainid() } 439 | return chainId; 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /contracts/GovernorBravoDelegate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | 3 | pragma solidity 0.6.11; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./GovernorBravoInterfaces.sol"; 7 | 8 | contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoEvents { 9 | 10 | /// @notice The name of this contract 11 | string public constant name = "Ampleforth Governor Bravo"; 12 | 13 | /// @notice The minimum setable proposal threshold 14 | uint public constant MIN_PROPOSAL_THRESHOLD = 75000e18; // 75,000 Forth 15 | 16 | /// @notice The maximum setable proposal threshold 17 | uint public constant MAX_PROPOSAL_THRESHOLD = 300000e18; // 300,000 Forth 18 | 19 | /// @notice The minimum setable voting period 20 | uint public constant MIN_VOTING_PERIOD = 5760; // About 24 hours 21 | 22 | /// @notice The max setable voting period 23 | uint public constant MAX_VOTING_PERIOD = 80640; // About 2 weeks 24 | 25 | /// @notice The min setable voting delay 26 | uint public constant MIN_VOTING_DELAY = 1; 27 | 28 | /// @notice The max setable voting delay 29 | uint public constant MAX_VOTING_DELAY = 40320; // About 1 week 30 | 31 | /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed 32 | uint public constant quorumVotes = 600000e18; // 600,000 = 4% of Forth 33 | 34 | /// @notice The maximum number of actions that can be included in a proposal 35 | uint public constant proposalMaxOperations = 10; // 10 actions 36 | 37 | /// @notice The EIP-712 typehash for the contract's domain 38 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 39 | 40 | /// @notice The EIP-712 typehash for the ballot struct used by the contract 41 | bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); 42 | 43 | /** 44 | * @notice Used to initialize the contract during delegator contructor 45 | * @param timelock_ The address of the Timelock 46 | * @param forth_ The address of the FORTH token 47 | * @param votingPeriod_ The initial voting period 48 | * @param votingDelay_ The initial voting delay 49 | * @param proposalThreshold_ The initial proposal threshold 50 | */ 51 | function initialize(address timelock_, address forth_, uint votingPeriod_, uint votingDelay_, uint proposalThreshold_) public { 52 | require(address(timelock) == address(0), "GovernorBravo::initialize: can only initialize once"); 53 | require(msg.sender == admin, "GovernorBravo::initialize: admin only"); 54 | require(timelock_ != address(0), "GovernorBravo::initialize: invalid timelock address"); 55 | require(forth_ != address(0), "GovernorBravo::initialize: invalid forth address"); 56 | require(votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, "GovernorBravo::initialize: invalid voting period"); 57 | require(votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, "GovernorBravo::initialize: invalid voting delay"); 58 | require(proposalThreshold_ >= MIN_PROPOSAL_THRESHOLD && proposalThreshold_ <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::initialize: invalid proposal threshold"); 59 | 60 | timelock = TimelockInterface(timelock_); 61 | timelock.acceptAdmin(); 62 | forth = ForthInterface(forth_); 63 | votingPeriod = votingPeriod_; 64 | votingDelay = votingDelay_; 65 | proposalThreshold = proposalThreshold_; 66 | } 67 | 68 | /** 69 | * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold 70 | * @param targets Target addresses for proposal calls 71 | * @param values Eth values for proposal calls 72 | * @param signatures Function signatures for proposal calls 73 | * @param calldatas Calldatas for proposal calls 74 | * @param description String description of the proposal 75 | * @return Proposal id of new proposal 76 | */ 77 | function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) { 78 | require(forth.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold, "GovernorBravo::propose: proposer votes below proposal threshold"); 79 | require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorBravo::propose: proposal function information arity mismatch"); 80 | require(targets.length != 0, "GovernorBravo::propose: must provide actions"); 81 | require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions"); 82 | 83 | uint latestProposalId = latestProposalIds[msg.sender]; 84 | if (latestProposalId != 0) { 85 | ProposalState proposersLatestProposalState = state(latestProposalId); 86 | require(proposersLatestProposalState != ProposalState.Active, "GovernorBravo::propose: one live proposal per proposer, found an already active proposal"); 87 | require(proposersLatestProposalState != ProposalState.Pending, "GovernorBravo::propose: one live proposal per proposer, found an already pending proposal"); 88 | } 89 | 90 | uint startBlock = add256(block.number, votingDelay); 91 | uint endBlock = add256(startBlock, votingPeriod); 92 | 93 | proposalCount++; 94 | Proposal memory newProposal = Proposal({ 95 | id: proposalCount, 96 | proposer: msg.sender, 97 | eta: 0, 98 | targets: targets, 99 | values: values, 100 | signatures: signatures, 101 | calldatas: calldatas, 102 | startBlock: startBlock, 103 | endBlock: endBlock, 104 | forVotes: 0, 105 | againstVotes: 0, 106 | abstainVotes: 0, 107 | canceled: false, 108 | executed: false 109 | }); 110 | 111 | proposals[newProposal.id] = newProposal; 112 | latestProposalIds[newProposal.proposer] = newProposal.id; 113 | 114 | emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description); 115 | return newProposal.id; 116 | } 117 | 118 | /** 119 | * @notice Queues a proposal of state succeeded 120 | * @param proposalId The id of the proposal to queue 121 | */ 122 | function queue(uint proposalId) external { 123 | require(state(proposalId) == ProposalState.Succeeded, "GovernorBravo::queue: proposal can only be queued if it is succeeded"); 124 | Proposal storage proposal = proposals[proposalId]; 125 | uint eta = add256(block.timestamp, timelock.delay()); 126 | for (uint i = 0; i < proposal.targets.length; i++) { 127 | queueOrRevertInternal(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta); 128 | } 129 | proposal.eta = eta; 130 | emit ProposalQueued(proposalId, eta); 131 | } 132 | 133 | function queueOrRevertInternal(address target, uint value, string memory signature, bytes memory data, uint eta) internal { 134 | require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorBravo::queueOrRevertInternal: identical proposal action already queued at eta"); 135 | timelock.queueTransaction(target, value, signature, data, eta); 136 | } 137 | 138 | /** 139 | * @notice Executes a queued proposal if eta has passed 140 | * @param proposalId The id of the proposal to execute 141 | */ 142 | function execute(uint proposalId) external payable { 143 | require(state(proposalId) == ProposalState.Queued, "GovernorBravo::execute: proposal can only be executed if it is queued"); 144 | Proposal storage proposal = proposals[proposalId]; 145 | proposal.executed = true; 146 | for (uint i = 0; i < proposal.targets.length; i++) { 147 | timelock.executeTransaction{value: proposal.values[i]}(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); 148 | } 149 | emit ProposalExecuted(proposalId); 150 | } 151 | 152 | /** 153 | * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold 154 | * @param proposalId The id of the proposal to cancel 155 | */ 156 | function cancel(uint proposalId) external { 157 | require(state(proposalId) != ProposalState.Executed, "GovernorBravo::cancel: cannot cancel executed proposal"); 158 | 159 | Proposal storage proposal = proposals[proposalId]; 160 | require(msg.sender == proposal.proposer || forth.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold, "GovernorBravo::cancel: proposer above threshold"); 161 | 162 | proposal.canceled = true; 163 | for (uint i = 0; i < proposal.targets.length; i++) { 164 | timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); 165 | } 166 | 167 | emit ProposalCanceled(proposalId); 168 | } 169 | 170 | /** 171 | * @notice Gets actions of a proposal 172 | * @param proposalId the id of the proposal 173 | * @return targets proposal targets 174 | * @return values proposal values 175 | * @return signatures proposal signatures 176 | * @return calldatas proposal calldatas 177 | */ 178 | function getActions(uint proposalId) external view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) { 179 | Proposal storage p = proposals[proposalId]; 180 | return (p.targets, p.values, p.signatures, p.calldatas); 181 | } 182 | 183 | /** 184 | * @notice Gets the receipt for a voter on a given proposal 185 | * @param proposalId the id of proposal 186 | * @param voter The address of the voter 187 | * @return The voting receipt 188 | */ 189 | function getReceipt(uint proposalId, address voter) external view returns (Receipt memory) { 190 | return proposals[proposalId].receipts[voter]; 191 | } 192 | 193 | /** 194 | * @notice Gets the state of a proposal 195 | * @param proposalId The id of the proposal 196 | * @return Proposal state 197 | */ 198 | function state(uint proposalId) public view returns (ProposalState) { 199 | require(proposalCount >= proposalId && proposalId > 0, "GovernorBravo::state: invalid proposal id"); 200 | Proposal storage proposal = proposals[proposalId]; 201 | if (proposal.canceled) { 202 | return ProposalState.Canceled; 203 | } else if (block.number <= proposal.startBlock) { 204 | return ProposalState.Pending; 205 | } else if (block.number <= proposal.endBlock) { 206 | return ProposalState.Active; 207 | } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) { 208 | return ProposalState.Defeated; 209 | } else if (proposal.eta == 0) { 210 | return ProposalState.Succeeded; 211 | } else if (proposal.executed) { 212 | return ProposalState.Executed; 213 | } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) { 214 | return ProposalState.Expired; 215 | } else { 216 | return ProposalState.Queued; 217 | } 218 | } 219 | 220 | /** 221 | * @notice Cast a vote for a proposal 222 | * @param proposalId The id of the proposal to vote on 223 | * @param support The support value for the vote. 0=against, 1=for, 2=abstain 224 | */ 225 | function castVote(uint proposalId, uint8 support) external { 226 | emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), ""); 227 | } 228 | 229 | /** 230 | * @notice Cast a vote for a proposal with a reason 231 | * @param proposalId The id of the proposal to vote on 232 | * @param support The support value for the vote. 0=against, 1=for, 2=abstain 233 | * @param reason The reason given for the vote by the voter 234 | */ 235 | function castVoteWithReason(uint proposalId, uint8 support, string calldata reason) external { 236 | emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), reason); 237 | } 238 | 239 | /** 240 | * @notice Cast a vote for a proposal by signature 241 | * @dev External function that accepts EIP-712 signatures for voting on proposals. 242 | */ 243 | function castVoteBySig(uint proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external { 244 | bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))); 245 | bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support)); 246 | bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); 247 | address signatory = ecrecover(digest, v, r, s); 248 | require(signatory != address(0), "GovernorBravo::castVoteBySig: invalid signature"); 249 | emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), ""); 250 | } 251 | 252 | /** 253 | * @notice Internal function that caries out voting logic 254 | * @param voter The voter that is casting their vote 255 | * @param proposalId The id of the proposal to vote on 256 | * @param support The support value for the vote. 0=against, 1=for, 2=abstain 257 | * @return The number of votes cast 258 | */ 259 | function castVoteInternal(address voter, uint proposalId, uint8 support) internal returns (uint96) { 260 | require(state(proposalId) == ProposalState.Active, "GovernorBravo::castVoteInternal: voting is closed"); 261 | require(support <= 2, "GovernorBravo::castVoteInternal: invalid vote type"); 262 | Proposal storage proposal = proposals[proposalId]; 263 | Receipt storage receipt = proposal.receipts[voter]; 264 | require(receipt.hasVoted == false, "GovernorBravo::castVoteInternal: voter already voted"); 265 | uint96 votes = forth.getPriorVotes(voter, proposal.startBlock); 266 | 267 | if (support == 0) { 268 | proposal.againstVotes = add256(proposal.againstVotes, votes); 269 | } else if (support == 1) { 270 | proposal.forVotes = add256(proposal.forVotes, votes); 271 | } else if (support == 2) { 272 | proposal.abstainVotes = add256(proposal.abstainVotes, votes); 273 | } 274 | 275 | receipt.hasVoted = true; 276 | receipt.support = support; 277 | receipt.votes = votes; 278 | 279 | return votes; 280 | } 281 | 282 | /** 283 | * @notice Admin function for setting the voting delay 284 | * @param newVotingDelay new voting delay, in blocks 285 | */ 286 | function _setVotingDelay(uint newVotingDelay) external { 287 | require(msg.sender == admin, "GovernorBravo::_setVotingDelay: admin only"); 288 | require(newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, "GovernorBravo::_setVotingDelay: invalid voting delay"); 289 | uint oldVotingDelay = votingDelay; 290 | votingDelay = newVotingDelay; 291 | 292 | emit VotingDelaySet(oldVotingDelay,votingDelay); 293 | } 294 | 295 | /** 296 | * @notice Admin function for setting the voting period 297 | * @param newVotingPeriod new voting period, in blocks 298 | */ 299 | function _setVotingPeriod(uint newVotingPeriod) external { 300 | require(msg.sender == admin, "GovernorBravo::_setVotingPeriod: admin only"); 301 | require(newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, "GovernorBravo::_setVotingPeriod: invalid voting period"); 302 | uint oldVotingPeriod = votingPeriod; 303 | votingPeriod = newVotingPeriod; 304 | 305 | emit VotingPeriodSet(oldVotingPeriod, votingPeriod); 306 | } 307 | 308 | /** 309 | * @notice Admin function for setting the proposal threshold 310 | * @dev newProposalThreshold must be greater than the hardcoded min 311 | * @param newProposalThreshold new proposal threshold 312 | */ 313 | function _setProposalThreshold(uint newProposalThreshold) external { 314 | require(msg.sender == admin, "GovernorBravo::_setProposalThreshold: admin only"); 315 | require(newProposalThreshold >= MIN_PROPOSAL_THRESHOLD && newProposalThreshold <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::_setProposalThreshold: invalid proposal threshold"); 316 | uint oldProposalThreshold = proposalThreshold; 317 | proposalThreshold = newProposalThreshold; 318 | 319 | emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold); 320 | } 321 | 322 | /** 323 | * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. 324 | * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. 325 | * @param newPendingAdmin New pending admin. 326 | */ 327 | function _setPendingAdmin(address newPendingAdmin) external { 328 | // Check caller = admin 329 | require(msg.sender == admin, "GovernorBravo:_setPendingAdmin: admin only"); 330 | 331 | // Save current value, if any, for inclusion in log 332 | address oldPendingAdmin = pendingAdmin; 333 | 334 | // Store pendingAdmin with value newPendingAdmin 335 | pendingAdmin = newPendingAdmin; 336 | 337 | // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) 338 | emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); 339 | } 340 | 341 | /** 342 | * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin 343 | * @dev Admin function for pending admin to accept role and update admin 344 | */ 345 | function _acceptAdmin() external { 346 | // Check caller is pendingAdmin and pendingAdmin ≠ address(0) 347 | require(msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending admin only"); 348 | 349 | // Save current values for inclusion in log 350 | address oldAdmin = admin; 351 | address oldPendingAdmin = pendingAdmin; 352 | 353 | // Store admin with value pendingAdmin 354 | admin = pendingAdmin; 355 | 356 | // Clear the pending value 357 | pendingAdmin = address(0); 358 | 359 | emit NewAdmin(oldAdmin, admin); 360 | emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); 361 | } 362 | 363 | function add256(uint256 a, uint256 b) internal pure returns (uint) { 364 | uint c = a + b; 365 | require(c >= a, "addition overflow"); 366 | return c; 367 | } 368 | 369 | function sub256(uint256 a, uint256 b) internal pure returns (uint) { 370 | require(b <= a, "subtraction underflow"); 371 | return a - b; 372 | } 373 | 374 | function getChainIdInternal() internal pure returns (uint) { 375 | uint chainId; 376 | assembly { chainId := chainid() } 377 | return chainId; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /contracts/GovernorBravoDelegator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | 3 | pragma solidity 0.6.11; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./GovernorBravoInterfaces.sol"; 7 | 8 | contract GovernorBravoDelegator is GovernorBravoDelegatorStorage, GovernorBravoEvents { 9 | constructor( 10 | address timelock_, 11 | address forth_, 12 | address admin_, 13 | address implementation_, 14 | uint votingPeriod_, 15 | uint votingDelay_, 16 | uint proposalThreshold_) public { 17 | 18 | // Admin set to msg.sender for initialization 19 | admin = msg.sender; 20 | 21 | delegateTo(implementation_, abi.encodeWithSignature("initialize(address,address,uint256,uint256,uint256)", 22 | timelock_, 23 | forth_, 24 | votingPeriod_, 25 | votingDelay_, 26 | proposalThreshold_)); 27 | 28 | _setImplementation(implementation_); 29 | 30 | admin = admin_; 31 | } 32 | 33 | 34 | /** 35 | * @notice Called by the admin to update the implementation of the delegator 36 | * @param implementation_ The address of the new implementation for delegation 37 | */ 38 | function _setImplementation(address implementation_) public { 39 | require(msg.sender == admin, "GovernorBravoDelegator::_setImplementation: admin only"); 40 | require(implementation_ != address(0), "GovernorBravoDelegator::_setImplementation: invalid implementation address"); 41 | 42 | address oldImplementation = implementation; 43 | implementation = implementation_; 44 | 45 | emit NewImplementation(oldImplementation, implementation); 46 | } 47 | 48 | /** 49 | * @notice Internal method to delegate execution to another contract 50 | * @dev It returns to the external caller whatever the implementation returns or forwards reverts 51 | * @param callee The contract to delegatecall 52 | * @param data The raw data to delegatecall 53 | */ 54 | function delegateTo(address callee, bytes memory data) internal { 55 | (bool success, bytes memory returnData) = callee.delegatecall(data); 56 | assembly { 57 | if eq(success, 0) { 58 | revert(add(returnData, 0x20), returndatasize()) 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * @dev Delegates the current call to implementation. 65 | * 66 | * This function does not return to its internall call site, it will return directly to the external caller. 67 | */ 68 | function _fallback() internal { 69 | // delegate all other functions to current implementation 70 | (bool success, ) = implementation.delegatecall(msg.data); 71 | 72 | assembly { 73 | let free_mem_ptr := mload(0x40) 74 | returndatacopy(free_mem_ptr, 0, returndatasize()) 75 | 76 | switch success 77 | case 0 { revert(free_mem_ptr, returndatasize()) } 78 | default { return(free_mem_ptr, returndatasize()) } 79 | } 80 | } 81 | 82 | /** 83 | * @dev Delegates execution to an implementation contract. 84 | * It returns to the external caller whatever the implementation returns or forwards reverts. 85 | */ 86 | fallback() external payable { 87 | _fallback(); 88 | } 89 | 90 | /** 91 | * @dev Fallback function that delegates calls to implementation. Will run if call data is empty. 92 | */ 93 | receive() external payable { 94 | _fallback(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /contracts/GovernorBravoInterfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | 3 | pragma solidity 0.6.11; 4 | pragma experimental ABIEncoderV2; 5 | 6 | 7 | contract GovernorBravoEvents { 8 | /// @notice An event emitted when a new proposal is created 9 | event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description); 10 | 11 | /// @notice An event emitted when a vote has been cast on a proposal 12 | /// @param voter The address which casted a vote 13 | /// @param proposalId The proposal id which was voted on 14 | /// @param support Support value for the vote. 0=against, 1=for, 2=abstain 15 | /// @param votes Number of votes which were cast by the voter 16 | /// @param reason The reason given for the vote by the voter 17 | event VoteCast(address indexed voter, uint proposalId, uint8 support, uint votes, string reason); 18 | 19 | /// @notice An event emitted when a proposal has been canceled 20 | event ProposalCanceled(uint id); 21 | 22 | /// @notice An event emitted when a proposal has been queued in the Timelock 23 | event ProposalQueued(uint id, uint eta); 24 | 25 | /// @notice An event emitted when a proposal has been executed in the Timelock 26 | event ProposalExecuted(uint id); 27 | 28 | /// @notice An event emitted when the voting delay is set 29 | event VotingDelaySet(uint oldVotingDelay, uint newVotingDelay); 30 | 31 | /// @notice An event emitted when the voting period is set 32 | event VotingPeriodSet(uint oldVotingPeriod, uint newVotingPeriod); 33 | 34 | /// @notice Emitted when implementation is changed 35 | event NewImplementation(address oldImplementation, address newImplementation); 36 | 37 | /// @notice Emitted when proposal threshold is set 38 | event ProposalThresholdSet(uint oldProposalThreshold, uint newProposalThreshold); 39 | 40 | /// @notice Emitted when pendingAdmin is changed 41 | event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); 42 | 43 | /// @notice Emitted when pendingAdmin is accepted, which means admin is updated 44 | event NewAdmin(address oldAdmin, address newAdmin); 45 | } 46 | 47 | contract GovernorBravoDelegatorStorage { 48 | /// @notice Administrator for this contract 49 | address public admin; 50 | 51 | /// @notice Pending administrator for this contract 52 | address public pendingAdmin; 53 | 54 | /// @notice Active brains of Governor 55 | address public implementation; 56 | } 57 | 58 | 59 | /** 60 | * @title Storage for Governor Bravo Delegate 61 | * @notice For future upgrades, do not change GovernorBravoDelegateStorageV1. Create a new 62 | * contract which implements GovernorBravoDelegateStorageV1 and following the naming convention 63 | * GovernorBravoDelegateStorageVX. 64 | */ 65 | contract GovernorBravoDelegateStorageV1 is GovernorBravoDelegatorStorage { 66 | 67 | /// @notice The delay before voting on a proposal may take place, once proposed, in blocks 68 | uint public votingDelay; 69 | 70 | /// @notice The duration of voting on a proposal, in blocks 71 | uint public votingPeriod; 72 | 73 | /// @notice The number of votes required in order for a voter to become a proposer 74 | uint public proposalThreshold; 75 | 76 | /// @notice The total number of proposals 77 | uint public proposalCount; 78 | 79 | /// @notice The address of the Ampleforth Protocol Timelock 80 | TimelockInterface public timelock; 81 | 82 | /// @notice The address of the Ampleforth governance token 83 | ForthInterface public forth; 84 | 85 | /// @notice The official record of all proposals ever proposed 86 | mapping (uint => Proposal) public proposals; 87 | 88 | /// @notice The latest proposal for each proposer 89 | mapping (address => uint) public latestProposalIds; 90 | 91 | 92 | struct Proposal { 93 | // Unique id for looking up a proposal 94 | uint id; 95 | 96 | // Creator of the proposal 97 | address proposer; 98 | 99 | // The timestamp that the proposal will be available for execution, set once the vote succeeds 100 | uint eta; 101 | 102 | // The ordered list of target addresses for calls to be made 103 | address[] targets; 104 | 105 | // The ordered list of values (i.e. msg.value) to be passed to the calls to be made 106 | uint[] values; 107 | 108 | // The ordered list of function signatures to be called 109 | string[] signatures; 110 | 111 | // The ordered list of calldata to be passed to each call 112 | bytes[] calldatas; 113 | 114 | // The block at which voting begins: holders must delegate their votes prior to this block 115 | uint startBlock; 116 | 117 | // The block at which voting ends: votes must be cast prior to this block 118 | uint endBlock; 119 | 120 | // Current number of votes in favor of this proposal 121 | uint forVotes; 122 | 123 | // Current number of votes in opposition to this proposal 124 | uint againstVotes; 125 | 126 | // Current number of votes for abstaining for this proposal 127 | uint abstainVotes; 128 | 129 | // Flag marking whether the proposal has been canceled 130 | bool canceled; 131 | 132 | // Flag marking whether the proposal has been executed 133 | bool executed; 134 | 135 | // Receipts of ballots for the entire set of voters 136 | mapping (address => Receipt) receipts; 137 | } 138 | 139 | /// @notice Ballot receipt record for a voter 140 | struct Receipt { 141 | // Whether or not a vote has been cast 142 | bool hasVoted; 143 | 144 | // Whether or not the voter supports the proposal or abstains 145 | uint8 support; 146 | 147 | // The number of votes the voter had, which were cast 148 | uint96 votes; 149 | } 150 | 151 | /// @notice Possible states that a proposal may be in 152 | enum ProposalState { 153 | Pending, 154 | Active, 155 | Canceled, 156 | Defeated, 157 | Succeeded, 158 | Queued, 159 | Expired, 160 | Executed 161 | } 162 | } 163 | 164 | interface TimelockInterface { 165 | function delay() external view returns (uint); 166 | function GRACE_PERIOD() external view returns (uint); 167 | function acceptAdmin() external; 168 | function queuedTransactions(bytes32 hash) external view returns (bool); 169 | function queueTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external returns (bytes32); 170 | function cancelTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external; 171 | function executeTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external payable returns (bytes memory); 172 | } 173 | 174 | interface ForthInterface { 175 | function getPriorVotes(address account, uint blockNumber) external view returns (uint96); 176 | } 177 | -------------------------------------------------------------------------------- /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.6.0 <0.8.0; 4 | 5 | /** 6 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 7 | * checks. 8 | * 9 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 10 | * in bugs, because programmers usually assume that an overflow raises an 11 | * error, which is the standard behavior in high level programming languages. 12 | * `SafeMath` restores this intuition by reverting the transaction when an 13 | * operation overflows. 14 | * 15 | * Using this library instead of the unchecked operations eliminates an entire 16 | * class of bugs, so it's recommended to use it always. 17 | */ 18 | library SafeMath { 19 | /** 20 | * @dev Returns the addition of two unsigned integers, with an overflow flag. 21 | * 22 | * _Available since v3.4._ 23 | */ 24 | function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { 25 | uint256 c = a + b; 26 | if (c < a) return (false, 0); 27 | return (true, c); 28 | } 29 | 30 | /** 31 | * @dev Returns the substraction of two unsigned integers, with an overflow flag. 32 | * 33 | * _Available since v3.4._ 34 | */ 35 | function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { 36 | if (b > a) return (false, 0); 37 | return (true, a - b); 38 | } 39 | 40 | /** 41 | * @dev Returns the multiplication of two unsigned integers, with an overflow flag. 42 | * 43 | * _Available since v3.4._ 44 | */ 45 | function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { 46 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 47 | // benefit is lost if 'b' is also tested. 48 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 49 | if (a == 0) return (true, 0); 50 | uint256 c = a * b; 51 | if (c / a != b) return (false, 0); 52 | return (true, c); 53 | } 54 | 55 | /** 56 | * @dev Returns the division of two unsigned integers, with a division by zero flag. 57 | * 58 | * _Available since v3.4._ 59 | */ 60 | function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { 61 | if (b == 0) return (false, 0); 62 | return (true, a / b); 63 | } 64 | 65 | /** 66 | * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. 67 | * 68 | * _Available since v3.4._ 69 | */ 70 | function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { 71 | if (b == 0) return (false, 0); 72 | return (true, a % b); 73 | } 74 | 75 | /** 76 | * @dev Returns the addition of two unsigned integers, reverting on 77 | * overflow. 78 | * 79 | * Counterpart to Solidity's `+` operator. 80 | * 81 | * Requirements: 82 | * 83 | * - Addition cannot overflow. 84 | */ 85 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 86 | uint256 c = a + b; 87 | require(c >= a, "SafeMath: addition overflow"); 88 | return c; 89 | } 90 | 91 | /** 92 | * @dev Returns the subtraction of two unsigned integers, reverting on 93 | * overflow (when the result is negative). 94 | * 95 | * Counterpart to Solidity's `-` operator. 96 | * 97 | * Requirements: 98 | * 99 | * - Subtraction cannot overflow. 100 | */ 101 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 102 | require(b <= a, "SafeMath: subtraction overflow"); 103 | return a - b; 104 | } 105 | 106 | /** 107 | * @dev Returns the multiplication of two unsigned integers, reverting on 108 | * overflow. 109 | * 110 | * Counterpart to Solidity's `*` operator. 111 | * 112 | * Requirements: 113 | * 114 | * - Multiplication cannot overflow. 115 | */ 116 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 117 | if (a == 0) return 0; 118 | uint256 c = a * b; 119 | require(c / a == b, "SafeMath: multiplication overflow"); 120 | return c; 121 | } 122 | 123 | /** 124 | * @dev Returns the integer division of two unsigned integers, reverting on 125 | * division by zero. The result is rounded towards zero. 126 | * 127 | * Counterpart to Solidity's `/` operator. Note: this function uses a 128 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 129 | * uses an invalid opcode to revert (consuming all remaining gas). 130 | * 131 | * Requirements: 132 | * 133 | * - The divisor cannot be zero. 134 | */ 135 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 136 | require(b > 0, "SafeMath: division by zero"); 137 | return a / b; 138 | } 139 | 140 | /** 141 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 142 | * reverting when dividing by zero. 143 | * 144 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 145 | * opcode (which leaves remaining gas untouched) while Solidity uses an 146 | * invalid opcode to revert (consuming all remaining gas). 147 | * 148 | * Requirements: 149 | * 150 | * - The divisor cannot be zero. 151 | */ 152 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 153 | require(b > 0, "SafeMath: modulo by zero"); 154 | return a % b; 155 | } 156 | 157 | /** 158 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 159 | * overflow (when the result is negative). 160 | * 161 | * CAUTION: This function is deprecated because it requires allocating memory for the error 162 | * message unnecessarily. For custom revert reasons use {trySub}. 163 | * 164 | * Counterpart to Solidity's `-` operator. 165 | * 166 | * Requirements: 167 | * 168 | * - Subtraction cannot overflow. 169 | */ 170 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 171 | require(b <= a, errorMessage); 172 | return a - b; 173 | } 174 | 175 | /** 176 | * @dev Returns the integer division of two unsigned integers, reverting with custom message on 177 | * division by zero. The result is rounded towards zero. 178 | * 179 | * CAUTION: This function is deprecated because it requires allocating memory for the error 180 | * message unnecessarily. For custom revert reasons use {tryDiv}. 181 | * 182 | * Counterpart to Solidity's `/` operator. Note: this function uses a 183 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 184 | * uses an invalid opcode to revert (consuming all remaining gas). 185 | * 186 | * Requirements: 187 | * 188 | * - The divisor cannot be zero. 189 | */ 190 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 191 | require(b > 0, errorMessage); 192 | return a / b; 193 | } 194 | 195 | /** 196 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 197 | * reverting with custom message when dividing by zero. 198 | * 199 | * CAUTION: This function is deprecated because it requires allocating memory for the error 200 | * message unnecessarily. For custom revert reasons use {tryMod}. 201 | * 202 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 203 | * opcode (which leaves remaining gas untouched) while Solidity uses an 204 | * invalid opcode to revert (consuming all remaining gas). 205 | * 206 | * Requirements: 207 | * 208 | * - The divisor cannot be zero. 209 | */ 210 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 211 | require(b > 0, errorMessage); 212 | return a % b; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /contracts/Timelock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | 3 | pragma solidity 0.6.11; 4 | 5 | import "./SafeMath.sol"; 6 | 7 | contract Timelock { 8 | using SafeMath for uint; 9 | 10 | event NewAdmin(address indexed newAdmin); 11 | event NewPendingAdmin(address indexed newPendingAdmin); 12 | event NewDelay(uint indexed newDelay); 13 | event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 14 | event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 15 | event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 16 | 17 | uint public constant GRACE_PERIOD = 14 days; 18 | uint public constant MINIMUM_DELAY = 2 days; 19 | uint public constant MAXIMUM_DELAY = 30 days; 20 | 21 | address public admin; 22 | address public pendingAdmin; 23 | uint public delay; 24 | 25 | mapping (bytes32 => bool) public queuedTransactions; 26 | 27 | 28 | constructor(address admin_, uint delay_) public { 29 | require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); 30 | require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); 31 | 32 | admin = admin_; 33 | delay = delay_; 34 | } 35 | 36 | receive() external payable { } 37 | 38 | function setDelay(uint delay_) public { 39 | require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); 40 | require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); 41 | require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); 42 | delay = delay_; 43 | 44 | emit NewDelay(delay); 45 | } 46 | 47 | function acceptAdmin() public { 48 | require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); 49 | admin = msg.sender; 50 | pendingAdmin = address(0); 51 | 52 | emit NewAdmin(admin); 53 | } 54 | 55 | function setPendingAdmin(address pendingAdmin_) public { 56 | require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); 57 | pendingAdmin = pendingAdmin_; 58 | 59 | emit NewPendingAdmin(pendingAdmin); 60 | } 61 | 62 | function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) { 63 | require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); 64 | require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay."); 65 | 66 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 67 | queuedTransactions[txHash] = true; 68 | 69 | emit QueueTransaction(txHash, target, value, signature, data, eta); 70 | return txHash; 71 | } 72 | 73 | function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public { 74 | require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); 75 | 76 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 77 | queuedTransactions[txHash] = false; 78 | 79 | emit CancelTransaction(txHash, target, value, signature, data, eta); 80 | } 81 | 82 | function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) { 83 | require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); 84 | 85 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 86 | require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); 87 | require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); 88 | require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale."); 89 | 90 | queuedTransactions[txHash] = false; 91 | 92 | bytes memory callData; 93 | 94 | if (bytes(signature).length == 0) { 95 | callData = data; 96 | } else { 97 | callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); 98 | } 99 | 100 | // solium-disable-next-line security/no-call-value 101 | (bool success, bytes memory returnData) = target.call{value: value}(callData); 102 | require(success, "Timelock::executeTransaction: Transaction execution reverted."); 103 | 104 | emit ExecuteTransaction(txHash, target, value, signature, data, eta); 105 | 106 | return returnData; 107 | } 108 | 109 | function getBlockTimestamp() internal view returns (uint) { 110 | // solium-disable-next-line security/no-block-members 111 | return block.timestamp; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ampleforth/Forth-v2", 3 | "version": "1.0.0", 4 | "description": "🏛 Governance contracts for the Ampleforth protocol", 5 | "author": "Brandon Iles", 6 | "license": "MIT", 7 | "files": [ 8 | "build" 9 | ], 10 | "scripts": { 11 | "precompile": "rimraf ./build/", 12 | "compile": "waffle", 13 | "pretest": "yarn compile", 14 | "test": "mocha", 15 | "lint": "prettier ./test/**/*.ts --check", 16 | "prepublishOnly": "yarn test" 17 | }, 18 | "devDependencies": { 19 | "@types/chai": "^4.2.12", 20 | "@types/mocha": "^8.0.3", 21 | "chai": "^4.2.0", 22 | "ethereum-waffle": "^3.1.0", 23 | "ethereumjs-util": "^7.0.4", 24 | "mocha": "^8.1.3", 25 | "prettier": "^2.1.1", 26 | "rimraf": "^3.0.2", 27 | "solc": "0.6.11", 28 | "ts-node": "^9.0.0", 29 | "typescript": "^4.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Forth.spec.ts: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai' 2 | import { BigNumber, Contract, constants, utils } from 'ethers' 3 | import { solidity, MockProvider, createFixtureLoader, deployContract } from 'ethereum-waffle' 4 | import { ecsign } from 'ethereumjs-util' 5 | 6 | import { governanceFixture } from './fixtures' 7 | import { expandTo18Decimals, mineBlock } from './utils' 8 | 9 | import Forth from '../build/Forth.json' 10 | 11 | chai.use(solidity) 12 | 13 | const DOMAIN_TYPEHASH = utils.keccak256( 14 | utils.toUtf8Bytes('EIP712Domain(string name,uint256 chainId,address verifyingContract)') 15 | ) 16 | 17 | const PERMIT_TYPEHASH = utils.keccak256( 18 | utils.toUtf8Bytes('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)') 19 | ) 20 | 21 | describe('Forth', () => { 22 | const provider = new MockProvider({ 23 | ganacheOptions: { 24 | hardfork: 'istanbul', 25 | mnemonic: 'horn horn horn horn horn horn horn horn horn horn horn horn', 26 | gasLimit: 9999999, 27 | }, 28 | }) 29 | const [wallet, other0, other1] = provider.getWallets() 30 | const loadFixture = createFixtureLoader([wallet], provider) 31 | 32 | let forth: Contract 33 | beforeEach(async () => { 34 | const fixture = await loadFixture(governanceFixture) 35 | forth = fixture.forth 36 | }) 37 | 38 | it('permit', async () => { 39 | const domainSeparator = utils.keccak256( 40 | utils.defaultAbiCoder.encode( 41 | ['bytes32', 'bytes32', 'uint256', 'address'], 42 | [DOMAIN_TYPEHASH, utils.keccak256(utils.toUtf8Bytes('Ampleforth Governance')), 1, forth.address] 43 | ) 44 | ) 45 | 46 | const owner = wallet.address 47 | const spender = other0.address 48 | const value = 123 49 | const nonce = await forth.nonces(wallet.address) 50 | const deadline = constants.MaxUint256 51 | const digest = utils.keccak256( 52 | utils.solidityPack( 53 | ['bytes1', 'bytes1', 'bytes32', 'bytes32'], 54 | [ 55 | '0x19', 56 | '0x01', 57 | domainSeparator, 58 | utils.keccak256( 59 | utils.defaultAbiCoder.encode( 60 | ['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'], 61 | [PERMIT_TYPEHASH, owner, spender, value, nonce, deadline] 62 | ) 63 | ), 64 | ] 65 | ) 66 | ) 67 | 68 | const { v, r, s } = ecsign(Buffer.from(digest.slice(2), 'hex'), Buffer.from(wallet.privateKey.slice(2), 'hex')) 69 | 70 | await forth.permit(owner, spender, value, deadline, v, utils.hexlify(r), utils.hexlify(s)) 71 | expect(await forth.allowance(owner, spender)).to.eq(value) 72 | expect(await forth.nonces(owner)).to.eq(1) 73 | 74 | await forth.connect(other0).transferFrom(owner, spender, value) 75 | }) 76 | 77 | it('nested delegation', async () => { 78 | await forth.transfer(other0.address, expandTo18Decimals(1)) 79 | await forth.transfer(other1.address, expandTo18Decimals(2)) 80 | 81 | let currectVotes0 = await forth.getCurrentVotes(other0.address) 82 | let currectVotes1 = await forth.getCurrentVotes(other1.address) 83 | expect(currectVotes0).to.be.eq(0) 84 | expect(currectVotes1).to.be.eq(0) 85 | 86 | await forth.connect(other0).delegate(other1.address) 87 | currectVotes1 = await forth.getCurrentVotes(other1.address) 88 | expect(currectVotes1).to.be.eq(expandTo18Decimals(1)) 89 | 90 | await forth.connect(other1).delegate(other1.address) 91 | currectVotes1 = await forth.getCurrentVotes(other1.address) 92 | expect(currectVotes1).to.be.eq(expandTo18Decimals(1).add(expandTo18Decimals(2))) 93 | 94 | await forth.connect(other1).delegate(wallet.address) 95 | currectVotes1 = await forth.getCurrentVotes(other1.address) 96 | expect(currectVotes1).to.be.eq(expandTo18Decimals(1)) 97 | }) 98 | 99 | it('mints', async () => { 100 | const { timestamp: now } = await provider.getBlock('latest') 101 | const forth = await deployContract(wallet, Forth, [wallet.address, wallet.address, now + 60 * 60]) 102 | const supply = await forth.totalSupply() 103 | 104 | await expect(forth.mint(wallet.address, 1)).to.be.revertedWith('Forth::mint: minting not allowed yet') 105 | 106 | let timestamp = await forth.mintingAllowedAfter() 107 | await mineBlock(provider, timestamp.toString()) 108 | 109 | await expect(forth.connect(other1).mint(other1.address, 1)).to.be.revertedWith( 110 | 'Forth::mint: only the minter can mint' 111 | ) 112 | await expect(forth.mint('0x0000000000000000000000000000000000000000', 1)).to.be.revertedWith( 113 | 'Forth::mint: cannot transfer to the zero address' 114 | ) 115 | 116 | // can mint up to 2% 117 | const mintCap = BigNumber.from(await forth.mintCap()) 118 | const amount = supply.mul(mintCap).div(100) 119 | await forth.mint(wallet.address, amount) 120 | expect(await forth.balanceOf(wallet.address)).to.be.eq(supply.add(amount)) 121 | 122 | timestamp = await forth.mintingAllowedAfter() 123 | await mineBlock(provider, timestamp.toString()) 124 | // cannot mint 2.01% 125 | await expect(forth.mint(wallet.address, supply.mul(mintCap.add(1)))).to.be.revertedWith( 126 | 'Forth::mint: exceeded mint cap' 127 | ) 128 | }) 129 | 130 | it('burn', async () => { 131 | const { timestamp: now } = await provider.getBlock('latest') 132 | const forth = await deployContract(wallet, Forth, [wallet.address, wallet.address, now + 60 * 60]) 133 | const supply = await forth.totalSupply() 134 | 135 | // burn 0 136 | let balanceBefore = await forth.balanceOf(wallet.address) 137 | await forth.connect(wallet).burn(0) 138 | expect(await forth.balanceOf(wallet.address)).to.be.eq(balanceBefore) 139 | expect(await forth.totalSupply()).to.be.eq(supply) 140 | 141 | // burn non-zero 142 | await forth.connect(wallet).burn(1) 143 | expect(await forth.balanceOf(wallet.address)).to.be.eq(balanceBefore.sub(1)) 144 | expect(await forth.totalSupply()).to.be.eq(supply.sub(1)) 145 | 146 | // burn > totalSupply 147 | await expect(forth.connect(wallet).burn(supply + 2)).to.be.revertedWith('Forth::_burn: amount exceeds totalSupply') 148 | 149 | // burn > balance 150 | await forth.connect(wallet).transfer(other0.address, 100) 151 | balanceBefore = await forth.balanceOf(wallet.address) 152 | await expect(forth.connect(wallet).burn(balanceBefore.add(1))).to.be.revertedWith( 153 | 'Forth::_burn: amount exceeds balance' 154 | ) 155 | }) 156 | 157 | it('burnFrom', async () => { 158 | const { timestamp: now } = await provider.getBlock('latest') 159 | const forth = await deployContract(wallet, Forth, [wallet.address, wallet.address, now + 60 * 60]) 160 | const supply = await forth.totalSupply() 161 | 162 | // burn 0 163 | let balanceBefore = await forth.balanceOf(wallet.address) 164 | await forth.connect(other0).burnFrom(wallet.address, 0) 165 | expect(await forth.balanceOf(wallet.address)).to.be.eq(balanceBefore) 166 | expect(await forth.totalSupply()).to.be.eq(supply) 167 | 168 | // burn non-zero 169 | await forth.connect(wallet).approve(other0.address, 100) 170 | await forth.connect(other0).burnFrom(wallet.address, 1) 171 | expect(await forth.balanceOf(wallet.address)).to.be.eq(balanceBefore.sub(1)) 172 | expect(await forth.totalSupply()).to.be.eq(supply.sub(1)) 173 | 174 | // burn > approval 175 | balanceBefore = await forth.balanceOf(wallet.address) 176 | await forth.connect(wallet).approve(other0.address, 100) 177 | await expect(forth.connect(other0).burnFrom(wallet.address, 101)).to.be.revertedWith( 178 | 'Forth::burnFrom: amount exceeds allowance' 179 | ) 180 | 181 | // burn > totalSupply 182 | balanceBefore = await forth.balanceOf(wallet.address) 183 | await forth.connect(wallet).approve(other0.address, balanceBefore.add(1)) 184 | await expect(forth.connect(other0).burnFrom(wallet.address, balanceBefore.add(1))).to.be.revertedWith( 185 | 'Forth::_burn: amount exceeds totalSupply' 186 | ) 187 | 188 | // burn > balance 189 | await forth.connect(wallet).transfer(other0.address, 100) 190 | balanceBefore = await forth.balanceOf(wallet.address) 191 | await forth.connect(wallet).approve(other0.address, balanceBefore.add(1)) 192 | await expect(forth.connect(other0).burnFrom(wallet.address, balanceBefore.add(1))).to.be.revertedWith( 193 | 'Forth::_burn: amount exceeds balance' 194 | ) 195 | 196 | // Zero Address 197 | await expect(forth.connect(wallet).burnFrom('0x0000000000000000000000000000000000000000', 0)).to.be.revertedWith( 198 | 'Forth::_burn: burn from the zero address' 199 | ) 200 | }) 201 | }) 202 | -------------------------------------------------------------------------------- /test/fixtures.ts: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai' 2 | import { Contract, Wallet, providers } from 'ethers' 3 | import { solidity, deployContract } from 'ethereum-waffle' 4 | 5 | import Forth from '../build/Forth.json' 6 | import Timelock from '../build/Timelock.json' 7 | 8 | import { DELAY } from './utils' 9 | 10 | chai.use(solidity) 11 | 12 | interface GovernanceFixture { 13 | forth: Contract 14 | } 15 | 16 | export async function governanceFixture( 17 | [wallet]: Wallet[], 18 | provider: providers.Web3Provider 19 | ): Promise { 20 | // deploy FORTH, sending the total supply to the deployer 21 | const { timestamp: now } = await provider.getBlock('latest') 22 | const timelockAddress = Contract.getContractAddress({ from: wallet.address, nonce: 1 }) 23 | const forth = await deployContract(wallet, Forth, [wallet.address, timelockAddress, now + 60 * 60]) 24 | 25 | return { forth } 26 | } 27 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { providers, BigNumber } from 'ethers' 2 | 3 | export const DELAY = 60 * 60 * 24 * 2 4 | 5 | export async function mineBlock(provider: providers.Web3Provider, timestamp: number): Promise { 6 | return provider.send('evm_mine', [timestamp]) 7 | } 8 | 9 | export function expandTo18Decimals(n: number): BigNumber { 10 | return BigNumber.from(n).mul(BigNumber.from(10).pow(18)) 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /waffle.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerType": "solcjs", 3 | "compilerVersion": "./node_modules/solc", 4 | "outputType": "all", 5 | "compilerOptions": { 6 | "outputSelection": { 7 | "*": { 8 | "*": [ 9 | "evm.bytecode.object", 10 | "evm.deployedBytecode.object", 11 | "abi", 12 | "evm.bytecode.sourceMap", 13 | "evm.deployedBytecode.sourceMap", 14 | "metadata" 15 | ], 16 | "": ["ast"] 17 | } 18 | }, 19 | "evmVersion": "istanbul", 20 | "optimizer": { 21 | "enabled": true, 22 | "runs": 999999 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------