Files
17
Lines
2800
Coverage
79%
573 / 720
Actions
80%
contracts/contracts/echidna/Debugger.sol
Lines covered: 4 / 5 (80%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | 0 | library Debugger { |
| 5 | event Debug(string debugString); |
|
| 6 | event Debug(string description, string data); |
|
| 7 | event Debug(string prefix, string description, string data); |
|
| 8 | event Debug(string description, bytes32 data); |
|
| 9 | event Debug(string prefix, string description, bytes32 data); |
|
| 10 | event Debug(string description, uint256 data); |
|
| 11 | event Debug(string prefix, string description, uint256 data); |
|
| 12 | event Debug(string description, int256 data); |
|
| 13 | event Debug(string prefix, string description, int256 data); |
|
| 14 | event Debug(string description, address data); |
|
| 15 | event Debug(string prefix, string description, address data); |
|
| 16 | event Debug(string description, bool data); |
|
| 17 | event Debug(string prefix, string description, bool data); |
|
| 18 | ||
| 19 | function log(string memory debugString) internal { |
|
| 20 | emit Debug(debugString); |
|
| 21 | } |
|
| 22 | ||
| 23 | function log(string memory description, string memory data) internal { |
|
| 24 | emit Debug(description, data); |
|
| 25 | } |
|
| 26 | ||
| 27 | function log( |
|
| 28 | string memory prefix, |
|
| 29 | string memory description, |
|
| 30 | string memory data |
|
| 31 | ) internal { |
|
| 32 | emit Debug(prefix, description, data); |
|
| 33 | } |
|
| 34 | ||
| 35 | function log(string memory description, bytes32 data) internal { |
|
| 36 | emit Debug(description, data); |
|
| 37 | } |
|
| 38 | ||
| 39 | function log( |
|
| 40 | string memory prefix, |
|
| 41 | string memory description, |
|
| 42 | bytes32 data |
|
| 43 | ) internal { |
|
| 44 | emit Debug(prefix, description, data); |
|
| 45 | } |
|
| 46 | ||
| 47 | 8× | function log(string memory description, uint256 data) internal { |
| 48 | 38× | emit Debug(description, data); |
| 49 | } |
|
| 50 | ||
| 51 | function log( |
|
| 52 | string memory prefix, |
|
| 53 | string memory description, |
|
| 54 | uint256 data |
|
| 55 | ) internal { |
|
| 56 | emit Debug(prefix, description, data); |
|
| 57 | } |
|
| 58 | ||
| 59 | 1× | function log(string memory description, int256 data) internal { |
| 60 | 11× | emit Debug(description, data); |
| 61 | } |
|
| 62 | ||
| 63 | function log( |
|
| 64 | string memory prefix, |
|
| 65 | string memory description, |
|
| 66 | int256 data |
|
| 67 | ) internal { |
|
| 68 | emit Debug(prefix, description, data); |
|
| 69 | } |
|
| 70 | ||
| 71 | function log(string memory description, address data) internal { |
|
| 72 | emit Debug(description, data); |
|
| 73 | } |
|
| 74 | ||
| 75 | function log( |
|
| 76 | string memory prefix, |
|
| 77 | string memory description, |
|
| 78 | address data |
|
| 79 | ) internal { |
|
| 80 | emit Debug(prefix, description, data); |
|
| 81 | } |
|
| 82 | ||
| 83 | function log(string memory description, bool data) internal { |
|
| 84 | emit Debug(description, data); |
|
| 85 | } |
|
| 86 | ||
| 87 | function log( |
|
| 88 | string memory prefix, |
|
| 89 | string memory description, |
|
| 90 | bool data |
|
| 91 | ) internal { |
|
| 92 | emit Debug(prefix, description, data); |
|
| 93 | } |
|
| 94 | } |
95%
contracts/contracts/echidna/EchidnaConfig.sol
Lines covered: 19 / 20 (95%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | /** |
|
| 5 | * @title Top-level mixin for configuring the desired fuzzing setup |
|
| 6 | * @author Rappie |
|
| 7 | */ |
|
| 8 | 0 | contract EchidnaConfig { |
| 9 | 10× | address internal constant ADDRESS_VAULT = address(0x10000); |
| 10 | 3× | address internal constant ADDRESS_OUTSIDER_USER = address(0x20000); |
| 11 | ||
| 12 | 7× | address internal constant ADDRESS_USER0 = address(0x30000); |
| 13 | 7× | address internal constant ADDRESS_USER1 = address(0x40000); |
| 14 | ||
| 15 | // Will be set in EchidnaSetup constructor |
|
| 16 | address internal ADDRESS_OUTSIDER_CONTRACT; |
|
| 17 | address internal ADDRESS_CONTRACT0; |
|
| 18 | address internal ADDRESS_CONTRACT1; |
|
| 19 | ||
| 20 | // Toggle known issues |
|
| 21 | // |
|
| 22 | // This can be used to skip tests that are known to fail. This is useful |
|
| 23 | // when debugging a specific issue, but should be disabled when running |
|
| 24 | // the full test suite. |
|
| 25 | // |
|
| 26 | // True => skip tests that are known to fail |
|
| 27 | // False => run all tests |
|
| 28 | // |
|
| 29 | bool internal constant TOGGLE_KNOWN_ISSUES = false; |
|
| 30 | ||
| 31 | // Toggle known issues within limits |
|
| 32 | // |
|
| 33 | // Same as TOGGLE_KNOWN_ISSUES, but also skip tests that are known to fail |
|
| 34 | // within limits set by the variables below. |
|
| 35 | // |
|
| 36 | bool internal constant TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS = false; |
|
| 37 | ||
| 38 | // Starting balance |
|
| 39 | // |
|
| 40 | // Gives OUSD a non-zero starting supply, which can be useful to ignore |
|
| 41 | // certain edge cases. |
|
| 42 | // |
|
| 43 | // The starting balance is given to outsider accounts that are not used as |
|
| 44 | // accounts while fuzzing. |
|
| 45 | // |
|
| 46 | bool internal constant TOGGLE_STARTING_BALANCE = true; |
|
| 47 | 2× | uint256 internal constant STARTING_BALANCE = 1_000_000e18; |
| 48 | ||
| 49 | // Change supply |
|
| 50 | // |
|
| 51 | // Set a limit to the amount of change per rebase, which can be useful to |
|
| 52 | // ignore certain edge cases. |
|
| 53 | // |
|
| 54 | // True => limit the amount of change to a percentage of total supply |
|
| 55 | // False => no limit |
|
| 56 | // |
|
| 57 | bool internal constant TOGGLE_CHANGESUPPLY_LIMIT = true; |
|
| 58 | 1× | uint256 internal constant CHANGESUPPLY_DIVISOR = 10; // 10% of total supply |
| 59 | ||
| 60 | // Mint limit |
|
| 61 | // |
|
| 62 | // Set a limit the amount minted per mint, which can be useful to |
|
| 63 | // ignore certain edge cases. |
|
| 64 | // |
|
| 65 | // True => limit the amount of minted tokens |
|
| 66 | // False => no limit |
|
| 67 | // |
|
| 68 | bool internal constant TOGGLE_MINT_LIMIT = true; |
|
| 69 | 1× | uint256 internal constant MINT_MODULO = 1_000_000_000_000e18; |
| 70 | ||
| 71 | // Known rounding errors |
|
| 72 | 1× | uint256 internal constant TRANSFER_ROUNDING_ERROR = 1e18 - 1; |
| 73 | 1× | uint256 internal constant OPT_IN_ROUNDING_ERROR = 1e18 - 1; |
| 74 | 1× | uint256 internal constant MINT_ROUNDING_ERROR = 1e18 - 1; |
| 75 | ||
| 76 | /** |
|
| 77 | * @notice Modifier to skip tests that are known to fail |
|
| 78 | * @dev see TOGGLE_KNOWN_ISSUES for more information |
|
| 79 | */ |
|
| 80 | modifier hasKnownIssue() { |
|
| 81 | if (TOGGLE_KNOWN_ISSUES) return; |
|
| 82 | 5× | _; |
| 83 | } |
|
| 84 | ||
| 85 | /** |
|
| 86 | * @notice Modifier to skip tests that are known to fail within limits |
|
| 87 | * @dev see TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS for more information |
|
| 88 | */ |
|
| 89 | modifier hasKnownIssueWithinLimits() { |
|
| 90 | if (TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS) return; |
|
| 91 | _; |
|
| 92 | } |
|
| 93 | ||
| 94 | /** |
|
| 95 | * @notice Translate an account ID to an address |
|
| 96 | * @param accountId The ID of the account |
|
| 97 | * @return account The address of the account |
|
| 98 | */ |
|
| 99 | 22× | function getAccount(uint8 accountId) |
| 100 | internal |
|
| 101 | view |
|
| 102 | 2× | returns (address account) |
| 103 | { |
|
| 104 | 16× | accountId = accountId / 64; |
| 105 | 16× | if (accountId == 0) return account = ADDRESS_USER0; |
| 106 | 16× | if (accountId == 1) return account = ADDRESS_USER1; |
| 107 | 24× | if (accountId == 2) return account = ADDRESS_CONTRACT0; |
| 108 | 22× | if (accountId == 3) return account = ADDRESS_CONTRACT1; |
| 109 | 2× | require(false, "Unknown account ID"); |
| 110 | } |
|
| 111 | } |
95%
contracts/contracts/echidna/EchidnaHelper.sol
Lines covered: 70 / 73 (95%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./EchidnaSetup.sol"; |
|
| 5 | import "./Debugger.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @title Mixin containing helper functions |
|
| 9 | * @author Rappie |
|
| 10 | */ |
|
| 11 | 0 | contract EchidnaHelper is EchidnaSetup { |
| 12 | /** |
|
| 13 | * @notice Mint tokens to an account |
|
| 14 | * @param toAcc Account to mint to |
|
| 15 | * @param amount Amount to mint |
|
| 16 | * @return Amount minted (in case of capped mint with modulo) |
|
| 17 | */ |
|
| 18 | 24× | function mint(uint8 toAcc, uint256 amount) public returns (uint256) { |
| 19 | 7× | address to = getAccount(toAcc); |
| 20 | ||
| 21 | if (TOGGLE_MINT_LIMIT) { |
|
| 22 | 7× | amount = amount % MINT_MODULO; |
| 23 | } |
|
| 24 | ||
| 25 | 39× | hevm.prank(ADDRESS_VAULT); |
| 26 | 52× | ousd.mint(to, amount); |
| 27 | ||
| 28 | 4× | return amount; |
| 29 | } |
|
| 30 | ||
| 31 | /** |
|
| 32 | * @notice Burn tokens from an account |
|
| 33 | * @param fromAcc Account to burn from |
|
| 34 | * @param amount Amount to burn |
|
| 35 | */ |
|
| 36 | 11× | function burn(uint8 fromAcc, uint256 amount) public { |
| 37 | 8× | address from = getAccount(fromAcc); |
| 38 | 39× | hevm.prank(ADDRESS_VAULT); |
| 39 | 47× | ousd.burn(from, amount); |
| 40 | } |
|
| 41 | ||
| 42 | /** |
|
| 43 | * @notice Change the total supply of OUSD (rebase) |
|
| 44 | * @param amount New total supply |
|
| 45 | */ |
|
| 46 | 11× | function changeSupply(uint256 amount) public { |
| 47 | if (TOGGLE_CHANGESUPPLY_LIMIT) { |
|
| 48 | 2× | amount = |
| 49 | 71× | ousd.totalSupply() + |
| 50 | 79× | (amount % (ousd.totalSupply() / CHANGESUPPLY_DIVISOR)); |
| 51 | } |
|
| 52 | ||
| 53 | 39× | hevm.prank(ADDRESS_VAULT); |
| 54 | 46× | ousd.changeSupply(amount); |
| 55 | } |
|
| 56 | ||
| 57 | /** |
|
| 58 | * @notice Transfer tokens between accounts |
|
| 59 | * @param fromAcc Account to transfer from |
|
| 60 | * @param toAcc Account to transfer to |
|
| 61 | * @param amount Amount to transfer |
|
| 62 | */ |
|
| 63 | 11× | function transfer( |
| 64 | uint8 fromAcc, |
|
| 65 | uint8 toAcc, |
|
| 66 | uint256 amount |
|
| 67 | 0 | ) public { |
| 68 | 8× | address from = getAccount(fromAcc); |
| 69 | 8× | address to = getAccount(toAcc); |
| 70 | 40× | hevm.prank(from); |
| 71 | // slither-disable-next-line unchecked-transfer |
|
| 72 | 67× | ousd.transfer(to, amount); |
| 73 | } |
|
| 74 | ||
| 75 | /** |
|
| 76 | * @notice Transfer approved tokens between accounts |
|
| 77 | * @param authorizedAcc Account that is authorized to transfer |
|
| 78 | * @param fromAcc Account to transfer from |
|
| 79 | * @param toAcc Account to transfer to |
|
| 80 | * @param amount Amount to transfer |
|
| 81 | */ |
|
| 82 | 16× | function transferFrom( |
| 83 | uint8 authorizedAcc, |
|
| 84 | uint8 fromAcc, |
|
| 85 | uint8 toAcc, |
|
| 86 | uint256 amount |
|
| 87 | 3× | ) public { |
| 88 | 8× | address authorized = getAccount(authorizedAcc); |
| 89 | 8× | address from = getAccount(fromAcc); |
| 90 | 8× | address to = getAccount(toAcc); |
| 91 | 40× | hevm.prank(authorized); |
| 92 | // slither-disable-next-line unchecked-transfer |
|
| 93 | 68× | ousd.transferFrom(from, to, amount); |
| 94 | } |
|
| 95 | ||
| 96 | /** |
|
| 97 | * @notice Opt in to rebasing |
|
| 98 | * @param targetAcc Account to opt in |
|
| 99 | */ |
|
| 100 | 11× | function optIn(uint8 targetAcc) public { |
| 101 | 8× | address target = getAccount(targetAcc); |
| 102 | 40× | hevm.prank(target); |
| 103 | 37× | ousd.rebaseOptIn(); |
| 104 | } |
|
| 105 | ||
| 106 | /** |
|
| 107 | * @notice Opt out of rebasing |
|
| 108 | * @param targetAcc Account to opt out |
|
| 109 | */ |
|
| 110 | 11× | function optOut(uint8 targetAcc) public { |
| 111 | 8× | address target = getAccount(targetAcc); |
| 112 | 40× | hevm.prank(target); |
| 113 | 53× | ousd.rebaseOptOut(); |
| 114 | } |
|
| 115 | ||
| 116 | /** |
|
| 117 | * @notice Approve an account to spend OUSD |
|
| 118 | * @param ownerAcc Account that owns the OUSD |
|
| 119 | * @param spenderAcc Account that is approved to spend the OUSD |
|
| 120 | * @param amount Amount to approve |
|
| 121 | */ |
|
| 122 | 15× | function approve( |
| 123 | uint8 ownerAcc, |
|
| 124 | uint8 spenderAcc, |
|
| 125 | uint256 amount |
|
| 126 | ) public { |
|
| 127 | 16× | address owner = getAccount(ownerAcc); |
| 128 | 16× | address spender = getAccount(spenderAcc); |
| 129 | 74× | hevm.prank(owner); |
| 130 | // slither-disable-next-line unused-return |
|
| 131 | 16× | ousd.approve(spender, amount); |
| 132 | } |
|
| 133 | ||
| 134 | /** |
|
| 135 | * @notice Get the sum of all OUSD balances |
|
| 136 | * @return total Total balance |
|
| 137 | */ |
|
| 138 | 27× | function getTotalBalance() public view returns (uint256 total) { |
| 139 | 68× | total += ousd.balanceOf(ADDRESS_VAULT); |
| 140 | 68× | total += ousd.balanceOf(ADDRESS_OUTSIDER_USER); |
| 141 | 74× | total += ousd.balanceOf(ADDRESS_OUTSIDER_CONTRACT); |
| 142 | 68× | total += ousd.balanceOf(ADDRESS_USER0); |
| 143 | 68× | total += ousd.balanceOf(ADDRESS_USER1); |
| 144 | 74× | total += ousd.balanceOf(ADDRESS_CONTRACT0); |
| 145 | 75× | total += ousd.balanceOf(ADDRESS_CONTRACT1); |
| 146 | } |
|
| 147 | ||
| 148 | /** |
|
| 149 | * @notice Get the sum of all non-rebasing OUSD balances |
|
| 150 | * @return total Total balance |
|
| 151 | */ |
|
| 152 | 7× | function getTotalNonRebasingBalance() public returns (uint256 total) { |
| 153 | 73× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_VAULT) |
| 154 | 1× | ? ousd.balanceOf(ADDRESS_VAULT) |
| 155 | 1× | : 0; |
| 156 | 73× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_OUTSIDER_USER) |
| 157 | 1× | ? ousd.balanceOf(ADDRESS_OUTSIDER_USER) |
| 158 | 1× | : 0; |
| 159 | 78× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_OUTSIDER_CONTRACT) |
| 160 | 63× | ? ousd.balanceOf(ADDRESS_OUTSIDER_CONTRACT) |
| 161 | 0 | : 0; |
| 162 | 74× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_USER0) |
| 163 | 58× | ? ousd.balanceOf(ADDRESS_USER0) |
| 164 | 1× | : 0; |
| 165 | 74× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_USER1) |
| 166 | 58× | ? ousd.balanceOf(ADDRESS_USER1) |
| 167 | 1× | : 0; |
| 168 | 80× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_CONTRACT0) |
| 169 | 63× | ? ousd.balanceOf(ADDRESS_CONTRACT0) |
| 170 | 1× | : 0; |
| 171 | 72× | total += ousd._isNonRebasingAccountEchidna(ADDRESS_CONTRACT1) |
| 172 | 20× | ? ousd.balanceOf(ADDRESS_CONTRACT1) |
| 173 | 1× | : 0; |
| 174 | } |
|
| 175 | } |
93%
contracts/contracts/echidna/EchidnaSetup.sol
Lines covered: 15 / 16 (93%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./IHevm.sol"; |
|
| 5 | import "./EchidnaConfig.sol"; |
|
| 6 | import "./OUSDEchidna.sol"; |
|
| 7 | ||
| 8 | 17× | contract Dummy {} |
| 9 | ||
| 10 | /** |
|
| 11 | * @title Mixin for setup and deployment |
|
| 12 | * @author Rappie |
|
| 13 | */ |
|
| 14 | 0 | contract EchidnaSetup is EchidnaConfig { |
| 15 | 8× | IHevm hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); |
| 16 | 34× | OUSDEchidna ousd = new OUSDEchidna(); |
| 17 | ||
| 18 | /** |
|
| 19 | * @notice Deploy the OUSD contract and set up initial state |
|
| 20 | */ |
|
| 21 | 3× | constructor() { |
| 22 | 45× | ousd.initialize(ADDRESS_VAULT, 1e18); |
| 23 | ||
| 24 | // Deploy dummny contracts as users |
|
| 25 | 23× | Dummy outsider = new Dummy(); |
| 26 | 12× | ADDRESS_OUTSIDER_CONTRACT = address(outsider); |
| 27 | 22× | Dummy dummy0 = new Dummy(); |
| 28 | 11× | ADDRESS_CONTRACT0 = address(dummy0); |
| 29 | 22× | Dummy dummy1 = new Dummy(); |
| 30 | 16× | ADDRESS_CONTRACT1 = address(dummy1); |
| 31 | ||
| 32 | // Start out with a reasonable amount of OUSD |
|
| 33 | if (TOGGLE_STARTING_BALANCE) { |
|
| 34 | // Rebasing tokens |
|
| 35 | 39× | hevm.prank(ADDRESS_VAULT); |
| 36 | 49× | ousd.mint(ADDRESS_OUTSIDER_USER, STARTING_BALANCE / 2); |
| 37 | ||
| 38 | // Non-rebasing tokens |
|
| 39 | 41× | hevm.prank(ADDRESS_VAULT); |
| 40 | 58× | ousd.mint(ADDRESS_OUTSIDER_CONTRACT, STARTING_BALANCE / 2); |
| 41 | } |
|
| 42 | } |
|
| 43 | } |
92%
contracts/contracts/echidna/EchidnaTestAccounting.sol
Lines covered: 37 / 40 (92%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./EchidnaDebug.sol"; |
|
| 5 | import "./EchidnaTestSupply.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @title Mixin for testing accounting functions |
|
| 9 | * @author Rappie |
|
| 10 | */ |
|
| 11 | 0 | contract EchidnaTestAccounting is EchidnaTestSupply { |
| 12 | /** |
|
| 13 | * @notice After opting in, balance should not increase. (Ok to lose rounding funds doing this) |
|
| 14 | * @param targetAcc Account to opt in |
|
| 15 | */ |
|
| 16 | 11× | function testOptInBalance(uint8 targetAcc) public { |
| 17 | 8× | address target = getAccount(targetAcc); |
| 18 | ||
| 19 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 20 | 5× | optIn(targetAcc); |
| 21 | 64× | uint256 balanceAfter = ousd.balanceOf(target); |
| 22 | ||
| 23 | 9× | assert(balanceAfter <= balanceBefore); |
| 24 | } |
|
| 25 | ||
| 26 | /** |
|
| 27 | * @notice After opting out, balance should remain the same |
|
| 28 | * @param targetAcc Account to opt out |
|
| 29 | */ |
|
| 30 | 11× | function testOptOutBalance(uint8 targetAcc) public { |
| 31 | 8× | address target = getAccount(targetAcc); |
| 32 | ||
| 33 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 34 | 5× | optOut(targetAcc); |
| 35 | 19× | uint256 balanceAfter = ousd.balanceOf(target); |
| 36 | ||
| 37 | assert(balanceAfter == balanceBefore); |
|
| 38 | } |
|
| 39 | ||
| 40 | /** |
|
| 41 | * @notice Account balance should remain the same after opting in minus rounding error |
|
| 42 | * @param targetAcc Account to opt in |
|
| 43 | */ |
|
| 44 | 11× | function testOptInBalanceRounding(uint8 targetAcc) public { |
| 45 | 8× | address target = getAccount(targetAcc); |
| 46 | ||
| 47 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 48 | 5× | optIn(targetAcc); |
| 49 | 64× | uint256 balanceAfter = ousd.balanceOf(target); |
| 50 | ||
| 51 | 8× | int256 delta = int256(balanceAfter) - int256(balanceBefore); |
| 52 | 21× | Debugger.log("delta", delta); |
| 53 | ||
| 54 | // slither-disable-next-line tautology |
|
| 55 | 14× | assert(-1 * delta >= 0); |
| 56 | 9× | assert(-1 * delta <= int256(OPT_IN_ROUNDING_ERROR)); |
| 57 | } |
|
| 58 | ||
| 59 | /** |
|
| 60 | * @notice After opting in, total supply should remain the same |
|
| 61 | * @param targetAcc Account to opt in |
|
| 62 | */ |
|
| 63 | 11× | function testOptInTotalSupply(uint8 targetAcc) public { |
| 64 | 70× | uint256 totalSupplyBefore = ousd.totalSupply(); |
| 65 | 4× | optIn(targetAcc); |
| 66 | uint256 totalSupplyAfter = ousd.totalSupply(); |
|
| 67 | ||
| 68 | assert(totalSupplyAfter == totalSupplyBefore); |
|
| 69 | } |
|
| 70 | ||
| 71 | /** |
|
| 72 | * @notice After opting out, total supply should remain the same |
|
| 73 | * @param targetAcc Account to opt out |
|
| 74 | */ |
|
| 75 | 11× | function testOptOutTotalSupply(uint8 targetAcc) public { |
| 76 | 70× | uint256 totalSupplyBefore = ousd.totalSupply(); |
| 77 | 5× | optOut(targetAcc); |
| 78 | 70× | uint256 totalSupplyAfter = ousd.totalSupply(); |
| 79 | ||
| 80 | 5× | assert(totalSupplyAfter == totalSupplyBefore); |
| 81 | } |
|
| 82 | ||
| 83 | /** |
|
| 84 | * @notice Account balance should remain the same when a smart contract auto converts |
|
| 85 | * @param targetAcc Account to auto convert |
|
| 86 | */ |
|
| 87 | 21× | function testAutoConvertBalance(uint8 targetAcc) public { |
| 88 | 8× | address target = getAccount(targetAcc); |
| 89 | ||
| 90 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 91 | // slither-disable-next-line unused-return |
|
| 92 | 60× | ousd._isNonRebasingAccountEchidna(target); |
| 93 | 130× | uint256 balanceAfter = ousd.balanceOf(target); |
| 94 | ||
| 95 | 12× | assert(balanceAfter == balanceBefore); |
| 96 | } |
|
| 97 | ||
| 98 | /** |
|
| 99 | * @notice The `balanceOf` function should never revert |
|
| 100 | * @param targetAcc Account to check balance of |
|
| 101 | */ |
|
| 102 | 11× | function testBalanceOfShouldNotRevert(uint8 targetAcc) public { |
| 103 | 8× | address target = getAccount(targetAcc); |
| 104 | ||
| 105 | // slither-disable-next-line unused-return |
|
| 106 | 58× | try ousd.balanceOf(target) { |
| 107 | 0 | assert(true); |
| 108 | } catch { |
|
| 109 | 0 | assert(false); |
| 110 | } |
|
| 111 | } |
|
| 112 | } |
96%
contracts/contracts/echidna/EchidnaTestApproval.sol
Lines covered: 30 / 31 (96%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./EchidnaTestMintBurn.sol"; |
|
| 5 | import "./Debugger.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @title Mixin for testing approval related functions |
|
| 9 | * @author Rappie |
|
| 10 | */ |
|
| 11 | 0 | contract EchidnaTestApproval is EchidnaTestMintBurn { |
| 12 | /** |
|
| 13 | * @notice Performing `transferFrom` with an amount inside the allowance should not revert |
|
| 14 | * @param authorizedAcc The account that is authorized to transfer |
|
| 15 | * @param fromAcc The account that is transferring |
|
| 16 | * @param toAcc The account that is receiving |
|
| 17 | * @param amount The amount to transfer |
|
| 18 | */ |
|
| 19 | 11× | function testTransferFromShouldNotRevert( |
| 20 | uint8 authorizedAcc, |
|
| 21 | uint8 fromAcc, |
|
| 22 | uint8 toAcc, |
|
| 23 | uint256 amount |
|
| 24 | ) public { |
|
| 25 | 8× | address authorized = getAccount(authorizedAcc); |
| 26 | 8× | address from = getAccount(fromAcc); |
| 27 | 8× | address to = getAccount(toAcc); |
| 28 | ||
| 29 | 67× | require(amount <= ousd.balanceOf(from)); |
| 30 | 68× | require(amount <= ousd.allowance(from, authorized)); |
| 31 | ||
| 32 | 41× | hevm.prank(authorized); |
| 33 | // slither-disable-next-line unchecked-transfer |
|
| 34 | 60× | try ousd.transferFrom(from, to, amount) { |
| 35 | // pass |
|
| 36 | } catch { |
|
| 37 | 3× | assert(false); |
| 38 | } |
|
| 39 | } |
|
| 40 | ||
| 41 | /** |
|
| 42 | * @notice Performing `transferFrom` with an amount outside the allowance should revert |
|
| 43 | * @param authorizedAcc The account that is authorized to transfer |
|
| 44 | * @param fromAcc The account that is transferring |
|
| 45 | * @param toAcc The account that is receiving |
|
| 46 | * @param amount The amount to transfer |
|
| 47 | */ |
|
| 48 | 16× | function testTransferFromShouldRevert( |
| 49 | uint8 authorizedAcc, |
|
| 50 | uint8 fromAcc, |
|
| 51 | uint8 toAcc, |
|
| 52 | uint256 amount |
|
| 53 | 3× | ) public { |
| 54 | 8× | address authorized = getAccount(authorizedAcc); |
| 55 | 8× | address from = getAccount(fromAcc); |
| 56 | 8× | address to = getAccount(toAcc); |
| 57 | ||
| 58 | 9× | require(amount > 0); |
| 59 | 6× | require( |
| 60 | 68× | !(amount <= ousd.balanceOf(from) && |
| 61 | 62× | amount <= ousd.allowance(from, authorized)) |
| 62 | ); |
|
| 63 | ||
| 64 | 41× | hevm.prank(authorized); |
| 65 | // slither-disable-next-line unchecked-transfer |
|
| 66 | 38× | try ousd.transferFrom(from, to, amount) { |
| 67 | 1× | assert(false); |
| 68 | } catch { |
|
| 69 | // pass |
|
| 70 | } |
|
| 71 | } |
|
| 72 | ||
| 73 | /** |
|
| 74 | * @notice Approving an amount should update the allowance and overwrite any previous allowance |
|
| 75 | * @param ownerAcc The account that is approving |
|
| 76 | * @param spenderAcc The account that is being approved |
|
| 77 | * @param amount The amount to approve |
|
| 78 | */ |
|
| 79 | 11× | function testApprove( |
| 80 | uint8 ownerAcc, |
|
| 81 | uint8 spenderAcc, |
|
| 82 | uint256 amount |
|
| 83 | ) public { |
|
| 84 | 8× | address owner = getAccount(ownerAcc); |
| 85 | 8× | address spender = getAccount(spenderAcc); |
| 86 | ||
| 87 | 7× | approve(ownerAcc, spenderAcc, amount); |
| 88 | 64× | uint256 allowanceAfter1 = ousd.allowance(owner, spender); |
| 89 | ||
| 90 | 6× | assert(allowanceAfter1 == amount); |
| 91 | ||
| 92 | 9× | approve(ownerAcc, spenderAcc, amount / 2); |
| 93 | 63× | uint256 allowanceAfter2 = ousd.allowance(owner, spender); |
| 94 | ||
| 95 | 10× | assert(allowanceAfter2 == amount / 2); |
| 96 | } |
|
| 97 | } |
95%
contracts/contracts/echidna/EchidnaTestMintBurn.sol
Lines covered: 43 / 45 (95%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./EchidnaDebug.sol"; |
|
| 5 | import "./EchidnaTestAccounting.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @title Mixin for testing Mint and Burn functions |
|
| 9 | * @author Rappie |
|
| 10 | */ |
|
| 11 | 0 | contract EchidnaTestMintBurn is EchidnaTestAccounting { |
| 12 | /** |
|
| 13 | * @notice Minting 0 tokens should not affect account balance |
|
| 14 | * @param targetAcc Account to mint to |
|
| 15 | */ |
|
| 16 | 11× | function testMintZeroBalance(uint8 targetAcc) public { |
| 17 | 8× | address target = getAccount(targetAcc); |
| 18 | ||
| 19 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 20 | 5× | mint(targetAcc, 0); |
| 21 | uint256 balanceAfter = ousd.balanceOf(target); |
|
| 22 | ||
| 23 | assert(balanceAfter == balanceBefore); |
|
| 24 | } |
|
| 25 | ||
| 26 | /** |
|
| 27 | * @notice Burning 0 tokens should not affect account balance |
|
| 28 | * @param targetAcc Account to burn from |
|
| 29 | */ |
|
| 30 | 11× | function testBurnZeroBalance(uint8 targetAcc) public { |
| 31 | 8× | address target = getAccount(targetAcc); |
| 32 | ||
| 33 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 34 | 5× | burn(targetAcc, 0); |
| 35 | uint256 balanceAfter = ousd.balanceOf(target); |
|
| 36 | ||
| 37 | assert(balanceAfter == balanceBefore); |
|
| 38 | } |
|
| 39 | ||
| 40 | /** |
|
| 41 | * @notice Minting tokens must increase the account balance by at least amount |
|
| 42 | * @param targetAcc Account to mint to |
|
| 43 | * @param amount Amount to mint |
|
| 44 | * @custom:error testMintBalance(uint8,uint256): failed!💥 |
|
| 45 | * Call sequence: |
|
| 46 | * changeSupply(1) |
|
| 47 | * testMintBalance(0,1) |
|
| 48 | * Event sequence: |
|
| 49 | * Debug(«balanceBefore», 0) |
|
| 50 | * Debug(«balanceAfter», 0) |
|
| 51 | */ |
|
| 52 | 17× | function testMintBalance(uint8 targetAcc, uint256 amount) |
| 53 | public |
|
| 54 | hasKnownIssue |
|
| 55 | hasKnownIssueWithinLimits |
|
| 56 | { |
|
| 57 | 8× | address target = getAccount(targetAcc); |
| 58 | ||
| 59 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 60 | 9× | uint256 amountMinted = mint(targetAcc, amount); |
| 61 | 63× | uint256 balanceAfter = ousd.balanceOf(target); |
| 62 | ||
| 63 | 21× | Debugger.log("amountMinted", amountMinted); |
| 64 | 21× | Debugger.log("balanceBefore", balanceBefore); |
| 65 | 21× | Debugger.log("balanceAfter", balanceAfter); |
| 66 | ||
| 67 | 11× | assert(balanceAfter >= balanceBefore + amountMinted); |
| 68 | } |
|
| 69 | ||
| 70 | /** |
|
| 71 | * @notice Burning tokens must decrease the account balance by at least amount |
|
| 72 | * @param targetAcc Account to burn from |
|
| 73 | * @param amount Amount to burn |
|
| 74 | * @custom:error testBurnBalance(uint8,uint256): failed!💥 |
|
| 75 | * Call sequence: |
|
| 76 | * changeSupply(1) |
|
| 77 | * mint(0,3) |
|
| 78 | * testBurnBalance(0,1) |
|
| 79 | * Event sequence: |
|
| 80 | * Debug(«balanceBefore», 2) |
|
| 81 | * Debug(«balanceAfter», 2) |
|
| 82 | */ |
|
| 83 | 11× | function testBurnBalance(uint8 targetAcc, uint256 amount) |
| 84 | public |
|
| 85 | hasKnownIssue |
|
| 86 | hasKnownIssueWithinLimits |
|
| 87 | { |
|
| 88 | 8× | address target = getAccount(targetAcc); |
| 89 | ||
| 90 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 91 | 6× | burn(targetAcc, amount); |
| 92 | 64× | uint256 balanceAfter = ousd.balanceOf(target); |
| 93 | ||
| 94 | 21× | Debugger.log("balanceBefore", balanceBefore); |
| 95 | 21× | Debugger.log("balanceAfter", balanceAfter); |
| 96 | ||
| 97 | 14× | assert(balanceAfter <= balanceBefore - amount); |
| 98 | } |
|
| 99 | ||
| 100 | /** |
|
| 101 | * @notice Minting tokens should not increase the account balance by less than rounding error above amount |
|
| 102 | * @param targetAcc Account to mint to |
|
| 103 | * @param amount Amount to mint |
|
| 104 | */ |
|
| 105 | 11× | function testMintBalanceRounding(uint8 targetAcc, uint256 amount) public { |
| 106 | 8× | address target = getAccount(targetAcc); |
| 107 | ||
| 108 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 109 | 9× | uint256 amountMinted = mint(targetAcc, amount); |
| 110 | 63× | uint256 balanceAfter = ousd.balanceOf(target); |
| 111 | ||
| 112 | 8× | int256 delta = int256(balanceAfter) - int256(balanceBefore); |
| 113 | ||
| 114 | // delta == amount, if no error |
|
| 115 | // delta < amount, if too little is minted |
|
| 116 | // delta > amount, if too much is minted |
|
| 117 | 8× | int256 error = int256(amountMinted) - delta; |
| 118 | ||
| 119 | 10× | assert(error >= 0); |
| 120 | 5× | assert(error <= int256(MINT_ROUNDING_ERROR)); |
| 121 | } |
|
| 122 | ||
| 123 | /** |
|
| 124 | * @notice A burn of an account balance must result in a zero balance |
|
| 125 | * @param targetAcc Account to burn from |
|
| 126 | */ |
|
| 127 | 11× | function testBurnAllBalanceToZero(uint8 targetAcc) public hasKnownIssue { |
| 128 | 8× | address target = getAccount(targetAcc); |
| 129 | ||
| 130 | 64× | burn(targetAcc, ousd.balanceOf(target)); |
| 131 | 62× | assert(ousd.balanceOf(target) == 0); |
| 132 | } |
|
| 133 | ||
| 134 | /** |
|
| 135 | * @notice You should always be able to burn an account's balance |
|
| 136 | * @param targetAcc Account to burn from |
|
| 137 | */ |
|
| 138 | 11× | function testBurnAllBalanceShouldNotRevert(uint8 targetAcc) |
| 139 | public |
|
| 140 | hasKnownIssue |
|
| 141 | { |
|
| 142 | 8× | address target = getAccount(targetAcc); |
| 143 | 63× | uint256 balance = ousd.balanceOf(target); |
| 144 | ||
| 145 | 39× | hevm.prank(ADDRESS_VAULT); |
| 146 | 48× | try ousd.burn(target, balance) { |
| 147 | assert(true); |
|
| 148 | } catch { |
|
| 149 | 0 | assert(false); |
| 150 | } |
|
| 151 | } |
|
| 152 | } |
95%
contracts/contracts/echidna/EchidnaTestSupply.sol
Lines covered: 39 / 41 (95%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./EchidnaDebug.sol"; |
|
| 5 | import "./EchidnaTestTransfer.sol"; |
|
| 6 | ||
| 7 | import { StableMath } from "../utils/StableMath.sol"; |
|
| 8 | ||
| 9 | /** |
|
| 10 | * @title Mixin for testing supply related functions |
|
| 11 | * @author Rappie |
|
| 12 | */ |
|
| 13 | 0 | contract EchidnaTestSupply is EchidnaTestTransfer { |
| 14 | using StableMath for uint256; |
|
| 15 | ||
| 16 | 2× | uint256 prevRebasingCreditsPerToken = type(uint256).max; |
| 17 | ||
| 18 | /** |
|
| 19 | * @notice After a `changeSupply`, the total supply should exactly |
|
| 20 | * match the target total supply. (This is needed to ensure successive |
|
| 21 | * rebases are correct). |
|
| 22 | * @param supply New total supply |
|
| 23 | * @custom:error testChangeSupply(uint256): failed!💥 |
|
| 24 | * Call sequence: |
|
| 25 | * testChangeSupply(1044505275072865171609) |
|
| 26 | * Event sequence: |
|
| 27 | * TotalSupplyUpdatedHighres(1044505275072865171610, 1000000000000000000000000, 957391048054055578595) |
|
| 28 | */ |
|
| 29 | 15× | function testChangeSupply(uint256 supply) |
| 30 | public |
|
| 31 | hasKnownIssue |
|
| 32 | hasKnownIssueWithinLimits |
|
| 33 | { |
|
| 34 | 41× | hevm.prank(ADDRESS_VAULT); |
| 35 | 51× | ousd.changeSupply(supply); |
| 36 | ||
| 37 | 76× | assert(ousd.totalSupply() == supply); |
| 38 | } |
|
| 39 | ||
| 40 | /** |
|
| 41 | * @notice The total supply must not be less than the sum of account balances. |
|
| 42 | * (The difference will go into future rebases) |
|
| 43 | * @custom:error testTotalSupplyLessThanTotalBalance(): failed!💥 |
|
| 44 | * Call sequence: |
|
| 45 | * mint(0,1) |
|
| 46 | * changeSupply(1) |
|
| 47 | * optOut(64) |
|
| 48 | * transfer(0,64,1) |
|
| 49 | * testTotalSupplyLessThanTotalBalance() |
|
| 50 | * Event sequence: |
|
| 51 | * Debug(«totalSupply», 1000000000000000001000001) |
|
| 52 | * Debug(«totalBalance», 1000000000000000001000002) |
|
| 53 | */ |
|
| 54 | 5× | function testTotalSupplyLessThanTotalBalance() |
| 55 | public |
|
| 56 | hasKnownIssue |
|
| 57 | hasKnownIssueWithinLimits |
|
| 58 | { |
|
| 59 | 70× | uint256 totalSupply = ousd.totalSupply(); |
| 60 | 7× | uint256 totalBalance = getTotalBalance(); |
| 61 | ||
| 62 | 21× | Debugger.log("totalSupply", totalSupply); |
| 63 | 20× | Debugger.log("totalBalance", totalBalance); |
| 64 | ||
| 65 | assert(totalSupply >= totalBalance); |
|
| 66 | } |
|
| 67 | ||
| 68 | /** |
|
| 69 | * @notice Non-rebasing supply should not be larger than total supply |
|
| 70 | * @custom:error testNonRebasingSupplyVsTotalSupply(): failed!💥 |
|
| 71 | * Call sequence: |
|
| 72 | * mint(0,2) |
|
| 73 | * changeSupply(3) |
|
| 74 | * burn(0,1) |
|
| 75 | * optOut(0) |
|
| 76 | * testNonRebasingSupplyVsTotalSupply() |
|
| 77 | */ |
|
| 78 | 5× | function testNonRebasingSupplyVsTotalSupply() public hasKnownIssue { |
| 79 | 70× | uint256 nonRebasingSupply = ousd.nonRebasingSupply(); |
| 80 | 70× | uint256 totalSupply = ousd.totalSupply(); |
| 81 | ||
| 82 | 6× | assert(nonRebasingSupply <= totalSupply); |
| 83 | } |
|
| 84 | ||
| 85 | /** |
|
| 86 | * @notice Global `rebasingCreditsPerToken` should never increase |
|
| 87 | * @custom:error testRebasingCreditsPerTokenNotIncreased(): failed!💥 |
|
| 88 | * Call sequence: |
|
| 89 | * testRebasingCreditsPerTokenNotIncreased() |
|
| 90 | * changeSupply(1) |
|
| 91 | * testRebasingCreditsPerTokenNotIncreased() |
|
| 92 | */ |
|
| 93 | 6× | function testRebasingCreditsPerTokenNotIncreased() public hasKnownIssue { |
| 94 | 70× | uint256 curRebasingCreditsPerToken = ousd |
| 95 | .rebasingCreditsPerTokenHighres(); |
|
| 96 | ||
| 97 | 21× | Debugger.log( |
| 98 | "prevRebasingCreditsPerToken", |
|
| 99 | 2× | prevRebasingCreditsPerToken |
| 100 | ); |
|
| 101 | 22× | Debugger.log("curRebasingCreditsPerToken", curRebasingCreditsPerToken); |
| 102 | ||
| 103 | 11× | assert(curRebasingCreditsPerToken <= prevRebasingCreditsPerToken); |
| 104 | ||
| 105 | 2× | prevRebasingCreditsPerToken = curRebasingCreditsPerToken; |
| 106 | } |
|
| 107 | ||
| 108 | /** |
|
| 109 | * @notice The rebasing credits per token ratio must greater than zero |
|
| 110 | */ |
|
| 111 | 5× | function testRebasingCreditsPerTokenAboveZero() public { |
| 112 | 71× | assert(ousd.rebasingCreditsPerTokenHighres() > 0); |
| 113 | } |
|
| 114 | ||
| 115 | /** |
|
| 116 | * @notice The sum of all non-rebasing balances should not be larger than |
|
| 117 | * non-rebasing supply |
|
| 118 | * @custom:error testTotalNonRebasingSupplyLessThanTotalBalance(): failed!💥 |
|
| 119 | * Call sequence |
|
| 120 | * mint(0,2) |
|
| 121 | * changeSupply(1) |
|
| 122 | * optOut(0) |
|
| 123 | * burn(0,1) |
|
| 124 | * testTotalNonRebasingSupplyLessThanTotalBalance() |
|
| 125 | * Event sequence: |
|
| 126 | * Debug(«totalNonRebasingSupply», 500000000000000000000001) |
|
| 127 | * Debug(«totalNonRebasingBalance», 500000000000000000000002) |
|
| 128 | */ |
|
| 129 | 6× | function testTotalNonRebasingSupplyLessThanTotalBalance() |
| 130 | public |
|
| 131 | hasKnownIssue |
|
| 132 | hasKnownIssueWithinLimits |
|
| 133 | 0 | { |
| 134 | 70× | uint256 totalNonRebasingSupply = ousd.nonRebasingSupply(); |
| 135 | 7× | uint256 totalNonRebasingBalance = getTotalNonRebasingBalance(); |
| 136 | ||
| 137 | 21× | Debugger.log("totalNonRebasingSupply", totalNonRebasingSupply); |
| 138 | 22× | Debugger.log("totalNonRebasingBalance", totalNonRebasingBalance); |
| 139 | ||
| 140 | 9× | assert(totalNonRebasingSupply >= totalNonRebasingBalance); |
| 141 | } |
|
| 142 | ||
| 143 | /** |
|
| 144 | * @notice An accounts credits / credits per token should not be larger it's balance |
|
| 145 | * @param targetAcc The account to check |
|
| 146 | */ |
|
| 147 | 11× | function testCreditsPerTokenVsBalance(uint8 targetAcc) public { |
| 148 | 8× | address target = getAccount(targetAcc); |
| 149 | ||
| 150 | 69× | (uint256 credits, uint256 creditsPerToken, ) = ousd |
| 151 | .creditsBalanceOfHighres(target); |
|
| 152 | 7× | uint256 expectedBalance = credits.divPrecisely(creditsPerToken); |
| 153 | ||
| 154 | 63× | uint256 balance = ousd.balanceOf(target); |
| 155 | ||
| 156 | 21× | Debugger.log("credits", credits); |
| 157 | 21× | Debugger.log("creditsPerToken", creditsPerToken); |
| 158 | 21× | Debugger.log("expectedBalance", expectedBalance); |
| 159 | 21× | Debugger.log("balance", balance); |
| 160 | ||
| 161 | 5× | assert(expectedBalance == balance); |
| 162 | } |
|
| 163 | } |
96%
contracts/contracts/echidna/EchidnaTestTransfer.sol
Lines covered: 103 / 107 (96%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "./EchidnaDebug.sol"; |
|
| 5 | import "./Debugger.sol"; |
|
| 6 | ||
| 7 | /** |
|
| 8 | * @title Mixin for testing transfer related functions |
|
| 9 | * @author Rappie |
|
| 10 | */ |
|
| 11 | 0 | contract EchidnaTestTransfer is EchidnaDebug { |
| 12 | /** |
|
| 13 | * @notice The receiving account's balance after a transfer must not increase by |
|
| 14 | * less than the amount transferred |
|
| 15 | * @param fromAcc Account to transfer from |
|
| 16 | * @param toAcc Account to transfer to |
|
| 17 | * @param amount Amount to transfer |
|
| 18 | * @custom:error testTransferBalanceReceivedLess(uint8,uint8,uint256): failed!💥 |
|
| 19 | * Call sequence: |
|
| 20 | * changeSupply(1) |
|
| 21 | * mint(64,2) |
|
| 22 | * testTransferBalanceReceivedLess(64,0,1) |
|
| 23 | * Event sequence: |
|
| 24 | * Debug(«totalSupply», 1000000000000000000500002) |
|
| 25 | * Debug(«toBalBefore», 0) |
|
| 26 | * Debug(«toBalAfter», 0) |
|
| 27 | */ |
|
| 28 | 11× | function testTransferBalanceReceivedLess( |
| 29 | uint8 fromAcc, |
|
| 30 | uint8 toAcc, |
|
| 31 | uint256 amount |
|
| 32 | ) public hasKnownIssue hasKnownIssueWithinLimits { |
|
| 33 | 8× | address from = getAccount(fromAcc); |
| 34 | 8× | address to = getAccount(toAcc); |
| 35 | ||
| 36 | 11× | require(from != to); |
| 37 | ||
| 38 | 64× | uint256 toBalBefore = ousd.balanceOf(to); |
| 39 | 7× | transfer(fromAcc, toAcc, amount); |
| 40 | 64× | uint256 toBalAfter = ousd.balanceOf(to); |
| 41 | ||
| 42 | 56× | Debugger.log("totalSupply", ousd.totalSupply()); |
| 43 | 21× | Debugger.log("toBalBefore", toBalBefore); |
| 44 | 21× | Debugger.log("toBalAfter", toBalAfter); |
| 45 | ||
| 46 | 5× | assert(toBalAfter >= toBalBefore + amount); |
| 47 | } |
|
| 48 | ||
| 49 | /** |
|
| 50 | * @notice The receiving account's balance after a transfer must not |
|
| 51 | * increase by more than the amount transferred |
|
| 52 | * @param fromAcc Account to transfer from |
|
| 53 | * @param toAcc Account to transfer to |
|
| 54 | * @param amount Amount to transfer |
|
| 55 | */ |
|
| 56 | 11× | function testTransferBalanceReceivedMore( |
| 57 | uint8 fromAcc, |
|
| 58 | uint8 toAcc, |
|
| 59 | uint256 amount |
|
| 60 | ) public { |
|
| 61 | 8× | address from = getAccount(fromAcc); |
| 62 | 8× | address to = getAccount(toAcc); |
| 63 | ||
| 64 | 11× | require(from != to); |
| 65 | ||
| 66 | 64× | uint256 toBalBefore = ousd.balanceOf(to); |
| 67 | 7× | transfer(fromAcc, toAcc, amount); |
| 68 | 64× | uint256 toBalAfter = ousd.balanceOf(to); |
| 69 | ||
| 70 | 56× | Debugger.log("totalSupply", ousd.totalSupply()); |
| 71 | 21× | Debugger.log("toBalBefore", toBalBefore); |
| 72 | 21× | Debugger.log("toBalAfter", toBalAfter); |
| 73 | ||
| 74 | 5× | assert(toBalAfter <= toBalBefore + amount); |
| 75 | } |
|
| 76 | ||
| 77 | /** |
|
| 78 | * @notice The sending account's balance after a transfer must not |
|
| 79 | * decrease by less than the amount transferred |
|
| 80 | * @param fromAcc Account to transfer from |
|
| 81 | * @param toAcc Account to transfer to |
|
| 82 | * @param amount Amount to transfer |
|
| 83 | * @custom:error testTransferBalanceSentLess(uint8,uint8,uint256): failed!💥 |
|
| 84 | * Call sequence: |
|
| 85 | * mint(0,1) |
|
| 86 | * changeSupply(1) |
|
| 87 | * testTransferBalanceSentLess(0,64,1) |
|
| 88 | * Event sequence: |
|
| 89 | * Debug(«totalSupply», 1000000000000000000500001) |
|
| 90 | * Debug(«fromBalBefore», 1) |
|
| 91 | * Debug(«fromBalAfter», 1) |
|
| 92 | */ |
|
| 93 | 19× | function testTransferBalanceSentLess( |
| 94 | uint8 fromAcc, |
|
| 95 | uint8 toAcc, |
|
| 96 | uint256 amount |
|
| 97 | 0 | ) public hasKnownIssue hasKnownIssueWithinLimits { |
| 98 | 8× | address from = getAccount(fromAcc); |
| 99 | 8× | address to = getAccount(toAcc); |
| 100 | ||
| 101 | 11× | require(from != to); |
| 102 | ||
| 103 | 64× | uint256 fromBalBefore = ousd.balanceOf(from); |
| 104 | 7× | transfer(fromAcc, toAcc, amount); |
| 105 | 64× | uint256 fromBalAfter = ousd.balanceOf(from); |
| 106 | ||
| 107 | 56× | Debugger.log("totalSupply", ousd.totalSupply()); |
| 108 | 21× | Debugger.log("fromBalBefore", fromBalBefore); |
| 109 | 21× | Debugger.log("fromBalAfter", fromBalAfter); |
| 110 | ||
| 111 | 14× | assert(fromBalAfter <= fromBalBefore - amount); |
| 112 | } |
|
| 113 | ||
| 114 | /** |
|
| 115 | * @notice The sending account's balance after a transfer must not |
|
| 116 | * decrease by more than the amount transferred |
|
| 117 | * @param fromAcc Account to transfer from |
|
| 118 | * @param toAcc Account to transfer to |
|
| 119 | * @param amount Amount to transfer |
|
| 120 | */ |
|
| 121 | 11× | function testTransferBalanceSentMore( |
| 122 | uint8 fromAcc, |
|
| 123 | uint8 toAcc, |
|
| 124 | uint256 amount |
|
| 125 | ) public { |
|
| 126 | 8× | address from = getAccount(fromAcc); |
| 127 | 8× | address to = getAccount(toAcc); |
| 128 | ||
| 129 | 11× | require(from != to); |
| 130 | ||
| 131 | 64× | uint256 fromBalBefore = ousd.balanceOf(from); |
| 132 | 7× | transfer(fromAcc, toAcc, amount); |
| 133 | 64× | uint256 fromBalAfter = ousd.balanceOf(from); |
| 134 | ||
| 135 | 56× | Debugger.log("totalSupply", ousd.totalSupply()); |
| 136 | 21× | Debugger.log("fromBalBefore", fromBalBefore); |
| 137 | 21× | Debugger.log("fromBalAfter", fromBalAfter); |
| 138 | ||
| 139 | 11× | assert(fromBalAfter >= fromBalBefore - amount); |
| 140 | } |
|
| 141 | ||
| 142 | /** |
|
| 143 | * @notice The receiving account's balance after a transfer must not |
|
| 144 | * increase by less than the amount transferred (minus rounding error) |
|
| 145 | * @param fromAcc Account to transfer from |
|
| 146 | * @param toAcc Account to transfer to |
|
| 147 | * @param amount Amount to transfer |
|
| 148 | */ |
|
| 149 | 15× | function testTransferBalanceReceivedLessRounding( |
| 150 | uint8 fromAcc, |
|
| 151 | uint8 toAcc, |
|
| 152 | uint256 amount |
|
| 153 | 6× | ) public { |
| 154 | 8× | address from = getAccount(fromAcc); |
| 155 | 8× | address to = getAccount(toAcc); |
| 156 | ||
| 157 | 11× | require(from != to); |
| 158 | ||
| 159 | 64× | uint256 toBalBefore = ousd.balanceOf(to); |
| 160 | 7× | transfer(fromAcc, toAcc, amount); |
| 161 | 64× | uint256 toBalAfter = ousd.balanceOf(to); |
| 162 | ||
| 163 | 8× | int256 toDelta = int256(toBalAfter) - int256(toBalBefore); |
| 164 | ||
| 165 | // delta == amount, if no error |
|
| 166 | // delta < amount, if too little is sent |
|
| 167 | // delta > amount, if too much is sent |
|
| 168 | 8× | int256 error = int256(amount) - toDelta; |
| 169 | ||
| 170 | 56× | Debugger.log("totalSupply", ousd.totalSupply()); |
| 171 | 21× | Debugger.log("toBalBefore", toBalBefore); |
| 172 | 21× | Debugger.log("toBalAfter", toBalAfter); |
| 173 | 21× | Debugger.log("toDelta", toDelta); |
| 174 | 21× | Debugger.log("error", error); |
| 175 | ||
| 176 | 10× | assert(error >= 0); |
| 177 | 6× | assert(error <= int256(TRANSFER_ROUNDING_ERROR)); |
| 178 | } |
|
| 179 | ||
| 180 | /** |
|
| 181 | * @notice The sending account's balance after a transfer must |
|
| 182 | * not decrease by less than the amount transferred (minus rounding error) |
|
| 183 | * @param fromAcc Account to transfer from |
|
| 184 | * @param toAcc Account to transfer to |
|
| 185 | * @param amount Amount to transfer |
|
| 186 | */ |
|
| 187 | 11× | function testTransferBalanceSentLessRounding( |
| 188 | uint8 fromAcc, |
|
| 189 | uint8 toAcc, |
|
| 190 | uint256 amount |
|
| 191 | ) public { |
|
| 192 | 8× | address from = getAccount(fromAcc); |
| 193 | 8× | address to = getAccount(toAcc); |
| 194 | ||
| 195 | 11× | require(from != to); |
| 196 | ||
| 197 | 64× | uint256 fromBalBefore = ousd.balanceOf(from); |
| 198 | 7× | transfer(fromAcc, toAcc, amount); |
| 199 | 64× | uint256 fromBalAfter = ousd.balanceOf(from); |
| 200 | ||
| 201 | 8× | int256 fromDelta = int256(fromBalAfter) - int256(fromBalBefore); |
| 202 | ||
| 203 | // delta == -amount, if no error |
|
| 204 | // delta < -amount, if too much is sent |
|
| 205 | // delta > -amount, if too little is sent |
|
| 206 | 8× | int256 error = int256(amount) + fromDelta; |
| 207 | ||
| 208 | 56× | Debugger.log("totalSupply", ousd.totalSupply()); |
| 209 | 21× | Debugger.log("fromBalBefore", fromBalBefore); |
| 210 | 21× | Debugger.log("fromBalAfter", fromBalAfter); |
| 211 | 20× | Debugger.log("fromDelta", fromDelta); |
| 212 | Debugger.log("error", error); |
|
| 213 | ||
| 214 | assert(error >= 0); |
|
| 215 | assert(error <= int256(TRANSFER_ROUNDING_ERROR)); |
|
| 216 | } |
|
| 217 | ||
| 218 | /** |
|
| 219 | * @notice An account should always be able to successfully transfer |
|
| 220 | * an amount within its balance. |
|
| 221 | * @param fromAcc Account to transfer from |
|
| 222 | * @param toAcc Account to transfer to |
|
| 223 | * @param amount Amount to transfer |
|
| 224 | * @custom:error testTransferWithinBalanceDoesNotRevert(uint8,uint8,uint8): failed!💥 |
|
| 225 | * Call sequence: |
|
| 226 | * mint(0,1) |
|
| 227 | * changeSupply(3) |
|
| 228 | * optOut(0) |
|
| 229 | * testTransferWithinBalanceDoesNotRevert(0,128,2) |
|
| 230 | * optIn(0) |
|
| 231 | * testTransferWithinBalanceDoesNotRevert(128,0,1) |
|
| 232 | * Event sequence: |
|
| 233 | * error Revert Panic(17): SafeMath over-/under-flows |
|
| 234 | */ |
|
| 235 | 23× | function testTransferWithinBalanceDoesNotRevert( |
| 236 | uint8 fromAcc, |
|
| 237 | uint8 toAcc, |
|
| 238 | uint256 amount |
|
| 239 | 4× | ) public hasKnownIssue { |
| 240 | 8× | address from = getAccount(fromAcc); |
| 241 | 8× | address to = getAccount(toAcc); |
| 242 | ||
| 243 | 9× | require(amount > 0); |
| 244 | 67× | amount = amount % ousd.balanceOf(from); |
| 245 | ||
| 246 | 118× | Debugger.log("Total supply", ousd.totalSupply()); |
| 247 | ||
| 248 | 41× | hevm.prank(from); |
| 249 | // slither-disable-next-line unchecked-transfer |
|
| 250 | 64× | try ousd.transfer(to, amount) { |
| 251 | 2× | assert(true); |
| 252 | } catch { |
|
| 253 | 4× | assert(false); |
| 254 | } |
|
| 255 | } |
|
| 256 | ||
| 257 | /** |
|
| 258 | * @notice An account should never be able to successfully transfer |
|
| 259 | * an amount greater than their balance. |
|
| 260 | * @param fromAcc Account to transfer from |
|
| 261 | * @param toAcc Account to transfer to |
|
| 262 | * @param amount Amount to transfer |
|
| 263 | */ |
|
| 264 | 11× | function testTransferExceedingBalanceReverts( |
| 265 | uint8 fromAcc, |
|
| 266 | uint8 toAcc, |
|
| 267 | uint256 amount |
|
| 268 | ) public { |
|
| 269 | 8× | address from = getAccount(fromAcc); |
| 270 | 8× | address to = getAccount(toAcc); |
| 271 | ||
| 272 | 75× | amount = ousd.balanceOf(from) + 1 + amount; |
| 273 | ||
| 274 | 40× | hevm.prank(from); |
| 275 | // slither-disable-next-line unchecked-transfer |
|
| 276 | 37× | try ousd.transfer(to, amount) { |
| 277 | 0 | assert(false); |
| 278 | } catch { |
|
| 279 | 1× | assert(true); |
| 280 | } |
|
| 281 | } |
|
| 282 | ||
| 283 | /** |
|
| 284 | * @notice A transfer to the same account should not change that account's balance |
|
| 285 | * @param targetAcc Account to transfer to |
|
| 286 | * @param amount Amount to transfer |
|
| 287 | */ |
|
| 288 | 11× | function testTransferSelf(uint8 targetAcc, uint256 amount) public { |
| 289 | 8× | address target = getAccount(targetAcc); |
| 290 | ||
| 291 | 63× | uint256 balanceBefore = ousd.balanceOf(target); |
| 292 | 7× | transfer(targetAcc, targetAcc, amount); |
| 293 | 64× | uint256 balanceAfter = ousd.balanceOf(target); |
| 294 | ||
| 295 | 8× | assert(balanceBefore == balanceAfter); |
| 296 | } |
|
| 297 | ||
| 298 | /** |
|
| 299 | * @notice Transfers to the zero account revert |
|
| 300 | * @param fromAcc Account to transfer from |
|
| 301 | * @param amount Amount to transfer |
|
| 302 | */ |
|
| 303 | 11× | function testTransferToZeroAddress(uint8 fromAcc, uint256 amount) public { |
| 304 | 8× | address from = getAccount(fromAcc); |
| 305 | ||
| 306 | 40× | hevm.prank(from); |
| 307 | // slither-disable-next-line unchecked-transfer |
|
| 308 | 37× | try ousd.transfer(address(0), amount) { |
| 309 | 0 | assert(false); |
| 310 | } catch { |
|
| 311 | 1× | assert(true); |
| 312 | } |
|
| 313 | } |
|
| 314 | } |
100%
contracts/contracts/echidna/OUSDEchidna.sol
Lines covered: 7 / 7 (100%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import "../token/OUSD.sol"; |
|
| 5 | ||
| 6 | 277× | contract OUSDEchidna is OUSD { |
| 7 | 6× | constructor() OUSD() { |
| 8 | // The Yield Delegation rewrite (#2298) left Governable without a |
|
| 9 | // constructor, so the governor slot is zero and the harness could |
|
| 10 | // never call initialize() (onlyGovernor). Make the deploying harness |
|
| 11 | // the governor so the suite is runnable again. |
|
| 12 | 5× | _setGovernor(msg.sender); |
| 13 | } |
|
| 14 | ||
| 15 | 12× | function _isNonRebasingAccountEchidna(address _account) |
| 16 | public |
|
| 17 | 1× | returns (bool) |
| 18 | { |
|
| 19 | 5× | _autoMigrate(_account); |
| 20 | 15× | return alternativeCreditsPerToken[_account] > 0; |
| 21 | } |
|
| 22 | } |
29%
contracts/contracts/governance/Governable.sol
Lines covered: 7 / 24 (29%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | /** |
|
| 5 | * @title Base for contracts that are managed by the Origin Protocol's Governor. |
|
| 6 | * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change |
|
| 7 | * from owner to governor and renounce methods removed. Does not use |
|
| 8 | * Context.sol like Ownable.sol does for simplification. |
|
| 9 | * @author Origin Protocol Inc |
|
| 10 | */ |
|
| 11 | abstract contract Governable { |
|
| 12 | // Storage position of the owner and pendingOwner of the contract |
|
| 13 | // keccak256("OUSD.governor"); |
|
| 14 | bytes32 private constant governorPosition = |
|
| 15 | 0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a; |
|
| 16 | ||
| 17 | // keccak256("OUSD.pending.governor"); |
|
| 18 | bytes32 private constant pendingGovernorPosition = |
|
| 19 | 0 | 0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db; |
| 20 | ||
| 21 | // keccak256("OUSD.reentry.status"); |
|
| 22 | bytes32 private constant reentryStatusPosition = |
|
| 23 | 0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535; |
|
| 24 | ||
| 25 | // See OpenZeppelin ReentrancyGuard implementation |
|
| 26 | uint256 constant _NOT_ENTERED = 1; |
|
| 27 | uint256 constant _ENTERED = 2; |
|
| 28 | ||
| 29 | event PendingGovernorshipTransfer( |
|
| 30 | address indexed previousGovernor, |
|
| 31 | address indexed newGovernor |
|
| 32 | ); |
|
| 33 | ||
| 34 | event GovernorshipTransferred( |
|
| 35 | address indexed previousGovernor, |
|
| 36 | address indexed newGovernor |
|
| 37 | ); |
|
| 38 | ||
| 39 | /** |
|
| 40 | * @notice Returns the address of the current Governor. |
|
| 41 | */ |
|
| 42 | 0 | function governor() public view returns (address) { |
| 43 | 0 | return _governor(); |
| 44 | } |
|
| 45 | ||
| 46 | /** |
|
| 47 | * @dev Returns the address of the current Governor. |
|
| 48 | */ |
|
| 49 | 1× | function _governor() internal view returns (address governorOut) { |
| 50 | bytes32 position = governorPosition; |
|
| 51 | // solhint-disable-next-line no-inline-assembly |
|
| 52 | assembly { |
|
| 53 | 2× | governorOut := sload(position) |
| 54 | } |
|
| 55 | } |
|
| 56 | ||
| 57 | /** |
|
| 58 | * @dev Returns the address of the pending Governor. |
|
| 59 | */ |
|
| 60 | function _pendingGovernor() |
|
| 61 | internal |
|
| 62 | view |
|
| 63 | returns (address pendingGovernor) |
|
| 64 | { |
|
| 65 | bytes32 position = pendingGovernorPosition; |
|
| 66 | // solhint-disable-next-line no-inline-assembly |
|
| 67 | assembly { |
|
| 68 | 0 | pendingGovernor := sload(position) |
| 69 | } |
|
| 70 | } |
|
| 71 | ||
| 72 | /** |
|
| 73 | * @dev Throws if called by any account other than the Governor. |
|
| 74 | */ |
|
| 75 | modifier onlyGovernor() { |
|
| 76 | 0 | require(isGovernor(), "Caller is not the Governor"); |
| 77 | _; |
|
| 78 | } |
|
| 79 | ||
| 80 | /** |
|
| 81 | * @notice Returns true if the caller is the current Governor. |
|
| 82 | */ |
|
| 83 | 0 | function isGovernor() public view returns (bool) { |
| 84 | 0 | return msg.sender == _governor(); |
| 85 | } |
|
| 86 | ||
| 87 | 2× | function _setGovernor(address newGovernor) internal { |
| 88 | 15× | emit GovernorshipTransferred(_governor(), newGovernor); |
| 89 | ||
| 90 | bytes32 position = governorPosition; |
|
| 91 | // solhint-disable-next-line no-inline-assembly |
|
| 92 | assembly { |
|
| 93 | 1× | sstore(position, newGovernor) |
| 94 | } |
|
| 95 | } |
|
| 96 | ||
| 97 | /** |
|
| 98 | * @dev Prevents a contract from calling itself, directly or indirectly. |
|
| 99 | * Calling a `nonReentrant` function from another `nonReentrant` |
|
| 100 | * function is not supported. It is possible to prevent this from happening |
|
| 101 | * by making the `nonReentrant` function external, and make it call a |
|
| 102 | * `private` function that does the actual work. |
|
| 103 | */ |
|
| 104 | modifier nonReentrant() { |
|
| 105 | bytes32 position = reentryStatusPosition; |
|
| 106 | uint256 _reentry_status; |
|
| 107 | // solhint-disable-next-line no-inline-assembly |
|
| 108 | assembly { |
|
| 109 | _reentry_status := sload(position) |
|
| 110 | } |
|
| 111 | ||
| 112 | // On the first call to nonReentrant, _notEntered will be true |
|
| 113 | require(_reentry_status != _ENTERED, "Reentrant call"); |
|
| 114 | ||
| 115 | // Any calls to nonReentrant after this point will fail |
|
| 116 | // solhint-disable-next-line no-inline-assembly |
|
| 117 | assembly { |
|
| 118 | sstore(position, _ENTERED) |
|
| 119 | } |
|
| 120 | ||
| 121 | _; |
|
| 122 | ||
| 123 | // By storing the original value once again, a refund is triggered (see |
|
| 124 | // https://eips.ethereum.org/EIPS/eip-2200) |
|
| 125 | // solhint-disable-next-line no-inline-assembly |
|
| 126 | assembly { |
|
| 127 | sstore(position, _NOT_ENTERED) |
|
| 128 | } |
|
| 129 | } |
|
| 130 | ||
| 131 | 0 | function _setPendingGovernor(address newGovernor) internal { |
| 132 | bytes32 position = pendingGovernorPosition; |
|
| 133 | // solhint-disable-next-line no-inline-assembly |
|
| 134 | assembly { |
|
| 135 | 0 | sstore(position, newGovernor) |
| 136 | } |
|
| 137 | } |
|
| 138 | ||
| 139 | /** |
|
| 140 | * @notice Transfers Governance of the contract to a new account (`newGovernor`). |
|
| 141 | * Can only be called by the current Governor. Must be claimed for this to complete |
|
| 142 | * @param _newGovernor Address of the new Governor |
|
| 143 | */ |
|
| 144 | 0 | function transferGovernance(address _newGovernor) external onlyGovernor { |
| 145 | 0 | _setPendingGovernor(_newGovernor); |
| 146 | 0 | emit PendingGovernorshipTransfer(_governor(), _newGovernor); |
| 147 | } |
|
| 148 | ||
| 149 | /** |
|
| 150 | * @notice Claim Governance of the contract to a new account (`newGovernor`). |
|
| 151 | * Can only be called by the new Governor. |
|
| 152 | */ |
|
| 153 | 1× | function claimGovernance() external { |
| 154 | 0 | require( |
| 155 | 0 | msg.sender == _pendingGovernor(), |
| 156 | "Only the pending Governor can complete the claim" |
|
| 157 | ); |
|
| 158 | 1× | _changeGovernor(msg.sender); |
| 159 | } |
|
| 160 | ||
| 161 | /** |
|
| 162 | * @dev Change Governance of the contract to a new account (`newGovernor`). |
|
| 163 | * @param _newGovernor Address of the new Governor |
|
| 164 | */ |
|
| 165 | 0 | function _changeGovernor(address _newGovernor) internal { |
| 166 | 0 | require(_newGovernor != address(0), "New Governor is address(0)"); |
| 167 | 0 | _setGovernor(_newGovernor); |
| 168 | } |
|
| 169 | } |
61%
contracts/contracts/token/OUSD.sol
Lines covered: 176 / 284 (61%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | /** |
|
| 5 | * @title OUSD Token Contract |
|
| 6 | * @dev ERC20 compatible contract for OUSD |
|
| 7 | * @dev Implements an elastic supply |
|
| 8 | * @author Origin Protocol Inc |
|
| 9 | */ |
|
| 10 | import { IVault } from "../interfaces/IVault.sol"; |
|
| 11 | import { Governable } from "../governance/Governable.sol"; |
|
| 12 | import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; |
|
| 13 | ||
| 14 | 0 | contract OUSD is Governable { |
| 15 | using SafeCast for int256; |
|
| 16 | using SafeCast for uint256; |
|
| 17 | ||
| 18 | /// @dev Event triggered when the supply changes |
|
| 19 | /// @param totalSupply Updated token total supply |
|
| 20 | /// @param rebasingCredits Updated token rebasing credits |
|
| 21 | /// @param rebasingCreditsPerToken Updated token rebasing credits per token |
|
| 22 | event TotalSupplyUpdatedHighres( |
|
| 23 | uint256 totalSupply, |
|
| 24 | uint256 rebasingCredits, |
|
| 25 | uint256 rebasingCreditsPerToken |
|
| 26 | ); |
|
| 27 | /// @dev Event triggered when an account opts in for rebasing |
|
| 28 | /// @param account Address of the account |
|
| 29 | event AccountRebasingEnabled(address account); |
|
| 30 | /// @dev Event triggered when an account opts out of rebasing |
|
| 31 | /// @param account Address of the account |
|
| 32 | event AccountRebasingDisabled(address account); |
|
| 33 | /// @dev Emitted when `value` tokens are moved from one account `from` to |
|
| 34 | /// another `to`. |
|
| 35 | /// @param from Address of the account tokens are moved from |
|
| 36 | /// @param to Address of the account tokens are moved to |
|
| 37 | /// @param value Amount of tokens transferred |
|
| 38 | event Transfer(address indexed from, address indexed to, uint256 value); |
|
| 39 | /// @dev Emitted when the allowance of a `spender` for an `owner` is set by |
|
| 40 | /// a call to {approve}. `value` is the new allowance. |
|
| 41 | /// @param owner Address of the owner approving allowance |
|
| 42 | /// @param spender Address of the spender allowance is granted to |
|
| 43 | /// @param value Amount of tokens spender can transfer |
|
| 44 | event Approval( |
|
| 45 | address indexed owner, |
|
| 46 | address indexed spender, |
|
| 47 | uint256 value |
|
| 48 | ); |
|
| 49 | /// @dev Yield resulting from {changeSupply} that a `source` account would |
|
| 50 | /// receive is directed to `target` account. |
|
| 51 | /// @param source Address of the source forwarding the yield |
|
| 52 | /// @param target Address of the target receiving the yield |
|
| 53 | event YieldDelegated(address source, address target); |
|
| 54 | /// @dev Yield delegation from `source` account to the `target` account is |
|
| 55 | /// suspended. |
|
| 56 | /// @param source Address of the source suspending yield forwarding |
|
| 57 | /// @param target Address of the target no longer receiving yield from `source` |
|
| 58 | /// account |
|
| 59 | event YieldUndelegated(address source, address target); |
|
| 60 | ||
| 61 | enum RebaseOptions { |
|
| 62 | NotSet, |
|
| 63 | StdNonRebasing, |
|
| 64 | StdRebasing, |
|
| 65 | YieldDelegationSource, |
|
| 66 | YieldDelegationTarget |
|
| 67 | } |
|
| 68 | ||
| 69 | uint256[154] private _gap; // Slots to align with deployed contract |
|
| 70 | uint256 private constant MAX_SUPPLY = type(uint128).max; |
|
| 71 | /// @dev The amount of tokens in existence |
|
| 72 | 12× | uint256 public totalSupply; |
| 73 | mapping(address => mapping(address => uint256)) private allowances; |
|
| 74 | /// @dev The vault with privileges to execute {mint}, {burn} |
|
| 75 | /// and {changeSupply} |
|
| 76 | 0 | address public vaultAddress; |
| 77 | mapping(address => uint256) internal creditBalances; |
|
| 78 | // the 2 storage variables below need trailing underscores to not name collide with public functions |
|
| 79 | uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts) |
|
| 80 | uint256 private rebasingCreditsPerToken_; |
|
| 81 | /// @dev The amount of tokens that are not rebasing - receiving yield |
|
| 82 | 6× | uint256 public nonRebasingSupply; |
| 83 | mapping(address => uint256) internal alternativeCreditsPerToken; |
|
| 84 | /// @dev A map of all addresses and their respective RebaseOptions |
|
| 85 | 0 | mapping(address => RebaseOptions) public rebaseState; |
| 86 | mapping(address => uint256) private __deprecated_isUpgraded; |
|
| 87 | /// @dev A map of addresses that have yields forwarded to. This is an |
|
| 88 | /// inverse mapping of {yieldFrom} |
|
| 89 | /// Key Account forwarding yield |
|
| 90 | /// Value Account receiving yield |
|
| 91 | 0 | mapping(address => address) public yieldTo; |
| 92 | /// @dev A map of addresses that are receiving the yield. This is an |
|
| 93 | /// inverse mapping of {yieldTo} |
|
| 94 | /// Key Account receiving yield |
|
| 95 | /// Value Account forwarding yield |
|
| 96 | 0 | mapping(address => address) public yieldFrom; |
| 97 | ||
| 98 | 0 | uint256 private constant RESOLUTION_INCREASE = 1e9; |
| 99 | uint256[34] private __gap; // including below gap totals up to 200 |
|
| 100 | ||
| 101 | /// @dev Verifies that the caller is the Governor or Strategist. |
|
| 102 | modifier onlyGovernorOrStrategist() { |
|
| 103 | 8× | require( |
| 104 | 0 | isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(), |
| 105 | "Caller is not the Strategist or Governor" |
|
| 106 | ); |
|
| 107 | _; |
|
| 108 | } |
|
| 109 | ||
| 110 | /// @dev Initializes the contract and sets necessary variables. |
|
| 111 | /// @param _vaultAddress Address of the vault contract |
|
| 112 | /// @param _initialCreditsPerToken The starting rebasing credits per token. |
|
| 113 | 0 | function initialize(address _vaultAddress, uint256 _initialCreditsPerToken) |
| 114 | external |
|
| 115 | onlyGovernor |
|
| 116 | { |
|
| 117 | 0 | require(_vaultAddress != address(0), "Zero vault address"); |
| 118 | 0 | require(vaultAddress == address(0), "Already initialized"); |
| 119 | ||
| 120 | 0 | rebasingCreditsPerToken_ = _initialCreditsPerToken; |
| 121 | 0 | vaultAddress = _vaultAddress; |
| 122 | } |
|
| 123 | ||
| 124 | /// @dev Returns the symbol of the token, a shorter version |
|
| 125 | /// of the name. |
|
| 126 | 0 | function symbol() external pure virtual returns (string memory) { |
| 127 | 0 | return "OUSD"; |
| 128 | } |
|
| 129 | ||
| 130 | /// @dev Returns the name of the token. |
|
| 131 | 16× | function name() external pure virtual returns (string memory) { |
| 132 | 0 | return "Origin Dollar"; |
| 133 | } |
|
| 134 | ||
| 135 | /// @dev Returns the number of decimals used to get its user representation. |
|
| 136 | 0 | function decimals() external pure virtual returns (uint8) { |
| 137 | 0 | return 18; |
| 138 | } |
|
| 139 | ||
| 140 | /** |
|
| 141 | * @dev Verifies that the caller is the Vault contract |
|
| 142 | */ |
|
| 143 | modifier onlyVault() { |
|
| 144 | 24× | require(vaultAddress == msg.sender, "Caller is not the Vault"); |
| 145 | 4× | _; |
| 146 | } |
|
| 147 | ||
| 148 | /** |
|
| 149 | * @return High resolution rebasingCreditsPerToken |
|
| 150 | */ |
|
| 151 | 3× | function rebasingCreditsPerTokenHighres() external view returns (uint256) { |
| 152 | 2× | return rebasingCreditsPerToken_; |
| 153 | } |
|
| 154 | ||
| 155 | /** |
|
| 156 | * @return Low resolution rebasingCreditsPerToken |
|
| 157 | */ |
|
| 158 | 0 | function rebasingCreditsPerToken() external view returns (uint256) { |
| 159 | 0 | return rebasingCreditsPerToken_ / RESOLUTION_INCREASE; |
| 160 | } |
|
| 161 | ||
| 162 | /** |
|
| 163 | * @return High resolution total number of rebasing credits |
|
| 164 | */ |
|
| 165 | 0 | function rebasingCreditsHighres() external view returns (uint256) { |
| 166 | 0 | return rebasingCredits_; |
| 167 | } |
|
| 168 | ||
| 169 | /** |
|
| 170 | * @return Low resolution total number of rebasing credits |
|
| 171 | */ |
|
| 172 | 8× | function rebasingCredits() external view returns (uint256) { |
| 173 | 0 | return rebasingCredits_ / RESOLUTION_INCREASE; |
| 174 | } |
|
| 175 | ||
| 176 | /** |
|
| 177 | * @notice Gets the balance of the specified address. |
|
| 178 | * @param _account Address to query the balance of. |
|
| 179 | * @return A uint256 representing the amount of base units owned by the |
|
| 180 | * specified address. |
|
| 181 | */ |
|
| 182 | 28× | function balanceOf(address _account) public view returns (uint256) { |
| 183 | 28× | RebaseOptions state = rebaseState[_account]; |
| 184 | 26× | if (state == RebaseOptions.YieldDelegationSource) { |
| 185 | // Saves a slot read when transferring to or from a yield delegating source |
|
| 186 | // since we know creditBalances equals the balance. |
|
| 187 | 0 | return creditBalances[_account]; |
| 188 | } |
|
| 189 | 54× | uint256 baseBalance = (creditBalances[_account] * 1e18) / |
| 190 | 10× | _creditsPerToken(_account); |
| 191 | 26× | if (state == RebaseOptions.YieldDelegationTarget) { |
| 192 | // creditBalances of yieldFrom accounts equals token balances |
|
| 193 | 0 | return baseBalance - creditBalances[yieldFrom[_account]]; |
| 194 | } |
|
| 195 | 2× | return baseBalance; |
| 196 | } |
|
| 197 | ||
| 198 | /** |
|
| 199 | * @notice Gets the credits balance of the specified address. |
|
| 200 | * @dev Backwards compatible with old low res credits per token. |
|
| 201 | * @param _account The address to query the balance of. |
|
| 202 | * @return (uint256, uint256) Credit balance and credits per token of the |
|
| 203 | * address |
|
| 204 | */ |
|
| 205 | 0 | function creditsBalanceOf(address _account) |
| 206 | external |
|
| 207 | view |
|
| 208 | 0 | returns (uint256, uint256) |
| 209 | { |
|
| 210 | 0 | uint256 cpt = _creditsPerToken(_account); |
| 211 | 0 | if (cpt == 1e27) { |
| 212 | // For a period before the resolution upgrade, we created all new |
|
| 213 | // contract accounts at high resolution. Since they are not changing |
|
| 214 | // as a result of this upgrade, we will return their true values |
|
| 215 | 0 | return (creditBalances[_account], cpt); |
| 216 | } else { |
|
| 217 | 0 | return ( |
| 218 | 0 | creditBalances[_account] / RESOLUTION_INCREASE, |
| 219 | 0 | cpt / RESOLUTION_INCREASE |
| 220 | ); |
|
| 221 | } |
|
| 222 | } |
|
| 223 | ||
| 224 | /** |
|
| 225 | * @notice Gets the credits balance of the specified address. |
|
| 226 | * @param _account The address to query the balance of. |
|
| 227 | * @return (uint256, uint256, bool) Credit balance, credits per token of the |
|
| 228 | * address, and isUpgraded |
|
| 229 | */ |
|
| 230 | 18× | function creditsBalanceOfHighres(address _account) |
| 231 | external |
|
| 232 | view |
|
| 233 | returns ( |
|
| 234 | 5× | uint256, |
| 235 | uint256, |
|
| 236 | bool |
|
| 237 | ) |
|
| 238 | { |
|
| 239 | 4× | return ( |
| 240 | 13× | creditBalances[_account], |
| 241 | 4× | _creditsPerToken(_account), |
| 242 | 2× | true // all accounts have their resolution "upgraded" |
| 243 | ); |
|
| 244 | } |
|
| 245 | ||
| 246 | // Backwards compatible view |
|
| 247 | 0 | function nonRebasingCreditsPerToken(address _account) |
| 248 | external |
|
| 249 | view |
|
| 250 | 0 | returns (uint256) |
| 251 | { |
|
| 252 | 0 | return alternativeCreditsPerToken[_account]; |
| 253 | } |
|
| 254 | ||
| 255 | /** |
|
| 256 | * @notice Transfer tokens to a specified address. |
|
| 257 | * @param _to the address to transfer to. |
|
| 258 | * @param _value the amount to be transferred. |
|
| 259 | * @return true on success. |
|
| 260 | */ |
|
| 261 | 12× | function transfer(address _to, uint256 _value) external returns (bool) { |
| 262 | 13× | require(_to != address(0), "Transfer to zero address"); |
| 263 | ||
| 264 | 7× | _executeTransfer(msg.sender, _to, _value); |
| 265 | ||
| 266 | 9× | emit Transfer(msg.sender, _to, _value); |
| 267 | return true; |
|
| 268 | } |
|
| 269 | ||
| 270 | /** |
|
| 271 | * @notice Transfer tokens from one address to another. |
|
| 272 | * @param _from The address you want to send tokens from. |
|
| 273 | * @param _to The address you want to transfer to. |
|
| 274 | * @param _value The amount of tokens to be transferred. |
|
| 275 | * @return true on success. |
|
| 276 | */ |
|
| 277 | 13× | function transferFrom( |
| 278 | address _from, |
|
| 279 | address _to, |
|
| 280 | uint256 _value |
|
| 281 | 1× | ) external returns (bool) { |
| 282 | 5× | require(_to != address(0), "Transfer to zero address"); |
| 283 | 24× | uint256 userAllowance = allowances[_from][msg.sender]; |
| 284 | 15× | require(_value <= userAllowance, "Allowance exceeded"); |
| 285 | ||
| 286 | unchecked { |
|
| 287 | 30× | allowances[_from][msg.sender] = userAllowance - _value; |
| 288 | } |
|
| 289 | ||
| 290 | 5× | _executeTransfer(_from, _to, _value); |
| 291 | ||
| 292 | 17× | emit Transfer(_from, _to, _value); |
| 293 | 2× | return true; |
| 294 | } |
|
| 295 | ||
| 296 | 5× | function _executeTransfer( |
| 297 | address _from, |
|
| 298 | address _to, |
|
| 299 | uint256 _value |
|
| 300 | 4× | ) internal { |
| 301 | 4× | ( |
| 302 | 1× | int256 fromRebasingCreditsDiff, |
| 303 | 1× | int256 fromNonRebasingSupplyDiff |
| 304 | 7× | ) = _adjustAccount(_from, -_value.toInt256()); |
| 305 | 4× | ( |
| 306 | 1× | int256 toRebasingCreditsDiff, |
| 307 | 1× | int256 toNonRebasingSupplyDiff |
| 308 | 7× | ) = _adjustAccount(_to, _value.toInt256()); |
| 309 | ||
| 310 | 2× | _adjustGlobals( |
| 311 | 5× | fromRebasingCreditsDiff + toRebasingCreditsDiff, |
| 312 | 5× | fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff |
| 313 | ); |
|
| 314 | } |
|
| 315 | ||
| 316 | 7× | function _adjustAccount(address _account, int256 _balanceChange) |
| 317 | internal |
|
| 318 | 4× | returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff) |
| 319 | 3× | { |
| 320 | 15× | RebaseOptions state = rebaseState[_account]; |
| 321 | 10× | int256 currentBalance = balanceOf(_account).toInt256(); |
| 322 | 11× | if (currentBalance + _balanceChange < 0) { |
| 323 | 8× | revert("Transfer amount exceeds balance"); |
| 324 | } |
|
| 325 | 9× | uint256 newBalance = (currentBalance + _balanceChange).toUint256(); |
| 326 | ||
| 327 | 13× | if (state == RebaseOptions.YieldDelegationSource) { |
| 328 | 0 | address target = yieldTo[_account]; |
| 329 | 0 | uint256 targetOldBalance = balanceOf(target); |
| 330 | 0 | uint256 targetNewCredits = _balanceToRebasingCredits( |
| 331 | 0 | targetOldBalance + newBalance |
| 332 | ); |
|
| 333 | 0 | rebasingCreditsDiff = |
| 334 | 0 | targetNewCredits.toInt256() - |
| 335 | 0 | creditBalances[target].toInt256(); |
| 336 | ||
| 337 | 0 | creditBalances[_account] = newBalance; |
| 338 | 0 | creditBalances[target] = targetNewCredits; |
| 339 | 14× | } else if (state == RebaseOptions.YieldDelegationTarget) { |
| 340 | 0 | uint256 newCredits = _balanceToRebasingCredits( |
| 341 | 0 | newBalance + creditBalances[yieldFrom[_account]] |
| 342 | ); |
|
| 343 | 0 | rebasingCreditsDiff = |
| 344 | 0 | newCredits.toInt256() - |
| 345 | 0 | creditBalances[_account].toInt256(); |
| 346 | 0 | creditBalances[_account] = newCredits; |
| 347 | 1× | } else { |
| 348 | 5× | _autoMigrate(_account); |
| 349 | 13× | uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[ |
| 350 | _account |
|
| 351 | ]; |
|
| 352 | 8× | if (alternativeCreditsPerTokenMem > 0) { |
| 353 | 3× | nonRebasingSupplyDiff = _balanceChange; |
| 354 | 6× | if (alternativeCreditsPerTokenMem != 1e18) { |
| 355 | 0 | alternativeCreditsPerToken[_account] = 1e18; |
| 356 | } |
|
| 357 | 15× | creditBalances[_account] = newBalance; |
| 358 | } else { |
|
| 359 | 8× | uint256 newCredits = _balanceToRebasingCredits(newBalance); |
| 360 | 1× | rebasingCreditsDiff = |
| 361 | 11× | newCredits.toInt256() - |
| 362 | 18× | creditBalances[_account].toInt256(); |
| 363 | 16× | creditBalances[_account] = newCredits; |
| 364 | } |
|
| 365 | } |
|
| 366 | } |
|
| 367 | ||
| 368 | 5× | function _adjustGlobals( |
| 369 | int256 _rebasingCreditsDiff, |
|
| 370 | int256 _nonRebasingSupplyDiff |
|
| 371 | ) internal { |
|
| 372 | 10× | if (_rebasingCreditsDiff != 0) { |
| 373 | 18× | rebasingCredits_ = (rebasingCredits_.toInt256() + |
| 374 | 1× | _rebasingCreditsDiff).toUint256(); |
| 375 | } |
|
| 376 | 8× | if (_nonRebasingSupplyDiff != 0) { |
| 377 | 9× | nonRebasingSupply = (nonRebasingSupply.toInt256() + |
| 378 | 1× | _nonRebasingSupplyDiff).toUint256(); |
| 379 | } |
|
| 380 | } |
|
| 381 | ||
| 382 | /** |
|
| 383 | * @notice Function to check the amount of tokens that _owner has allowed |
|
| 384 | * to `_spender`. |
|
| 385 | * @param _owner The address which owns the funds. |
|
| 386 | * @param _spender The address which will spend the funds. |
|
| 387 | * @return The number of tokens still available for the _spender. |
|
| 388 | */ |
|
| 389 | 18× | function allowance(address _owner, address _spender) |
| 390 | external |
|
| 391 | view |
|
| 392 | 2× | returns (uint256) |
| 393 | { |
|
| 394 | 56× | return allowances[_owner][_spender]; |
| 395 | } |
|
| 396 | ||
| 397 | /** |
|
| 398 | * @notice Approve the passed address to spend the specified amount of |
|
| 399 | * tokens on behalf of msg.sender. |
|
| 400 | * @param _spender The address which will spend the funds. |
|
| 401 | * @param _value The amount of tokens to be spent. |
|
| 402 | * @return true on success. |
|
| 403 | */ |
|
| 404 | 34× | function approve(address _spender, uint256 _value) external returns (bool) { |
| 405 | 29× | allowances[msg.sender][_spender] = _value; |
| 406 | 13× | emit Approval(msg.sender, _spender, _value); |
| 407 | 1× | return true; |
| 408 | } |
|
| 409 | ||
| 410 | /** |
|
| 411 | * @notice Creates `_amount` tokens and assigns them to `_account`, |
|
| 412 | * increasing the total supply. |
|
| 413 | */ |
|
| 414 | 16× | function mint(address _account, uint256 _amount) external onlyVault { |
| 415 | 5× | require(_account != address(0), "Mint to the zero address"); |
| 416 | ||
| 417 | // Account |
|
| 418 | 4× | ( |
| 419 | 1× | int256 toRebasingCreditsDiff, |
| 420 | 1× | int256 toNonRebasingSupplyDiff |
| 421 | 10× | ) = _adjustAccount(_account, _amount.toInt256()); |
| 422 | // Globals |
|
| 423 | 6× | _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff); |
| 424 | 13× | totalSupply = totalSupply + _amount; |
| 425 | ||
| 426 | 11× | require(totalSupply < MAX_SUPPLY, "Max supply"); |
| 427 | 16× | emit Transfer(address(0), _account, _amount); |
| 428 | } |
|
| 429 | ||
| 430 | /** |
|
| 431 | * @notice Destroys `_amount` tokens from `_account`, |
|
| 432 | * reducing the total supply. |
|
| 433 | */ |
|
| 434 | 17× | function burn(address _account, uint256 _amount) external onlyVault { |
| 435 | 5× | require(_account != address(0), "Burn from the zero address"); |
| 436 | 3× | if (_amount == 0) { |
| 437 | 1× | return; |
| 438 | } |
|
| 439 | ||
| 440 | // Account |
|
| 441 | 4× | ( |
| 442 | 1× | int256 toRebasingCreditsDiff, |
| 443 | 1× | int256 toNonRebasingSupplyDiff |
| 444 | 12× | ) = _adjustAccount(_account, -_amount.toInt256()); |
| 445 | // Globals |
|
| 446 | 6× | _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff); |
| 447 | 11× | totalSupply = totalSupply - _amount; |
| 448 | ||
| 449 | 9× | emit Transfer(_account, address(0), _amount); |
| 450 | } |
|
| 451 | ||
| 452 | /** |
|
| 453 | * @dev Get the credits per token for an account. Returns a fixed amount |
|
| 454 | * if the account is non-rebasing. |
|
| 455 | * @param _account Address of the account. |
|
| 456 | */ |
|
| 457 | 10× | function _creditsPerToken(address _account) |
| 458 | internal |
|
| 459 | view |
|
| 460 | 2× | returns (uint256) |
| 461 | { |
|
| 462 | 24× | uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[ |
| 463 | _account |
|
| 464 | ]; |
|
| 465 | 10× | if (alternativeCreditsPerTokenMem != 0) { |
| 466 | 2× | return alternativeCreditsPerTokenMem; |
| 467 | } else { |
|
| 468 | 6× | return rebasingCreditsPerToken_; |
| 469 | } |
|
| 470 | } |
|
| 471 | ||
| 472 | /** |
|
| 473 | * @dev Auto migrate contracts to be non rebasing, |
|
| 474 | * unless they have opted into yield. |
|
| 475 | * @param _account Address of the account. |
|
| 476 | */ |
|
| 477 | 10× | function _autoMigrate(address _account) internal { |
| 478 | 8× | uint256 codeLen = _account.code.length; |
| 479 | 16× | bool isEOA = (codeLen == 0) || |
| 480 | 16× | (codeLen == 23 && bytes3(_account.code) == 0xef0100); |
| 481 | // In previous code versions, contracts would not have had their |
|
| 482 | // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated |
|
| 483 | // therefore we check the actual accounting used on the account as well. |
|
| 484 | 6× | if ( |
| 485 | 24× | (!isEOA) && |
| 486 | 46× | rebaseState[_account] == RebaseOptions.NotSet && |
| 487 | 28× | alternativeCreditsPerToken[_account] == 0 |
| 488 | ) { |
|
| 489 | 10× | _rebaseOptOut(_account); |
| 490 | } |
|
| 491 | } |
|
| 492 | ||
| 493 | /** |
|
| 494 | * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and |
|
| 495 | * also balance that corresponds to those credits. The latter is important |
|
| 496 | * when adjusting the contract's global nonRebasingSupply to circumvent any |
|
| 497 | * possible rounding errors. |
|
| 498 | * |
|
| 499 | * @param _balance Balance of the account. |
|
| 500 | */ |
|
| 501 | 1× | function _balanceToRebasingCredits(uint256 _balance) |
| 502 | internal |
|
| 503 | view |
|
| 504 | 1× | returns (uint256 rebasingCredits) |
| 505 | { |
|
| 506 | // Rounds up, because we need to ensure that accounts always have |
|
| 507 | // at least the balance that they should have. |
|
| 508 | // Note this should always be used on an absolute account value, |
|
| 509 | // not on a possibly negative diff, because then the rounding would be wrong. |
|
| 510 | 28× | return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18; |
| 511 | } |
|
| 512 | ||
| 513 | /** |
|
| 514 | * @notice The calling account will start receiving yield after a successful call. |
|
| 515 | * @param _account Address of the account. |
|
| 516 | */ |
|
| 517 | 0 | function governanceRebaseOptIn(address _account) external onlyGovernor { |
| 518 | 0 | require(_account != address(0), "Zero address not allowed"); |
| 519 | 0 | _rebaseOptIn(_account); |
| 520 | } |
|
| 521 | ||
| 522 | /** |
|
| 523 | * @notice The calling account will start receiving yield after a successful call. |
|
| 524 | */ |
|
| 525 | 5× | function rebaseOptIn() external { |
| 526 | 4× | _rebaseOptIn(msg.sender); |
| 527 | } |
|
| 528 | ||
| 529 | 1× | function _rebaseOptIn(address _account) internal { |
| 530 | 8× | uint256 balance = balanceOf(_account); |
| 531 | ||
| 532 | // prettier-ignore |
|
| 533 | 11× | require( |
| 534 | 19× | alternativeCreditsPerToken[_account] > 0 || |
| 535 | // Accounts may explicitly `rebaseOptIn` regardless of |
|
| 536 | // accounting if they have a 0 balance. |
|
| 537 | 14× | creditBalances[_account] == 0 |
| 538 | , |
|
| 539 | "Account must be non-rebasing" |
|
| 540 | ); |
|
| 541 | 16× | RebaseOptions state = rebaseState[_account]; |
| 542 | // prettier-ignore |
|
| 543 | 11× | require( |
| 544 | 13× | state == RebaseOptions.StdNonRebasing || |
| 545 | 10× | state == RebaseOptions.NotSet, |
| 546 | "Only standard non-rebasing accounts can opt in" |
|
| 547 | ); |
|
| 548 | ||
| 549 | 8× | uint256 newCredits = _balanceToRebasingCredits(balance); |
| 550 | ||
| 551 | // Account |
|
| 552 | 22× | rebaseState[_account] = RebaseOptions.StdRebasing; |
| 553 | 9× | alternativeCreditsPerToken[_account] = 0; |
| 554 | 9× | creditBalances[_account] = newCredits; |
| 555 | // Globals |
|
| 556 | 10× | _adjustGlobals(newCredits.toInt256(), -balance.toInt256()); |
| 557 | ||
| 558 | 5× | emit AccountRebasingEnabled(_account); |
| 559 | } |
|
| 560 | ||
| 561 | /** |
|
| 562 | * @notice The calling account will no longer receive yield |
|
| 563 | */ |
|
| 564 | 5× | function rebaseOptOut() external { |
| 565 | 4× | _rebaseOptOut(msg.sender); |
| 566 | } |
|
| 567 | ||
| 568 | 12× | function _rebaseOptOut(address _account) internal { |
| 569 | 14× | require( |
| 570 | 28× | alternativeCreditsPerToken[_account] == 0, |
| 571 | "Account must be rebasing" |
|
| 572 | ); |
|
| 573 | 30× | RebaseOptions state = rebaseState[_account]; |
| 574 | 6× | require( |
| 575 | 48× | state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet, |
| 576 | "Only standard rebasing accounts can opt out" |
|
| 577 | ); |
|
| 578 | ||
| 579 | 30× | uint256 oldCredits = creditBalances[_account]; |
| 580 | 10× | uint256 balance = balanceOf(_account); |
| 581 | ||
| 582 | // Account |
|
| 583 | 44× | rebaseState[_account] = RebaseOptions.StdNonRebasing; |
| 584 | 18× | alternativeCreditsPerToken[_account] = 1e18; |
| 585 | 18× | creditBalances[_account] = balance; |
| 586 | // Globals |
|
| 587 | 32× | _adjustGlobals(-oldCredits.toInt256(), balance.toInt256()); |
| 588 | ||
| 589 | 24× | emit AccountRebasingDisabled(_account); |
| 590 | } |
|
| 591 | ||
| 592 | /** |
|
| 593 | * @notice Distribute yield to users. This changes the exchange rate |
|
| 594 | * between "credits" and OUSD tokens to change rebasing user's balances. |
|
| 595 | * @param _newTotalSupply New total supply of OUSD. |
|
| 596 | */ |
|
| 597 | 18× | function changeSupply(uint256 _newTotalSupply) external onlyVault { |
| 598 | 7× | require(totalSupply > 0, "Cannot increase 0 supply"); |
| 599 | ||
| 600 | 7× | if (totalSupply == _newTotalSupply) { |
| 601 | 12× | emit TotalSupplyUpdatedHighres( |
| 602 | 2× | totalSupply, |
| 603 | 2× | rebasingCredits_, |
| 604 | 2× | rebasingCreditsPerToken_ |
| 605 | ); |
|
| 606 | return; |
|
| 607 | } |
|
| 608 | ||
| 609 | 12× | totalSupply = _newTotalSupply > MAX_SUPPLY |
| 610 | ? MAX_SUPPLY |
|
| 611 | 1× | : _newTotalSupply; |
| 612 | ||
| 613 | 12× | uint256 rebasingSupply = totalSupply - nonRebasingSupply; |
| 614 | // round up in the favour of the protocol |
|
| 615 | 4× | rebasingCreditsPerToken_ = |
| 616 | 29× | (rebasingCredits_ * 1e18 + rebasingSupply - 1) / |
| 617 | 1× | rebasingSupply; |
| 618 | ||
| 619 | 3× | require(rebasingCreditsPerToken_ > 0, "Invalid change in supply"); |
| 620 | ||
| 621 | 12× | emit TotalSupplyUpdatedHighres( |
| 622 | 2× | totalSupply, |
| 623 | 2× | rebasingCredits_, |
| 624 | 2× | rebasingCreditsPerToken_ |
| 625 | ); |
|
| 626 | } |
|
| 627 | ||
| 628 | /* |
|
| 629 | * @notice Send the yield from one account to another account. |
|
| 630 | * Each account keeps its own balances. |
|
| 631 | */ |
|
| 632 | 0 | function delegateYield(address _from, address _to) |
| 633 | external |
|
| 634 | onlyGovernorOrStrategist |
|
| 635 | 0 | { |
| 636 | 0 | require(_from != address(0), "Zero from address not allowed"); |
| 637 | 0 | require(_to != address(0), "Zero to address not allowed"); |
| 638 | ||
| 639 | 0 | require(_from != _to, "Cannot delegate to self"); |
| 640 | 0 | require( |
| 641 | 0 | yieldFrom[_to] == address(0) && |
| 642 | 0 | yieldTo[_to] == address(0) && |
| 643 | 0 | yieldFrom[_from] == address(0) && |
| 644 | 0 | yieldTo[_from] == address(0), |
| 645 | "Blocked by existing yield delegation" |
|
| 646 | ); |
|
| 647 | 0 | RebaseOptions stateFrom = rebaseState[_from]; |
| 648 | 0 | RebaseOptions stateTo = rebaseState[_to]; |
| 649 | ||
| 650 | 0 | require( |
| 651 | 0 | stateFrom == RebaseOptions.NotSet || |
| 652 | 0 | stateFrom == RebaseOptions.StdNonRebasing || |
| 653 | 0 | stateFrom == RebaseOptions.StdRebasing, |
| 654 | "Invalid rebaseState from" |
|
| 655 | ); |
|
| 656 | ||
| 657 | 0 | require( |
| 658 | 0 | stateTo == RebaseOptions.NotSet || |
| 659 | 0 | stateTo == RebaseOptions.StdNonRebasing || |
| 660 | 0 | stateTo == RebaseOptions.StdRebasing, |
| 661 | "Invalid rebaseState to" |
|
| 662 | ); |
|
| 663 | ||
| 664 | 0 | if (alternativeCreditsPerToken[_from] == 0) { |
| 665 | 0 | _rebaseOptOut(_from); |
| 666 | } |
|
| 667 | 0 | if (alternativeCreditsPerToken[_to] > 0) { |
| 668 | 0 | _rebaseOptIn(_to); |
| 669 | } |
|
| 670 | ||
| 671 | 0 | uint256 fromBalance = balanceOf(_from); |
| 672 | 0 | uint256 toBalance = balanceOf(_to); |
| 673 | 0 | uint256 oldToCredits = creditBalances[_to]; |
| 674 | 0 | uint256 newToCredits = _balanceToRebasingCredits( |
| 675 | 0 | fromBalance + toBalance |
| 676 | ); |
|
| 677 | ||
| 678 | // Set up the bidirectional links |
|
| 679 | 0 | yieldTo[_from] = _to; |
| 680 | 0 | yieldFrom[_to] = _from; |
| 681 | ||
| 682 | // Local |
|
| 683 | 0 | rebaseState[_from] = RebaseOptions.YieldDelegationSource; |
| 684 | 0 | alternativeCreditsPerToken[_from] = 1e18; |
| 685 | 0 | creditBalances[_from] = fromBalance; |
| 686 | 0 | rebaseState[_to] = RebaseOptions.YieldDelegationTarget; |
| 687 | 0 | creditBalances[_to] = newToCredits; |
| 688 | ||
| 689 | // Global |
|
| 690 | 0 | int256 creditsChange = newToCredits.toInt256() - |
| 691 | 0 | oldToCredits.toInt256(); |
| 692 | 5× | _adjustGlobals(creditsChange, -(fromBalance).toInt256()); |
| 693 | 0 | emit YieldDelegated(_from, _to); |
| 694 | } |
|
| 695 | ||
| 696 | /* |
|
| 697 | * @notice Stop sending the yield from one account to another account. |
|
| 698 | */ |
|
| 699 | 2× | function undelegateYield(address _from) external onlyGovernorOrStrategist { |
| 700 | // Require a delegation, which will also ensure a valid delegation |
|
| 701 | 0 | require(yieldTo[_from] != address(0), "Zero address not allowed"); |
| 702 | ||
| 703 | 0 | address to = yieldTo[_from]; |
| 704 | 0 | uint256 fromBalance = balanceOf(_from); |
| 705 | 0 | uint256 toBalance = balanceOf(to); |
| 706 | 0 | uint256 oldToCredits = creditBalances[to]; |
| 707 | 0 | uint256 newToCredits = _balanceToRebasingCredits(toBalance); |
| 708 | ||
| 709 | // Remove the bidirectional links |
|
| 710 | 0 | yieldFrom[to] = address(0); |
| 711 | 0 | yieldTo[_from] = address(0); |
| 712 | ||
| 713 | // Local |
|
| 714 | 0 | rebaseState[_from] = RebaseOptions.StdNonRebasing; |
| 715 | // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()` |
|
| 716 | 0 | creditBalances[_from] = fromBalance; |
| 717 | 0 | rebaseState[to] = RebaseOptions.StdRebasing; |
| 718 | // alternativeCreditsPerToken[to] already 0 from `delegateYield()` |
|
| 719 | 0 | creditBalances[to] = newToCredits; |
| 720 | ||
| 721 | // Global |
|
| 722 | 0 | int256 creditsChange = newToCredits.toInt256() - |
| 723 | 0 | oldToCredits.toInt256(); |
| 724 | 6× | _adjustGlobals(creditsChange, fromBalance.toInt256()); |
| 725 | 0 | emit YieldUndelegated(_from, to); |
| 726 | } |
|
| 727 | } |
83%
contracts/contracts/utils/StableMath.sol
Lines covered: 5 / 6 (83%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; |
|
| 5 | ||
| 6 | // Based on StableMath from Stability Labs Pty. Ltd. |
|
| 7 | // https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol |
|
| 8 | ||
| 9 | 0 | library StableMath { |
| 10 | using SafeMath for uint256; |
|
| 11 | ||
| 12 | /** |
|
| 13 | * @dev Scaling unit for use in specific calculations, |
|
| 14 | * where 1 * 10**18, or 1e18 represents a unit '1' |
|
| 15 | */ |
|
| 16 | 1× | uint256 private constant FULL_SCALE = 1e18; |
| 17 | ||
| 18 | /*************************************** |
|
| 19 | Helpers |
|
| 20 | ****************************************/ |
|
| 21 | ||
| 22 | /** |
|
| 23 | * @dev Adjust the scale of an integer |
|
| 24 | * @param to Decimals to scale to |
|
| 25 | * @param from Decimals to scale from |
|
| 26 | */ |
|
| 27 | function scaleBy( |
|
| 28 | uint256 x, |
|
| 29 | uint256 to, |
|
| 30 | uint256 from |
|
| 31 | ) internal pure returns (uint256) { |
|
| 32 | if (to > from) { |
|
| 33 | x = x.mul(10**(to - from)); |
|
| 34 | } else if (to < from) { |
|
| 35 | // slither-disable-next-line divide-before-multiply |
|
| 36 | x = x.div(10**(from - to)); |
|
| 37 | } |
|
| 38 | return x; |
|
| 39 | } |
|
| 40 | ||
| 41 | /*************************************** |
|
| 42 | Precise Arithmetic |
|
| 43 | ****************************************/ |
|
| 44 | ||
| 45 | /** |
|
| 46 | * @dev Multiplies two precise units, and then truncates by the full scale |
|
| 47 | * @param x Left hand input to multiplication |
|
| 48 | * @param y Right hand input to multiplication |
|
| 49 | * @return Result after multiplying the two inputs and then dividing by the shared |
|
| 50 | * scale unit |
|
| 51 | */ |
|
| 52 | function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) { |
|
| 53 | return mulTruncateScale(x, y, FULL_SCALE); |
|
| 54 | } |
|
| 55 | ||
| 56 | /** |
|
| 57 | * @dev Multiplies two precise units, and then truncates by the given scale. For example, |
|
| 58 | * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18 |
|
| 59 | * @param x Left hand input to multiplication |
|
| 60 | * @param y Right hand input to multiplication |
|
| 61 | * @param scale Scale unit |
|
| 62 | * @return Result after multiplying the two inputs and then dividing by the shared |
|
| 63 | * scale unit |
|
| 64 | */ |
|
| 65 | function mulTruncateScale( |
|
| 66 | uint256 x, |
|
| 67 | uint256 y, |
|
| 68 | uint256 scale |
|
| 69 | ) internal pure returns (uint256) { |
|
| 70 | // e.g. assume scale = fullScale |
|
| 71 | // z = 10e18 * 9e17 = 9e36 |
|
| 72 | uint256 z = x.mul(y); |
|
| 73 | // return 9e36 / 1e18 = 9e18 |
|
| 74 | return z.div(scale); |
|
| 75 | } |
|
| 76 | ||
| 77 | /** |
|
| 78 | * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result |
|
| 79 | * @param x Left hand input to multiplication |
|
| 80 | * @param y Right hand input to multiplication |
|
| 81 | * @return Result after multiplying the two inputs and then dividing by the shared |
|
| 82 | * scale unit, rounded up to the closest base unit. |
|
| 83 | */ |
|
| 84 | function mulTruncateCeil(uint256 x, uint256 y) |
|
| 85 | internal |
|
| 86 | pure |
|
| 87 | returns (uint256) |
|
| 88 | { |
|
| 89 | // e.g. 8e17 * 17268172638 = 138145381104e17 |
|
| 90 | uint256 scaled = x.mul(y); |
|
| 91 | // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17 |
|
| 92 | uint256 ceil = scaled.add(FULL_SCALE.sub(1)); |
|
| 93 | // e.g. 13814538111.399...e18 / 1e18 = 13814538111 |
|
| 94 | return ceil.div(FULL_SCALE); |
|
| 95 | } |
|
| 96 | ||
| 97 | /** |
|
| 98 | * @dev Precisely divides two units, by first scaling the left hand operand. Useful |
|
| 99 | * for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17) |
|
| 100 | * @param x Left hand input to division |
|
| 101 | * @param y Right hand input to division |
|
| 102 | * @return Result after multiplying the left operand by the scale, and |
|
| 103 | * executing the division on the right hand input. |
|
| 104 | */ |
|
| 105 | 3× | function divPrecisely(uint256 x, uint256 y) |
| 106 | internal |
|
| 107 | pure |
|
| 108 | 2× | returns (uint256) |
| 109 | { |
|
| 110 | // e.g. 8e18 * 1e18 = 8e36 |
|
| 111 | 7× | uint256 z = x.mul(FULL_SCALE); |
| 112 | // e.g. 8e36 / 10e18 = 8e17 |
|
| 113 | 6× | return z.div(y); |
| 114 | } |
|
| 115 | } |
83%
lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
Lines covered: 5 / 6 (83%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.0; |
|
| 5 | ||
| 6 | /** |
|
| 7 | * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow |
|
| 8 | * checks. |
|
| 9 | * |
|
| 10 | * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can |
|
| 11 | * easily result in undesired exploitation or bugs, since developers usually |
|
| 12 | * assume that overflows raise errors. `SafeCast` restores this intuition by |
|
| 13 | * reverting the transaction when such an 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 | * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing |
|
| 19 | * all math on `uint256` and `int256` and then downcasting. |
|
| 20 | */ |
|
| 21 | 0 | library SafeCast { |
| 22 | /** |
|
| 23 | * @dev Returns the downcasted uint224 from uint256, reverting on |
|
| 24 | * overflow (when the input is greater than largest uint224). |
|
| 25 | * |
|
| 26 | * Counterpart to Solidity's `uint224` operator. |
|
| 27 | * |
|
| 28 | * Requirements: |
|
| 29 | * |
|
| 30 | * - input must fit into 224 bits |
|
| 31 | */ |
|
| 32 | function toUint224(uint256 value) internal pure returns (uint224) { |
|
| 33 | require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); |
|
| 34 | return uint224(value); |
|
| 35 | } |
|
| 36 | ||
| 37 | /** |
|
| 38 | * @dev Returns the downcasted uint128 from uint256, reverting on |
|
| 39 | * overflow (when the input is greater than largest uint128). |
|
| 40 | * |
|
| 41 | * Counterpart to Solidity's `uint128` operator. |
|
| 42 | * |
|
| 43 | * Requirements: |
|
| 44 | * |
|
| 45 | * - input must fit into 128 bits |
|
| 46 | */ |
|
| 47 | function toUint128(uint256 value) internal pure returns (uint128) { |
|
| 48 | require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); |
|
| 49 | return uint128(value); |
|
| 50 | } |
|
| 51 | ||
| 52 | /** |
|
| 53 | * @dev Returns the downcasted uint96 from uint256, reverting on |
|
| 54 | * overflow (when the input is greater than largest uint96). |
|
| 55 | * |
|
| 56 | * Counterpart to Solidity's `uint96` operator. |
|
| 57 | * |
|
| 58 | * Requirements: |
|
| 59 | * |
|
| 60 | * - input must fit into 96 bits |
|
| 61 | */ |
|
| 62 | function toUint96(uint256 value) internal pure returns (uint96) { |
|
| 63 | require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); |
|
| 64 | return uint96(value); |
|
| 65 | } |
|
| 66 | ||
| 67 | /** |
|
| 68 | * @dev Returns the downcasted uint64 from uint256, reverting on |
|
| 69 | * overflow (when the input is greater than largest uint64). |
|
| 70 | * |
|
| 71 | * Counterpart to Solidity's `uint64` operator. |
|
| 72 | * |
|
| 73 | * Requirements: |
|
| 74 | * |
|
| 75 | * - input must fit into 64 bits |
|
| 76 | */ |
|
| 77 | function toUint64(uint256 value) internal pure returns (uint64) { |
|
| 78 | require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); |
|
| 79 | return uint64(value); |
|
| 80 | } |
|
| 81 | ||
| 82 | /** |
|
| 83 | * @dev Returns the downcasted uint32 from uint256, reverting on |
|
| 84 | * overflow (when the input is greater than largest uint32). |
|
| 85 | * |
|
| 86 | * Counterpart to Solidity's `uint32` operator. |
|
| 87 | * |
|
| 88 | * Requirements: |
|
| 89 | * |
|
| 90 | * - input must fit into 32 bits |
|
| 91 | */ |
|
| 92 | function toUint32(uint256 value) internal pure returns (uint32) { |
|
| 93 | require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); |
|
| 94 | return uint32(value); |
|
| 95 | } |
|
| 96 | ||
| 97 | /** |
|
| 98 | * @dev Returns the downcasted uint16 from uint256, reverting on |
|
| 99 | * overflow (when the input is greater than largest uint16). |
|
| 100 | * |
|
| 101 | * Counterpart to Solidity's `uint16` operator. |
|
| 102 | * |
|
| 103 | * Requirements: |
|
| 104 | * |
|
| 105 | * - input must fit into 16 bits |
|
| 106 | */ |
|
| 107 | function toUint16(uint256 value) internal pure returns (uint16) { |
|
| 108 | require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); |
|
| 109 | return uint16(value); |
|
| 110 | } |
|
| 111 | ||
| 112 | /** |
|
| 113 | * @dev Returns the downcasted uint8 from uint256, reverting on |
|
| 114 | * overflow (when the input is greater than largest uint8). |
|
| 115 | * |
|
| 116 | * Counterpart to Solidity's `uint8` operator. |
|
| 117 | * |
|
| 118 | * Requirements: |
|
| 119 | * |
|
| 120 | * - input must fit into 8 bits. |
|
| 121 | */ |
|
| 122 | function toUint8(uint256 value) internal pure returns (uint8) { |
|
| 123 | require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); |
|
| 124 | return uint8(value); |
|
| 125 | } |
|
| 126 | ||
| 127 | /** |
|
| 128 | * @dev Converts a signed int256 into an unsigned uint256. |
|
| 129 | * |
|
| 130 | * Requirements: |
|
| 131 | * |
|
| 132 | * - input must be greater than or equal to 0. |
|
| 133 | */ |
|
| 134 | 2× | function toUint256(int256 value) internal pure returns (uint256) { |
| 135 | 6× | require(value >= 0, "SafeCast: value must be positive"); |
| 136 | return uint256(value); |
|
| 137 | } |
|
| 138 | ||
| 139 | /** |
|
| 140 | * @dev Returns the downcasted int128 from int256, reverting on |
|
| 141 | * overflow (when the input is less than smallest int128 or |
|
| 142 | * greater than largest int128). |
|
| 143 | * |
|
| 144 | * Counterpart to Solidity's `int128` operator. |
|
| 145 | * |
|
| 146 | * Requirements: |
|
| 147 | * |
|
| 148 | * - input must fit into 128 bits |
|
| 149 | * |
|
| 150 | * _Available since v3.1._ |
|
| 151 | */ |
|
| 152 | function toInt128(int256 value) internal pure returns (int128) { |
|
| 153 | require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits"); |
|
| 154 | return int128(value); |
|
| 155 | } |
|
| 156 | ||
| 157 | /** |
|
| 158 | * @dev Returns the downcasted int64 from int256, reverting on |
|
| 159 | * overflow (when the input is less than smallest int64 or |
|
| 160 | * greater than largest int64). |
|
| 161 | * |
|
| 162 | * Counterpart to Solidity's `int64` operator. |
|
| 163 | * |
|
| 164 | * Requirements: |
|
| 165 | * |
|
| 166 | * - input must fit into 64 bits |
|
| 167 | * |
|
| 168 | * _Available since v3.1._ |
|
| 169 | */ |
|
| 170 | function toInt64(int256 value) internal pure returns (int64) { |
|
| 171 | require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits"); |
|
| 172 | return int64(value); |
|
| 173 | } |
|
| 174 | ||
| 175 | /** |
|
| 176 | * @dev Returns the downcasted int32 from int256, reverting on |
|
| 177 | * overflow (when the input is less than smallest int32 or |
|
| 178 | * greater than largest int32). |
|
| 179 | * |
|
| 180 | * Counterpart to Solidity's `int32` operator. |
|
| 181 | * |
|
| 182 | * Requirements: |
|
| 183 | * |
|
| 184 | * - input must fit into 32 bits |
|
| 185 | * |
|
| 186 | * _Available since v3.1._ |
|
| 187 | */ |
|
| 188 | function toInt32(int256 value) internal pure returns (int32) { |
|
| 189 | require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits"); |
|
| 190 | return int32(value); |
|
| 191 | } |
|
| 192 | ||
| 193 | /** |
|
| 194 | * @dev Returns the downcasted int16 from int256, reverting on |
|
| 195 | * overflow (when the input is less than smallest int16 or |
|
| 196 | * greater than largest int16). |
|
| 197 | * |
|
| 198 | * Counterpart to Solidity's `int16` operator. |
|
| 199 | * |
|
| 200 | * Requirements: |
|
| 201 | * |
|
| 202 | * - input must fit into 16 bits |
|
| 203 | * |
|
| 204 | * _Available since v3.1._ |
|
| 205 | */ |
|
| 206 | function toInt16(int256 value) internal pure returns (int16) { |
|
| 207 | require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits"); |
|
| 208 | return int16(value); |
|
| 209 | } |
|
| 210 | ||
| 211 | /** |
|
| 212 | * @dev Returns the downcasted int8 from int256, reverting on |
|
| 213 | * overflow (when the input is less than smallest int8 or |
|
| 214 | * greater than largest int8). |
|
| 215 | * |
|
| 216 | * Counterpart to Solidity's `int8` operator. |
|
| 217 | * |
|
| 218 | * Requirements: |
|
| 219 | * |
|
| 220 | * - input must fit into 8 bits. |
|
| 221 | * |
|
| 222 | * _Available since v3.1._ |
|
| 223 | */ |
|
| 224 | function toInt8(int256 value) internal pure returns (int8) { |
|
| 225 | require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits"); |
|
| 226 | return int8(value); |
|
| 227 | } |
|
| 228 | ||
| 229 | /** |
|
| 230 | * @dev Converts an unsigned uint256 into a signed int256. |
|
| 231 | * |
|
| 232 | * Requirements: |
|
| 233 | * |
|
| 234 | * - input must be less than or equal to maxInt256. |
|
| 235 | */ |
|
| 236 | 6× | function toInt256(uint256 value) internal pure returns (int256) { |
| 237 | // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive |
|
| 238 | 20× | require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256"); |
| 239 | 2× | return int256(value); |
| 240 | } |
|
| 241 | } |
80%
lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol
Lines covered: 4 / 5 (80%)
| 1 | // SPDX-License-Identifier: MIT |
|
| 2 | // OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol) |
|
| 3 | ||
| 4 | pragma solidity ^0.8.0; |
|
| 5 | ||
| 6 | // CAUTION |
|
| 7 | // This version of SafeMath should only be used with Solidity 0.8 or later, |
|
| 8 | // because it relies on the compiler's built in overflow checks. |
|
| 9 | ||
| 10 | /** |
|
| 11 | * @dev Wrappers over Solidity's arithmetic operations. |
|
| 12 | * |
|
| 13 | * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler |
|
| 14 | * now has built in overflow checking. |
|
| 15 | */ |
|
| 16 | 0 | library SafeMath { |
| 17 | /** |
|
| 18 | * @dev Returns the addition of two unsigned integers, with an overflow flag. |
|
| 19 | * |
|
| 20 | * _Available since v3.4._ |
|
| 21 | */ |
|
| 22 | function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { |
|
| 23 | unchecked { |
|
| 24 | uint256 c = a + b; |
|
| 25 | if (c < a) return (false, 0); |
|
| 26 | return (true, c); |
|
| 27 | } |
|
| 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 | unchecked { |
|
| 37 | if (b > a) return (false, 0); |
|
| 38 | return (true, a - b); |
|
| 39 | } |
|
| 40 | } |
|
| 41 | ||
| 42 | /** |
|
| 43 | * @dev Returns the multiplication of two unsigned integers, with an overflow flag. |
|
| 44 | * |
|
| 45 | * _Available since v3.4._ |
|
| 46 | */ |
|
| 47 | function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { |
|
| 48 | unchecked { |
|
| 49 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the |
|
| 50 | // benefit is lost if 'b' is also tested. |
|
| 51 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 |
|
| 52 | if (a == 0) return (true, 0); |
|
| 53 | uint256 c = a * b; |
|
| 54 | if (c / a != b) return (false, 0); |
|
| 55 | return (true, c); |
|
| 56 | } |
|
| 57 | } |
|
| 58 | ||
| 59 | /** |
|
| 60 | * @dev Returns the division of two unsigned integers, with a division by zero flag. |
|
| 61 | * |
|
| 62 | * _Available since v3.4._ |
|
| 63 | */ |
|
| 64 | function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { |
|
| 65 | unchecked { |
|
| 66 | if (b == 0) return (false, 0); |
|
| 67 | return (true, a / b); |
|
| 68 | } |
|
| 69 | } |
|
| 70 | ||
| 71 | /** |
|
| 72 | * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. |
|
| 73 | * |
|
| 74 | * _Available since v3.4._ |
|
| 75 | */ |
|
| 76 | function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { |
|
| 77 | unchecked { |
|
| 78 | if (b == 0) return (false, 0); |
|
| 79 | return (true, a % b); |
|
| 80 | } |
|
| 81 | } |
|
| 82 | ||
| 83 | /** |
|
| 84 | * @dev Returns the addition of two unsigned integers, reverting on |
|
| 85 | * overflow. |
|
| 86 | * |
|
| 87 | * Counterpart to Solidity's `+` operator. |
|
| 88 | * |
|
| 89 | * Requirements: |
|
| 90 | * |
|
| 91 | * - Addition cannot overflow. |
|
| 92 | */ |
|
| 93 | function add(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 94 | return a + b; |
|
| 95 | } |
|
| 96 | ||
| 97 | /** |
|
| 98 | * @dev Returns the subtraction of two unsigned integers, reverting on |
|
| 99 | * overflow (when the result is negative). |
|
| 100 | * |
|
| 101 | * Counterpart to Solidity's `-` operator. |
|
| 102 | * |
|
| 103 | * Requirements: |
|
| 104 | * |
|
| 105 | * - Subtraction cannot overflow. |
|
| 106 | */ |
|
| 107 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 108 | return a - b; |
|
| 109 | } |
|
| 110 | ||
| 111 | /** |
|
| 112 | * @dev Returns the multiplication of two unsigned integers, reverting on |
|
| 113 | * overflow. |
|
| 114 | * |
|
| 115 | * Counterpart to Solidity's `*` operator. |
|
| 116 | * |
|
| 117 | * Requirements: |
|
| 118 | * |
|
| 119 | * - Multiplication cannot overflow. |
|
| 120 | */ |
|
| 121 | 6× | function mul(uint256 a, uint256 b) internal pure returns (uint256) { |
| 122 | 9× | return a * b; |
| 123 | } |
|
| 124 | ||
| 125 | /** |
|
| 126 | * @dev Returns the integer division of two unsigned integers, reverting on |
|
| 127 | * division by zero. The result is rounded towards zero. |
|
| 128 | * |
|
| 129 | * Counterpart to Solidity's `/` operator. |
|
| 130 | * |
|
| 131 | * Requirements: |
|
| 132 | * |
|
| 133 | * - The divisor cannot be zero. |
|
| 134 | */ |
|
| 135 | 2× | function div(uint256 a, uint256 b) internal pure returns (uint256) { |
| 136 | 5× | return a / b; |
| 137 | } |
|
| 138 | ||
| 139 | /** |
|
| 140 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), |
|
| 141 | * reverting when dividing by zero. |
|
| 142 | * |
|
| 143 | * Counterpart to Solidity's `%` operator. This function uses a `revert` |
|
| 144 | * opcode (which leaves remaining gas untouched) while Solidity uses an |
|
| 145 | * invalid opcode to revert (consuming all remaining gas). |
|
| 146 | * |
|
| 147 | * Requirements: |
|
| 148 | * |
|
| 149 | * - The divisor cannot be zero. |
|
| 150 | */ |
|
| 151 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { |
|
| 152 | return a % b; |
|
| 153 | } |
|
| 154 | ||
| 155 | /** |
|
| 156 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on |
|
| 157 | * overflow (when the result is negative). |
|
| 158 | * |
|
| 159 | * CAUTION: This function is deprecated because it requires allocating memory for the error |
|
| 160 | * message unnecessarily. For custom revert reasons use {trySub}. |
|
| 161 | * |
|
| 162 | * Counterpart to Solidity's `-` operator. |
|
| 163 | * |
|
| 164 | * Requirements: |
|
| 165 | * |
|
| 166 | * - Subtraction cannot overflow. |
|
| 167 | */ |
|
| 168 | function sub( |
|
| 169 | uint256 a, |
|
| 170 | uint256 b, |
|
| 171 | string memory errorMessage |
|
| 172 | ) internal pure returns (uint256) { |
|
| 173 | unchecked { |
|
| 174 | require(b <= a, errorMessage); |
|
| 175 | return a - b; |
|
| 176 | } |
|
| 177 | } |
|
| 178 | ||
| 179 | /** |
|
| 180 | * @dev Returns the integer division of two unsigned integers, reverting with custom message on |
|
| 181 | * division by zero. The result is rounded towards zero. |
|
| 182 | * |
|
| 183 | * Counterpart to Solidity's `/` operator. Note: this function uses a |
|
| 184 | * `revert` opcode (which leaves remaining gas untouched) while Solidity |
|
| 185 | * uses an invalid opcode to revert (consuming all remaining gas). |
|
| 186 | * |
|
| 187 | * Requirements: |
|
| 188 | * |
|
| 189 | * - The divisor cannot be zero. |
|
| 190 | */ |
|
| 191 | function div( |
|
| 192 | uint256 a, |
|
| 193 | uint256 b, |
|
| 194 | string memory errorMessage |
|
| 195 | ) internal pure returns (uint256) { |
|
| 196 | unchecked { |
|
| 197 | require(b > 0, errorMessage); |
|
| 198 | return a / b; |
|
| 199 | } |
|
| 200 | } |
|
| 201 | ||
| 202 | /** |
|
| 203 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), |
|
| 204 | * reverting with custom message when dividing by zero. |
|
| 205 | * |
|
| 206 | * CAUTION: This function is deprecated because it requires allocating memory for the error |
|
| 207 | * message unnecessarily. For custom revert reasons use {tryMod}. |
|
| 208 | * |
|
| 209 | * Counterpart to Solidity's `%` operator. This function uses a `revert` |
|
| 210 | * opcode (which leaves remaining gas untouched) while Solidity uses an |
|
| 211 | * invalid opcode to revert (consuming all remaining gas). |
|
| 212 | * |
|
| 213 | * Requirements: |
|
| 214 | * |
|
| 215 | * - The divisor cannot be zero. |
|
| 216 | */ |
|
| 217 | function mod( |
|
| 218 | uint256 a, |
|
| 219 | uint256 b, |
|
| 220 | string memory errorMessage |
|
| 221 | ) internal pure returns (uint256) { |
|
| 222 | unchecked { |
|
| 223 | require(b > 0, errorMessage); |
|
| 224 | return a % b; |
|
| 225 | } |
|
| 226 | } |
|
| 227 | } |
100%
test/recon/CryticTester.sol
Lines covered: 1 / 1 (100%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {Properties} from "./Properties.sol"; |
|
| 5 | ||
| 6 | // echidna test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml |
|
| 7 | 332× | contract CryticTester is Properties {} |
88%
test/recon/Properties.sol
Lines covered: 8 / 9 (88%)
| 1 | // SPDX-License-Identifier: BUSL-1.1 |
|
| 2 | pragma solidity ^0.8.0; |
|
| 3 | ||
| 4 | import {Echidna} from "../../contracts/contracts/echidna/Echidna.sol"; |
|
| 5 | ||
| 6 | /// @notice scfuzzbench adaptation layer: canary checks on top of the |
|
| 7 | /// upstream OUSD Echidna suite. Assertion failures are surfaced the same |
|
| 8 | /// way for Echidna, Medusa, Foundry, and Recon (AssertionFailed event + |
|
| 9 | /// assert), and dedup to the emitting function name across fuzzers. |
|
| 10 | abstract contract Properties is Echidna { |
|
| 11 | string internal constant ASSERTION_CANARY = "!!! canary assertion"; |
|
| 12 | string internal constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE = "Canary invariant"; |
|
| 13 | ||
| 14 | event AssertionFailed(string reason); |
|
| 15 | ||
| 16 | 1× | function t(bool b, string memory reason) internal { |
| 17 | 3× | if (!b) { |
| 18 | 17× | emit AssertionFailed(reason); |
| 19 | 3× | assert(false); |
| 20 | } |
|
| 21 | } |
|
| 22 | ||
| 23 | 6× | function invariant_canary() public returns (bool) { |
| 24 | 20× | t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE); |
| 25 | 0 | return true; |
| 26 | } |
|
| 27 | ||
| 28 | 11× | function assert_canary_ASSERTION_CANARY(uint256 entropy) public { |
| 29 | 22× | t(entropy > 0, ASSERTION_CANARY); |
| 30 | } |
|
| 31 | } |