├── .github └── workflows │ ├── certora.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.MD ├── audits ├── FeeFlow Ottersec Report.pdf ├── FeeFlow Team Omega Report.pdf └── FeeFlow Zellic Report.pdf ├── docs └── whitepaper.md ├── formal-verification ├── .gitignore ├── Makefile ├── NOTES.md ├── certora │ ├── conf │ │ └── default.conf │ ├── harnesses │ │ └── FeeFlowControllerHarness.sol │ └── specs │ │ ├── FeeFlowController.spec │ │ ├── helpers │ │ ├── ArbitraryValues.sol │ │ ├── erc20.spec │ │ └── tokens │ │ │ ├── DummyERC20A.sol │ │ │ ├── DummyERC20B.sol │ │ │ ├── DummyERC20Impl.sol │ │ │ ├── DummyWeth.sol │ │ │ ├── ERC20Basic.sol │ │ │ ├── FTT.sol │ │ │ ├── SushiToken.sol │ │ │ ├── USDC.sol │ │ │ └── USDT.sol │ │ └── methods │ │ └── IFeeFlowController.spec ├── diff │ └── MinimalEVCClient.sol.patch ├── make-patch.sh ├── make-record.sh └── run.sh ├── foundry.toml ├── remappings.txt ├── src └── FeeFlowController.sol └── test ├── FeeFlowController.t.sol ├── MinimalEVCClient.t.sol └── lib ├── MockEVC.sol ├── MockToken.sol ├── OverflowableEpochIdFeeFlowController.sol ├── PredictAddress.sol ├── ReenteringMockToken.sol └── TestableMinimalEVCClient.sol /.github/workflows/certora.yml: -------------------------------------------------------------------------------- 1 | # A workflow file for running Certora verification through GitHub actions. 2 | # Find results for each push in the "Actions" tab on the GitHub website. 3 | name: Certora verification 4 | 5 | on: 6 | pull_request: 7 | types: [opened, synchronize, reopened, edited] 8 | workflow_dispatch: {} 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | verify: 14 | runs-on: ubuntu-latest 15 | steps: 16 | # check out the current version 17 | - uses: actions/checkout@v4 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | submodules: true 21 | 22 | # install Certora dependencies and CLI 23 | - name: Install python 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: '3.10' 27 | # cache: 'pip' 28 | - name: Install certora 29 | run: pip3 install certora-cli 30 | 31 | # the following is only necessary if your project depends on contracts 32 | # installed using yarn 33 | # - name: Install yarn 34 | # uses: actions/setup-node@v3 35 | # with: 36 | # node-version: 16 37 | # cache: 'yarn' 38 | # - name: Install dependencies 39 | # run: yarn 40 | 41 | # Install the appropriate version of solc 42 | - name: Install solc 43 | run: | 44 | pip install solc-select 45 | solc-select install 0.8.24 46 | solc-select use 0.8.24 47 | # It is also possible to download the solc binaries directly 48 | # wget https://github.com/ethereum/solidity/releases/download/v0.8.0/solc-static-linux 49 | # sudo mv solc-static-linux /usr/local/bin/solc8.0 50 | # chmod +x /usr/local/bin/solc8.0 51 | # ln -s /usr/local/bin/solc8.0 /usr/local/bin/solc 52 | 53 | # Do the actual verification. The `run` field could be simply 54 | # 55 | # certoraRun certora/conf/${{ matrix.params }} 56 | # 57 | # but we do a little extra work to get the commit messages into the 58 | # `--msg` argument 59 | # 60 | # Here ${{ matrix.params }} gets replaced with each of the parameters 61 | # listed in the `params` section below. 62 | - name: Verify rule ${{ matrix.params.name }} 63 | run: > 64 | message="$(git log -n 1 --pretty=format:'CI_${{matrix.params.name}}_%h')"; 65 | ./formal-verification/run.sh ${{ matrix.params.command }} \ 66 | --msg "$(echo $message | sed 's/[^a-zA-Z0-9., _-]/ /g')" \ 67 | 68 | env: 69 | # For this to work, you must set your CERTORAKEY secret on the GitHub 70 | # website (settings > secrets > actions > new repository secret) 71 | CERTORAKEY: ${{ secrets.CERTORAKEY }} 72 | 73 | # The following two steps save the output json as a GitHub artifact. 74 | # This can be useful for automation that collects the output. 75 | - name: Download output json 76 | if: always() 77 | run: > 78 | outputLink=$(sed 's/zipOutput/output/g' .zip-output-url.txt | sed 's/?/\/output.json?/g'); 79 | curl -L -b "certoraKey=$CERTORAKEY;" ${outputLink} --output output.json || true; 80 | touch output.json; 81 | 82 | - name: Archive output json 83 | if: always() 84 | uses: actions/upload-artifact@v3 85 | with: 86 | name: output for ${{ matrix.params.name }} 87 | path: output.json 88 | 89 | strategy: 90 | fail-fast: false 91 | max-parallel: 4 92 | matrix: 93 | params: 94 | # Each of these commands is passed to the "Verify rule" step above, 95 | # which runs certoraRun on certora/conf/ 96 | # 97 | # Note that each of these lines will appear as a separate run on 98 | # prover.certora.com 99 | # 100 | # It is often helpful to split up by rule or even by method for a 101 | # parametric rule, although it is certainly possible to run everything 102 | # at once by not passing the `--rule` or `--method` options 103 | #- {name: transferSpec, command: 'ERC20'} 104 | #- {name: generalRulesOnERC20, command: 'generalRules_ERC20'} 105 | #- {name: generalRulesOnVAULT, command: 'generalRules_VAULT'} 106 | - {name: RulesForFeeFlowController, command: 'default'} 107 | 108 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Dotenv file 11 | .env 12 | 13 | 14 | .DS_Store 15 | 16 | # Coverage files 17 | coverage/ 18 | lcov.info 19 | .certora* 20 | 21 | node_modules/ 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solmate"] 5 | path = lib/solmate 6 | url = https://github.com/transmissions11/solmate 7 | [submodule "lib/ethereum-vault-connector"] 8 | path = lib/ethereum-vault-connector 9 | url = https://github.com/euler-xyz/ethereum-vault-connector 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Fee Flow 2 | 3 | Efficient, decentralised and MEV resistant mechanism to convert fee assets to a single token. Powered by a continuous auto-adjusting Dutch auction mechanism. 4 | 5 | For more information, refer to the [whitepaper](docs/whitepaper.md) 6 | 7 | ## How to run 8 | 9 | ```bash 10 | # compile 11 | forge build 12 | # test 13 | forge test 14 | # generate coverage 15 | forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage 16 | ``` 17 | 18 | ## Security 19 | 20 | Fee Flow has been audited by Zellic, Ottersec and Team Omega. You can find the audit reports [here](audits/). 21 | 22 | **No warranties are provided** and **no liability will be accepted for any loss** incurred through the use of this codebase. 23 | 24 | 25 | ## License 26 | 27 | Licensed under the [GPL-2.0-or-later](LICENSE) license. -------------------------------------------------------------------------------- /audits/FeeFlow Ottersec Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/fee-flow/88ca60a441d50f1bf49c6872a38efb1cf3523f44/audits/FeeFlow Ottersec Report.pdf -------------------------------------------------------------------------------- /audits/FeeFlow Team Omega Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/fee-flow/88ca60a441d50f1bf49c6872a38efb1cf3523f44/audits/FeeFlow Team Omega Report.pdf -------------------------------------------------------------------------------- /audits/FeeFlow Zellic Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/fee-flow/88ca60a441d50f1bf49c6872a38efb1cf3523f44/audits/FeeFlow Zellic Report.pdf -------------------------------------------------------------------------------- /docs/whitepaper.md: -------------------------------------------------------------------------------- 1 | # Fee Flow 2 | 3 | ## Authors 4 | 5 | Mick de Graaf & Michael Bentley 6 | 7 | ## Introduction 8 | 9 | Protocols in decentralised finance (DeFi) often generate revenues by accruing fees across a range of markets in a variety of different asset types. The default behaviour of the protocol will typically be to hold all these asset types on the protocol’s balance sheet as protocol-owned liquidity (POL). However, this will often be a suboptimal use of accrued fees. 10 | 11 | In many instances it might be beneficial for the protocol to convert accrued fees into a single currency (perhaps USDC or ETH or the project’s native token) for accumulation or future distribution. Yet mechanisms for converting accrued fees into a single asset are notoriously problematic and generally not common in DeFi. Specifically, they are often inefficient, vulnerable to value extraction by validators (MEV), or otherwise require interventions by governance or trusted parties. 12 | 13 | Here, we outline Fee Flow: an efficient, decentralised, MEV-resistant mechanism for protocols to be able to auction their accrued fees for a single type of asset. 14 | 15 | ## Fee Flow Dutch Auction 16 | 17 | Let us assume a protocol is accruing fees in a variety of different asset types across a wide variety of markets. Each market has a function that allows anyone to transfer accrued fees from the market to a `FeeFlowController` smart contract inside of `FeeFlow` at a time of their choosing. Being costly to do this, and in the absence of any other incentive to do so, this function is unlikely to be called on a market very often. 18 | 19 | The `FeeFlowController` accumulates fees (often implicitly, see below) and periodically auctions them via a Dutch auction. The auction takes place in discrete epochs. Each epoch, the `initPrice` of the auction starts at a factor `priceMultiplier` times the settlement price of the auction in the prior epoch. It then falls linearly over time, tending to zero, over an `epochPeriod`. For example, the auction might start at a factor `priceMultiplier=2` times the settlement price of the prior auction and last an `epochPeriod=100` days. 20 | 21 | At the beginning of an epoch, the `FeeFlowController` holds no fees and has a high price, so is unlikely to settle any time soon. However, as fees accrue on markets, and the auction price falls, there will usually come a time when the auction price is lower than the aggregate value of all accrued fees. The first person to pay the auction price at this point is allowed to claim all assets in the `FeeFlowController`. 22 | 23 | Note that in practice the winning auction bidder will likely monitor the value of accrued fees across markets off chain and only transfer them to the `FeeFlowController` just-in-time; that is, in the same transaction or same block as the pay the winning bid for the auction. Thus the `FeeFlowController` will often not actually hold many, or indeed any, assets. It will instead usually only implicitly hold assets. 24 | 25 | Inevitably, accrued fees in some of the markets will not be desired by bidders. They might not be worth the cost of gas to transfer them, or sell them, in the future. These will simply remain in their respective markets and may or may not be purchased in a later auction. 26 | 27 | ## Example 28 | 29 | Let us assume that there is a lending protocol accruing fees over 100 different markets. At epoch 0, the `FeeFlowController` initial price is set to 100 USDC, parameterised with `priceMultiplier=2`, and `epochPeriod=1` month. Suppose the lending protocol accumulates, on average, $20 worth of accrued fees per day. 30 | 31 | After five days, there are $100 worth of assets to claim. Each day the auction decreases in price by 100 / 30 USDC. So, after five days, the auction price is 100 - 5 * 100 / 30 = 83.333 USDC. 32 | 33 | Potential bidders monitor the value of accrued fees and the price of the auction carefully. Paying 83.33 USDC for $100 worth of assets might seem like a good deal, but the winning bidder has to account for the cost of gas to transfer all the assets to the `FeeFlowController`, and then to themselves, and then the cost to sell them. Thus, five days might not be enough to let this auction settle, but six or seven days might be. 34 | 35 | Each potential bidder wants to wait as long as they can to let the price of the auction fall, and the accrued fee value rise, in order to maximise their profit. But allowing any extra time after the auction has become profitable risks losing profit to a competing bidder. Thus, in an efficient-market the auction settlement price is predicted to approach the marginal price of profitability of the auction. 36 | 37 | In this regard, the auction is likely to get as close to maximal efficiency as possible. The auction also requires no human or governance intervention. It can run automatically every epoch. Finally, the auction is also MEV-resistant. In fact, MEV searchers are likely to participate in the auction helping to increase its economic efficiency. 38 | 39 | ## Use cases 40 | 41 | Fee Flow is very flexible. It is agnostic to the underlying protocol it accrues fees from, and can convert fees into any kind of token. Popular choices might be ETH, USDC, some kind of LP token, or the broader project’s native token. In the latter case, Fee Flow represents a maximally efficient buy-back mechanism for the project’s native token that does not expose the buy-back to MEV or require intervention from governance or trusted parties. Accumulated purchase tokens inside the `FeeFlowController` can be deposited back into the protocol, deposited into a project’s treasury, redistributed via staking, or burned, depending on the goals of the project [1]. 42 | 43 | ## References 44 | 45 | [1] 2020. *Stop Burning Tokens – Buyback And Make Instead*. Joel Monegro, Placeholder.vc. https://www.placeholder.vc/blog/2020/9/17/stop-burning-tokens-buyback-and-make-instead. 46 | -------------------------------------------------------------------------------- /formal-verification/.gitignore: -------------------------------------------------------------------------------- 1 | patched 2 | metadata.json 3 | *.instrumented 4 | *.original 5 | .certora* 6 | .certora*.json 7 | **.last_conf* 8 | certora-logs 9 | resource_errors.json 10 | certora_debug_log.txt 11 | .zip-output-url.txt 12 | smt 13 | node_modules 14 | -------------------------------------------------------------------------------- /formal-verification/Makefile: -------------------------------------------------------------------------------- 1 | default: help 2 | 3 | SRC := ../src 4 | DST := patched 5 | DIFF := diff 6 | LIBS := $(shell find $(SRC)/lib -type f) 7 | SRCS := $(shell find $(SRC) -type f) 8 | DSTS := $(shell find $(DST) -type f) 9 | DIFFS := $(shell find $(DIFF) -type f) 10 | 11 | ############################################################################### 12 | # Apply all patches in the $DIFF folder to the $DST folder 13 | apply: $(DST) $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$(DIFFS))) 14 | 15 | # Reset the $DST folder 16 | $(DST): FORCE 17 | @rm -rf $@ 18 | @cp -r $(SRC) $@ 19 | # Update a solidity file in the $DST directory using the corresponding patch 20 | $(DST)/%.sol: FORCE 21 | @echo Applying patch to $@ 22 | @patch --batch -p1 -d $(DST) < $(patsubst $(DST)_%,$(DIFF)/%.patch,$(subst /,_,$@)) 23 | 24 | ############################################################################### 25 | # Record all difference between $SRC and $DST in patches 26 | record: $(DIFF) $(patsubst %,$(DIFF)/%.patch,$(subst /,_,$(subst $(SRC)/,,$(SRCS)) $(subst $(DST)/,,$(DSTS)))) 27 | 28 | # Create the $DIFF folder 29 | $(DIFF): FORCE 30 | @rm -rf $@ 31 | @mkdir $@ 32 | 33 | # Create the patch file by comparing the source and the destination 34 | $(DIFF)/%.patch: FORCE 35 | @echo Generating patch $@ 36 | @diff -ruN \ 37 | $(patsubst $(DIFF)/%.patch,$(SRC)/%,$(subst _,/,$@)) \ 38 | $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$@)) \ 39 | | sed 's+^\($(SRC)/[^[:space:]]*\))+$(SRC)/+' \ 40 | | sed 's+^\($(DST)/[^[:space:]]*\))+$(DST)/+' \ 41 | > $@ 42 | @[ -s $@ ] || rm $@ 43 | 44 | ############################################################################### 45 | help: 46 | @echo "usage:" 47 | @echo " make apply: create $(DST) directory by applying the patches to $(SRC)" 48 | @echo " make record: record the patches capturing the differences between $(SRC) and $(DST)" 49 | @echo " make clean: remove all generated files (those ignored by git)" 50 | 51 | clean: 52 | git clean -fdX 53 | 54 | FORCE: ; 55 | 56 | # echo that the script is done 57 | @echo "Done execution of Makefile" 58 | -------------------------------------------------------------------------------- /formal-verification/NOTES.md: -------------------------------------------------------------------------------- 1 | ~~ 1. Sending an empty array of assets would still transfer your funds. Maybe consider checking the length of the array?~~ (Resolved) 2 | 3 | ~~2. minInitPrice_ can be instantiated to up to uint256(-1) (2^256 - 1)~~ 4 | ~~However in case if(newInitPrice < minInitPrice) we are setting newInitPrice = minInitPrice; and then we are casting to uint128. This means that initPrice can be less then the minInitPrice in some edge cases.~~ 5 | ~~A mitigation can be for the minInitPrice to be uint128(-1) (2^128 - 1) and then we can cast to uint128 safely.~~ (Resolved) 6 | 7 | 3. Some tokens that are have some value but change their balance in a case of a real execution vs staticCall execution might bait a buy that transfers the payment amount but the received value is 0 for example. It would be more of a phishing attack if anyone can deploy a fee flow controller. 8 | 9 | 4. (INFO) Fee on transfer tokens might cause the emit of incorrect event amounts. Something to keep in mind. 10 | 11 | 5. (INFO) for gas optimization you can transfer the whole balance -1 wei to keep the slot non-zero. This will keep the gas cost constant and more predictable as well. 12 | 13 | 6. The init price casting has some edge cases where the price can go above uint128 and then it will be casted to uint128. This can be mitigated by setting the max price to uint128(-1) and then casting to uint128. This will make sure that the price will never go above uint128. The fix to this is in the FeeFlowController.sol.patch file. 14 | -------------------------------------------------------------------------------- /formal-verification/certora/conf/default.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "./formal-verification/certora/harnesses/FeeFlowControllerHarness.sol", 4 | "./formal-verification/certora/specs/helpers/tokens/DummyERC20A.sol", 5 | "./formal-verification/certora/specs/helpers/tokens/DummyERC20B.sol", 6 | "./formal-verification/certora/specs/helpers/tokens/DummyERC20Impl.sol", 7 | "./formal-verification/certora/specs/helpers/tokens/DummyWeth.sol", 8 | "./formal-verification/certora/specs/helpers/tokens/ERC20Basic.sol", 9 | "./formal-verification/certora/specs/helpers/tokens/FTT.sol", 10 | "./formal-verification/certora/specs/helpers/tokens/SushiToken.sol", 11 | ], 12 | "msg": "Test Fee flow controller buying auction", 13 | "process": "emv", 14 | "prover_args": [ 15 | "-deleteSMTFile false -s [z3,cvc5:nonlin,cvc4]" 16 | ], 17 | "wait_for_results": "all", 18 | "smt_timeout": "900", 19 | "verify": "FeeFlowControllerHarness:./formal-verification/certora/specs/FeeFlowController.spec", 20 | "optimistic_loop": true, 21 | "loop_iter": "4", 22 | } 23 | -------------------------------------------------------------------------------- /formal-verification/certora/harnesses/FeeFlowControllerHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "../../patched/FeeFlowController.sol"; 5 | 6 | contract FeeFlowControllerHarness is FeeFlowController { 7 | constructor( 8 | address evc, 9 | uint256 initPrice, 10 | address paymentToken_, 11 | address paymentReceiver_, 12 | uint256 epochPeriod_, 13 | uint256 priceMultiplier_, 14 | uint256 minInitPrice_ 15 | ) 16 | FeeFlowController(evc, initPrice, paymentToken_, paymentReceiver_, epochPeriod_, priceMultiplier_, minInitPrice_) 17 | {} 18 | 19 | function getAddressThis() external view returns (address) { 20 | return address(this); 21 | } 22 | 23 | function reentrancyMock() external nonReentrant { 24 | // this makes sure we are setting the reentrancy guard correctly 25 | } 26 | 27 | function getEVC() external view returns (address) { 28 | return address(evc); 29 | } 30 | 31 | function getTokenBalanceOf(address _token, address _account) external view returns (uint256) { 32 | ERC20 token = ERC20(_token); 33 | return token.balanceOf(_account); 34 | } 35 | 36 | function getPaymentTokenAllowance(address owner) external view returns (uint256) { 37 | return paymentToken.allowance(owner, address(this)); 38 | } 39 | 40 | function getPaymentTokenBalanceOf(address account) external view returns (uint256) { 41 | return paymentToken.balanceOf(account); 42 | } 43 | 44 | function getInitPrice() external view returns (uint256) { 45 | return slot0.initPrice; 46 | } 47 | 48 | function getStartTime() external view returns (uint256) { 49 | return slot0.startTime; 50 | } 51 | 52 | function getEpochId() external view returns (uint256) { 53 | return slot0.epochId; 54 | } 55 | 56 | // address immutable public paymentReceiver; 57 | function getPaymentReceiver() external view returns (address) { 58 | return paymentReceiver; 59 | } 60 | 61 | function getPaymentToken() external view returns (address) { 62 | return address(paymentToken); 63 | } 64 | 65 | // uint256 immutable public epochPeriod; 66 | function getEpochPeriod() external view returns (uint256) { 67 | return epochPeriod; 68 | } 69 | // uint256 immutable public priceMultiplier; 70 | 71 | function getPriceMultiplier() external view returns (uint256) { 72 | return priceMultiplier; 73 | } 74 | // uint256 immutable public minInitPrice; 75 | 76 | function getMinInitPrice() external view returns (uint256) { 77 | return minInitPrice; 78 | } 79 | 80 | // uint256 constant public MIN_EPOCH_PERIOD = 1 hours; 81 | function getMIN_EPOCH_PERIOD() external pure returns (uint256) { 82 | return MIN_EPOCH_PERIOD; 83 | } 84 | // uint256 constant public MIN_PRICE_MULTIPLIER = 1.1e18; // Should at least be 110% of settlement price 85 | 86 | function getMAX_EPOCH_PERIOD() external pure returns (uint256) { 87 | return MAX_EPOCH_PERIOD; 88 | } 89 | 90 | function getMIN_PRICE_MULTIPLIER() external pure returns (uint256) { 91 | return MIN_PRICE_MULTIPLIER; 92 | } 93 | // uint256 constant public MIN_MIN_INIT_PRICE = 1e6; // Minimum sane value for init price 94 | 95 | function getABS_MIN_INIT_PRICE() external pure returns (uint256) { 96 | return ABS_MIN_INIT_PRICE; 97 | } 98 | 99 | function getABS_MAX_INIT_PRICE() external pure returns (uint256) { 100 | return ABS_MAX_INIT_PRICE; 101 | } 102 | // uint256 constant public PRICE_MULTIPLIER_SCALE = 1e18; 103 | 104 | function getPRICE_MULTIPLIER_SCALE() external pure returns (uint256) { 105 | return PRICE_MULTIPLIER_SCALE; 106 | } 107 | 108 | function getMAX_PRICE_MULTIPLIER() external pure returns (uint256) { 109 | return MAX_PRICE_MULTIPLIER; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/FeeFlowController.spec: -------------------------------------------------------------------------------- 1 | import "methods/IFeeFlowController.spec"; 2 | import "helpers/erc20.spec"; 3 | 4 | using FeeFlowControllerHarness as feeFlowController; 5 | 6 | // Reusable functions 7 | function constructorAssumptions(env e) { 8 | uint initPriceStart = getInitPrice(); 9 | uint minInitPriceStart = getMinInitPrice(); 10 | uint ABS_MIN_INIT_PRICE = getABS_MIN_INIT_PRICE(); 11 | uint ABS_MAX_INIT_PRICE = getABS_MAX_INIT_PRICE(); 12 | uint MIN_PRICE_MULTIPLIER = getMIN_PRICE_MULTIPLIER(); 13 | uint MAX_PRICE_MULTIPLIER = getMAX_PRICE_MULTIPLIER(); 14 | 15 | //! if(initPrice < minInitPrice_) revert InitPriceBelowMin(); 16 | require initPriceStart >= minInitPriceStart; 17 | 18 | //! if(initPrice > ABS_MAX_INIT_PRICE) revert InitPriceAboveMax(); 19 | require initPriceStart <= ABS_MAX_INIT_PRICE; 20 | 21 | uint epochPeriodStart = getEpochPeriod(); 22 | uint MIN_EPOCH_PERIOD = getMIN_EPOCH_PERIOD(); 23 | 24 | //! if(epochPeriod_ < MIN_EPOCH_PERIOD) revert EpochPeriodBelowMin(); 25 | require epochPeriodStart >= MIN_EPOCH_PERIOD; 26 | 27 | //! if(epochPeriod_ > MAX_EPOCH_PERIOD) revert EpochPeriodExceedsMax(); 28 | uint MAX_EPOCH_PERIOD = getMAX_EPOCH_PERIOD(); 29 | require epochPeriodStart <= MAX_EPOCH_PERIOD; 30 | 31 | uint priceMultiplierStart = getPriceMultiplier(); 32 | 33 | //! if(priceMultiplier_ < MIN_PRICE_MULTIPLIER) revert PriceMultiplierBelowMin(); 34 | require priceMultiplierStart >= MIN_PRICE_MULTIPLIER; 35 | 36 | // ! if(priceMultiplier_ > MAX_PRICE_MULTIPLIER) revert PriceMultiplierAboveMax(); 37 | require priceMultiplierStart <= MAX_PRICE_MULTIPLIER; 38 | 39 | //! if(minInitPrice_ < ABS_MIN_INIT_PRICE) revert MinInitPriceBelowMin(); 40 | require minInitPriceStart >= ABS_MIN_INIT_PRICE; 41 | 42 | //! if(minInitPrice_ > ABS_MAX_INIT_PRICE) revert MinInitPriceExceedsuint128(); 43 | require minInitPriceStart <= ABS_MAX_INIT_PRICE; 44 | 45 | //! if(paymentReceiver_ == address(this)) revert PaymentReceiverIsThis(); 46 | address paymentReceiver = getPaymentReceiver(); 47 | require(paymentReceiver != feeFlowController); 48 | } 49 | 50 | function initialStateAssertions(env e) { 51 | uint initPrice = getInitPrice(); 52 | uint minInitPrice = getMinInitPrice(); 53 | uint epochPeriod = getEpochPeriod(); 54 | uint priceMultiplier = getPriceMultiplier(); 55 | uint startTime = getStartTime(); 56 | uint MIN_EPOCH_PERIOD = getMIN_EPOCH_PERIOD(); 57 | uint MIN_PRICE_MULTIPLIER = getMIN_PRICE_MULTIPLIER(); 58 | uint ABS_MIN_INIT_PRICE = getABS_MIN_INIT_PRICE(); 59 | uint ABS_MAX_INIT_PRICE = getABS_MAX_INIT_PRICE(); 60 | 61 | assert initPrice >= minInitPrice, "initPrice >= minInitPrice"; 62 | assert initPrice <= ABS_MAX_INIT_PRICE, "initPrice < ABS_MAX_INIT_PRICE"; 63 | assert epochPeriod >= MIN_EPOCH_PERIOD, "epochPeriod >= MIN_EPOCH_PERIOD"; 64 | assert priceMultiplier >= MIN_PRICE_MULTIPLIER, "priceMultiplier >= MIN_PRICE_MULTIPLIER"; 65 | assert minInitPrice >= ABS_MIN_INIT_PRICE, "minInitPrice >= ABS_MIN_INIT_PRICE"; 66 | assert minInitPrice <= ABS_MAX_INIT_PRICE, "minInitPrice <= ABS_MAX_INIT_PRICE"; 67 | assert e.block.timestamp >= startTime, "e.block.timestamp >= startTime"; 68 | } 69 | 70 | function requirementsForSuccessfulBuyExecution(env e, address[] assets, address assetsReceiver,uint256 epochId, uint256 deadline, uint256 maxPaymentTokenAmount) { 71 | reentrancyMock(); // this sets the lock to the correct initial value 72 | uint256 initPriceStart = getInitPrice(); 73 | require(epochId == getEpochId()); 74 | require(maxPaymentTokenAmount > initPriceStart); 75 | require(e.block.timestamp <= deadline); 76 | uint256 paymentAmount = getPrice(e); 77 | require(paymentAmount <= maxPaymentTokenAmount && paymentAmount > 0); 78 | require(e.msg.value == 0); // if we send ether the transaction will revert 79 | // we have enough allowance and we have enough balance 80 | uint256 allowance = feeFlowController.getPaymentTokenAllowance(e, e.msg.sender); 81 | uint256 balance = feeFlowController.getPaymentTokenBalanceOf(e, e.msg.sender); 82 | address receiver = feeFlowController.paymentReceiver(); 83 | require(receiver != 0); // this is for us to cover the ERC20Basic implementation 84 | require(e.msg.sender != 0); 85 | 86 | uint256 receiverBalance = feeFlowController.getPaymentTokenBalanceOf(e, receiver); 87 | mathint receiverNewBalance = receiverBalance + paymentAmount; 88 | require(receiverNewBalance < max_uint256); 89 | require(balance >= paymentAmount && allowance >= balance); 90 | require(assets.length == 1); // making 1 transfer for simplicity 91 | 92 | address token = assets[0]; 93 | require(assetsReceiver != 0); // make sure the receiver is not 0 94 | require(assetsReceiver != receiver); // make sure the receiver is not the payment receiver 95 | uint256 feeControllerTokenBalance = feeFlowController.getTokenBalanceOf(e, token, feeFlowController); 96 | uint256 assetReceiverTokenBalance = feeFlowController.getTokenBalanceOf(e, token, assetsReceiver); 97 | uint256 assetReceiverTokenBalanceAfter = require_uint256(feeControllerTokenBalance + assetReceiverTokenBalance); 98 | } 99 | 100 | persistent ghost bool reentrancy_happened { 101 | init_state axiom !reentrancy_happened; 102 | } 103 | 104 | persistent ghost bool reverted { 105 | init_state axiom !reverted; 106 | } 107 | 108 | hook CALL(uint g, address addr, uint value, uint argsOffset, uint argsLength, 109 | uint retOffset, uint retLength) uint rc { 110 | if (addr == currentContract) { 111 | reentrancy_happened = reentrancy_happened 112 | || executingContract == currentContract; 113 | } 114 | } 115 | 116 | hook REVERT(uint offset, uint size) { 117 | reverted = true; 118 | } 119 | 120 | // Invariants 121 | // NOTE: we are executing optimistic dispatches for the ERC20 tokens 122 | invariant invariant_no_reentrant_calls() !reentrancy_happened || reentrancy_happened && reverted; 123 | invariant invariant_init_price_must_be_in_range() getInitPrice() >= getMinInitPrice() && getInitPrice() <= getABS_MAX_INIT_PRICE(); 124 | invariant invariant_price_must_be_below_max_init_price(env e) getPrice(e) <= getInitPrice(); 125 | 126 | 127 | // Rules 128 | rule reachability(method f) 129 | { 130 | env e; 131 | calldataarg args; 132 | feeFlowController.f(e,args); 133 | satisfy true, "a non-reverting path through this method was found"; 134 | } 135 | 136 | rule check_constructorAssumptionsSatisfiedAfterBuy() { 137 | env e; 138 | constructorAssumptions(e); 139 | calldataarg args; 140 | buy(e, args); 141 | initialStateAssertions(e); 142 | } 143 | 144 | rule check_buyNeverRevertsUnexpectedly() { 145 | env e; 146 | constructorAssumptions(e); 147 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 148 | requirementsForSuccessfulBuyExecution(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 149 | buy@withrevert(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 150 | assert !lastReverted, "buy never reverts with arithmetic exceptions or internal solidity reverts"; 151 | } 152 | 153 | rule check_buyNextInitPriceAtLeastBuyPriceTimesMultiplier() { 154 | env e; 155 | constructorAssumptions(e); 156 | calldataarg args; 157 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 158 | requirementsForSuccessfulBuyExecution(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 159 | mathint paymentAmount = buy@withrevert(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 160 | 161 | mathint priceMultiplier = getPriceMultiplier(); 162 | mathint PRICE_MULTIPLIER_SCALE = getPRICE_MULTIPLIER_SCALE(); 163 | mathint predictedInitPrice = paymentAmount * priceMultiplier / PRICE_MULTIPLIER_SCALE; 164 | 165 | mathint initPriceAfter = getInitPrice(); 166 | mathint minInitPrice = getMinInitPrice(); 167 | mathint absMaxInitPrice = getABS_MAX_INIT_PRICE(); 168 | if (predictedInitPrice < minInitPrice) { 169 | assert initPriceAfter == minInitPrice, "initPrice == minInitPrice"; 170 | } else { 171 | if(predictedInitPrice > absMaxInitPrice) { 172 | assert initPriceAfter == absMaxInitPrice, "initPrice == ABS_MAX_INIT_PRICE"; 173 | } else { 174 | assert initPriceAfter == predictedInitPrice, "initPrice == paymentAmount * priceMultiplier / PRICE_MULTIPLIER_SCALE"; 175 | } 176 | } 177 | } 178 | 179 | // Balance of fee flow controller of a bought asset is always 0 after buy 180 | rule check_feeFlowControllerTokenBalanceOfAfterBuy() { 181 | env e; 182 | constructorAssumptions(e); 183 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 184 | 185 | require(assets.length == 1); // making 1 transfer for simplicity and since we have no loops in CVL 186 | require(assetsReceiver != feeFlowController); // make sure the receiver is not feeFlowController 187 | 188 | buy(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 189 | uint256 feeControllerTokenBalance = feeFlowController.getTokenBalanceOf(e, assets[0], feeFlowController); 190 | assert feeControllerTokenBalance == 0, "feeFlowController.getTokenBalanceOf(assets[0], feeFlowController) == 0"; 191 | } 192 | 193 | // Balance of asset receiver is incremented by balance of fee flow controller after buy 194 | rule check_balanceOfAssetsReceiverIsIncrementedByFeeFlowControllerTokenBalanceOfAfterBuy() { 195 | env e; 196 | constructorAssumptions(e); 197 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 198 | 199 | require(assets.length == 1); // making 1 transfer for simplicity and since we have no loops in CVL 200 | 201 | require(assetsReceiver != feeFlowController); // make sure the receiver is not feeFlowController 202 | require(assets[0] != getPaymentToken()); // make sure the asset is not the payment token 203 | 204 | uint256 assetReceiverTokenBalance = feeFlowController.getTokenBalanceOf(e, assets[0], assetsReceiver); 205 | uint256 feeControllerTokenBalance = feeFlowController.getTokenBalanceOf(e, assets[0], feeFlowController); 206 | uint256 assetReceiverTokenBalanceAfterPredicted = require_uint256(feeControllerTokenBalance + assetReceiverTokenBalance); 207 | 208 | buy(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 209 | 210 | assert feeFlowController.getTokenBalanceOf(e, assets[0], assetsReceiver) == assetReceiverTokenBalanceAfterPredicted, "feeFlowController.getTokenBalanceOf(assets[0], assetsReceiver) == assetReceiverTokenBalanceAfter"; 211 | } 212 | 213 | // Balance of buyer is reduced by payment amount, and never by more than max payment amount 214 | rule check_balanceOfBuyerIsReducedByPaymentAmount() { 215 | env e; 216 | constructorAssumptions(e); 217 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 218 | 219 | require(assets.length == 1); // making 1 transfer for simplicity and since we have no loops in CVL 220 | 221 | require(assetsReceiver != feeFlowController); // make sure the receiver is not feeFlowController 222 | require(assets[0] != getPaymentToken()); // make sure the asset is not the payment toke 223 | require(getPaymentReceiver() != e.msg.sender); // make sure the payment receiver is not the buyer 224 | 225 | uint256 paymentAmount = getPrice(e); 226 | uint256 balanceBefore = feeFlowController.getPaymentTokenBalanceOf(e, e.msg.sender); 227 | buy(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 228 | uint256 balanceAfter = feeFlowController.getPaymentTokenBalanceOf(e, e.msg.sender); 229 | assert to_mathint(balanceAfter) == (balanceBefore - paymentAmount), "msg.sender (PaymentToken) => balanceAfter == (balanceBefore - paymentAmount)"; 230 | } 231 | 232 | // Balance of payment receiver is increased by payment amount 233 | rule check_balanceOfPaymentReceiverIsIncreasedByPaymentAmount() { 234 | env e; 235 | constructorAssumptions(e); 236 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 237 | 238 | require(assets.length == 1); // making 1 transfer for simplicity and since we have no loops in CVL 239 | 240 | require(assetsReceiver != feeFlowController); // make sure the receiver is not feeFlowController 241 | require(assets[0] != getPaymentToken()); // make sure the asset is not the payment token 242 | require(getPaymentReceiver() != e.msg.sender); // make sure the payment receiver is not the buyer 243 | 244 | uint256 paymentAmount = getPrice(e); 245 | address paymentReceiver = getPaymentReceiver(); 246 | 247 | uint256 balanceBefore = feeFlowController.getPaymentTokenBalanceOf(e, paymentReceiver); 248 | buy(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 249 | uint256 balanceAfter = feeFlowController.getPaymentTokenBalanceOf(e, paymentReceiver); 250 | assert to_mathint(balanceAfter) == (balanceBefore + paymentAmount), "paymentReceiver (PaymentToken) => balanceAfter == (balanceBefore + paymentAmount)"; 251 | } 252 | 253 | // Payment amount returned on buy is never higher than maximum payout 254 | rule check_paymentAmountReturnedOnBuyIsNeverHigherThanMaximumPayout() { 255 | env e; 256 | constructorAssumptions(e); 257 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 258 | 259 | require(assets.length == 1); // making 1 transfer for simplicity and since we have no loops in CVL 260 | 261 | require(assetsReceiver != feeFlowController); // make sure the receiver is not feeFlowController 262 | require(assets[0] != getPaymentToken()); // make sure the asset is not the payment token 263 | require(getPaymentReceiver() != e.msg.sender); // make sure the payment receiver is not the buyer 264 | 265 | uint256 paymentAmount = getPrice(e); 266 | uint256 balanceBefore = feeFlowController.getPaymentTokenBalanceOf(e, assetsReceiver); 267 | buy(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 268 | uint256 balanceAfter = feeFlowController.getPaymentTokenBalanceOf(e, assetsReceiver); 269 | assert paymentAmount <= maxPaymentTokenAmount, "paymentAmount <= maxPaymentTokenAmount"; 270 | assert to_mathint(balanceAfter) <= (balanceBefore + maxPaymentTokenAmount), "balanceAfter <= (balanceBefore + maxPaymentTokenAmount)"; 271 | } 272 | 273 | // Epoch Id is always incremented by 1 after buy (and becomes 0 if it reaches max uint16) 274 | rule check_epochIdAlwaysIncrementedBy1AfterBuy() { 275 | env e; 276 | constructorAssumptions(e); 277 | address[] assets; address assetsReceiver; uint256 epochId; uint256 deadline; uint256 maxPaymentTokenAmount; 278 | uint256 epochIdBefore = getEpochId(); 279 | buy(e, assets, assetsReceiver, epochId, deadline, maxPaymentTokenAmount); 280 | uint256 epochIdAfter = getEpochId(); 281 | assert (epochIdAfter == 0 && epochIdBefore == max_uint16) || epochIdAfter == assert_uint256(epochIdBefore + 1), "epochIdAfter == (epochIdBefore + 1) || (epochIdAfter == 0 && epochIdBefore == 65535)"; 282 | } 283 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/ArbitraryValues.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.8.0; 2 | 3 | /** 4 | * This is a utility contract for constructing harnesses for the Certora Prover. 5 | * 6 | * Harnesses can inherit this contract and then call methods like 7 | * {arbitraryBool}, {arbitraryUint}, and so on. The prover will choose a 8 | * new non-deterministic value for each call to one of these methods. 9 | */ 10 | abstract contract ArbitraryValues { 11 | 12 | /* 13 | * These mappings are used to create arbitrary values for the prover. 14 | * The prover is allowed to choose any value for arbitraryUints[k] and 15 | * for arbitraryAddresses[k], so it has a lot of flexibility in choosing 16 | * the behavior of this contract when constructing counterexamples. 17 | * 18 | * Each time we need a new value, we use {counter} to index into the 19 | * mappings, and then increment {counter} (allowing the Prover to choose 20 | * a different value for the next arbitrary value). 21 | */ 22 | 23 | uint8 private counter; 24 | 25 | 26 | 27 | 28 | 29 | mapping (uint8 => bool) private arbitraryBools; 30 | 31 | /** nondeterministic bool value */ 32 | function arbitraryBool() internal returns(bool) { 33 | return arbitraryBools[counter++]; 34 | } 35 | 36 | mapping (uint8 => address) private arbitraryAddresss; 37 | 38 | /** nondeterministic address value */ 39 | function arbitraryAddress() internal returns(address) { 40 | return arbitraryAddresss[counter++]; 41 | } 42 | 43 | mapping (uint8 => int) private arbitraryInts; 44 | 45 | /** nondeterministic int value */ 46 | function arbitraryInt() internal returns(int) { 47 | return arbitraryInts[counter++]; 48 | } 49 | 50 | mapping (uint8 => uint) private arbitraryUints; 51 | 52 | /** nondeterministic uint value */ 53 | function arbitraryUint() internal returns(uint) { 54 | return arbitraryUints[counter++]; 55 | } 56 | 57 | 58 | 59 | mapping (uint8 => uint8) private arbitraryUint8s; 60 | 61 | /** nondeterministic uint8 value */ 62 | function arbitraryUint8() internal returns(uint8) { 63 | return arbitraryUint8s[counter++]; 64 | } 65 | 66 | mapping (uint8 => uint16) private arbitraryUint16s; 67 | 68 | /** nondeterministic uint16 value */ 69 | function arbitraryUint16() internal returns(uint16) { 70 | return arbitraryUint16s[counter++]; 71 | } 72 | 73 | mapping (uint8 => uint24) private arbitraryUint24s; 74 | 75 | /** nondeterministic uint24 value */ 76 | function arbitraryUint24() internal returns(uint24) { 77 | return arbitraryUint24s[counter++]; 78 | } 79 | 80 | mapping (uint8 => uint32) private arbitraryUint32s; 81 | 82 | /** nondeterministic uint32 value */ 83 | function arbitraryUint32() internal returns(uint32) { 84 | return arbitraryUint32s[counter++]; 85 | } 86 | 87 | mapping (uint8 => uint40) private arbitraryUint40s; 88 | 89 | /** nondeterministic uint40 value */ 90 | function arbitraryUint40() internal returns(uint40) { 91 | return arbitraryUint40s[counter++]; 92 | } 93 | 94 | mapping (uint8 => uint48) private arbitraryUint48s; 95 | 96 | /** nondeterministic uint48 value */ 97 | function arbitraryUint48() internal returns(uint48) { 98 | return arbitraryUint48s[counter++]; 99 | } 100 | 101 | mapping (uint8 => uint56) private arbitraryUint56s; 102 | 103 | /** nondeterministic uint56 value */ 104 | function arbitraryUint56() internal returns(uint56) { 105 | return arbitraryUint56s[counter++]; 106 | } 107 | 108 | mapping (uint8 => uint64) private arbitraryUint64s; 109 | 110 | /** nondeterministic uint64 value */ 111 | function arbitraryUint64() internal returns(uint64) { 112 | return arbitraryUint64s[counter++]; 113 | } 114 | 115 | mapping (uint8 => uint72) private arbitraryUint72s; 116 | 117 | /** nondeterministic uint72 value */ 118 | function arbitraryUint72() internal returns(uint72) { 119 | return arbitraryUint72s[counter++]; 120 | } 121 | 122 | mapping (uint8 => uint80) private arbitraryUint80s; 123 | 124 | /** nondeterministic uint80 value */ 125 | function arbitraryUint80() internal returns(uint80) { 126 | return arbitraryUint80s[counter++]; 127 | } 128 | 129 | mapping (uint8 => uint88) private arbitraryUint88s; 130 | 131 | /** nondeterministic uint88 value */ 132 | function arbitraryUint88() internal returns(uint88) { 133 | return arbitraryUint88s[counter++]; 134 | } 135 | 136 | mapping (uint8 => uint96) private arbitraryUint96s; 137 | 138 | /** nondeterministic uint96 value */ 139 | function arbitraryUint96() internal returns(uint96) { 140 | return arbitraryUint96s[counter++]; 141 | } 142 | 143 | mapping (uint8 => uint104) private arbitraryUint104s; 144 | 145 | /** nondeterministic uint104 value */ 146 | function arbitraryUint104() internal returns(uint104) { 147 | return arbitraryUint104s[counter++]; 148 | } 149 | 150 | mapping (uint8 => uint112) private arbitraryUint112s; 151 | 152 | /** nondeterministic uint112 value */ 153 | function arbitraryUint112() internal returns(uint112) { 154 | return arbitraryUint112s[counter++]; 155 | } 156 | 157 | mapping (uint8 => uint120) private arbitraryUint120s; 158 | 159 | /** nondeterministic uint120 value */ 160 | function arbitraryUint120() internal returns(uint120) { 161 | return arbitraryUint120s[counter++]; 162 | } 163 | 164 | mapping (uint8 => uint128) private arbitraryUint128s; 165 | 166 | /** nondeterministic uint128 value */ 167 | function arbitraryUint128() internal returns(uint128) { 168 | return arbitraryUint128s[counter++]; 169 | } 170 | 171 | mapping (uint8 => uint136) private arbitraryUint136s; 172 | 173 | /** nondeterministic uint136 value */ 174 | function arbitraryUint136() internal returns(uint136) { 175 | return arbitraryUint136s[counter++]; 176 | } 177 | 178 | mapping (uint8 => uint144) private arbitraryUint144s; 179 | 180 | /** nondeterministic uint144 value */ 181 | function arbitraryUint144() internal returns(uint144) { 182 | return arbitraryUint144s[counter++]; 183 | } 184 | 185 | mapping (uint8 => uint152) private arbitraryUint152s; 186 | 187 | /** nondeterministic uint152 value */ 188 | function arbitraryUint152() internal returns(uint152) { 189 | return arbitraryUint152s[counter++]; 190 | } 191 | 192 | mapping (uint8 => uint160) private arbitraryUint160s; 193 | 194 | /** nondeterministic uint160 value */ 195 | function arbitraryUint160() internal returns(uint160) { 196 | return arbitraryUint160s[counter++]; 197 | } 198 | 199 | mapping (uint8 => uint168) private arbitraryUint168s; 200 | 201 | /** nondeterministic uint168 value */ 202 | function arbitraryUint168() internal returns(uint168) { 203 | return arbitraryUint168s[counter++]; 204 | } 205 | 206 | mapping (uint8 => uint176) private arbitraryUint176s; 207 | 208 | /** nondeterministic uint176 value */ 209 | function arbitraryUint176() internal returns(uint176) { 210 | return arbitraryUint176s[counter++]; 211 | } 212 | 213 | mapping (uint8 => uint184) private arbitraryUint184s; 214 | 215 | /** nondeterministic uint184 value */ 216 | function arbitraryUint184() internal returns(uint184) { 217 | return arbitraryUint184s[counter++]; 218 | } 219 | 220 | mapping (uint8 => uint192) private arbitraryUint192s; 221 | 222 | /** nondeterministic uint192 value */ 223 | function arbitraryUint192() internal returns(uint192) { 224 | return arbitraryUint192s[counter++]; 225 | } 226 | 227 | mapping (uint8 => uint200) private arbitraryUint200s; 228 | 229 | /** nondeterministic uint200 value */ 230 | function arbitraryUint200() internal returns(uint200) { 231 | return arbitraryUint200s[counter++]; 232 | } 233 | 234 | mapping (uint8 => uint208) private arbitraryUint208s; 235 | 236 | /** nondeterministic uint208 value */ 237 | function arbitraryUint208() internal returns(uint208) { 238 | return arbitraryUint208s[counter++]; 239 | } 240 | 241 | mapping (uint8 => uint216) private arbitraryUint216s; 242 | 243 | /** nondeterministic uint216 value */ 244 | function arbitraryUint216() internal returns(uint216) { 245 | return arbitraryUint216s[counter++]; 246 | } 247 | 248 | mapping (uint8 => uint224) private arbitraryUint224s; 249 | 250 | /** nondeterministic uint224 value */ 251 | function arbitraryUint224() internal returns(uint224) { 252 | return arbitraryUint224s[counter++]; 253 | } 254 | 255 | mapping (uint8 => uint232) private arbitraryUint232s; 256 | 257 | /** nondeterministic uint232 value */ 258 | function arbitraryUint232() internal returns(uint232) { 259 | return arbitraryUint232s[counter++]; 260 | } 261 | 262 | mapping (uint8 => uint240) private arbitraryUint240s; 263 | 264 | /** nondeterministic uint240 value */ 265 | function arbitraryUint240() internal returns(uint240) { 266 | return arbitraryUint240s[counter++]; 267 | } 268 | 269 | mapping (uint8 => uint248) private arbitraryUint248s; 270 | 271 | /** nondeterministic uint248 value */ 272 | function arbitraryUint248() internal returns(uint248) { 273 | return arbitraryUint248s[counter++]; 274 | } 275 | 276 | mapping (uint8 => uint256) private arbitraryUint256s; 277 | 278 | /** nondeterministic uint256 value */ 279 | function arbitraryUint256() internal returns(uint256) { 280 | return arbitraryUint256s[counter++]; 281 | } 282 | 283 | 284 | mapping (uint8 => int8) private arbitraryInt8s; 285 | 286 | /** nondeterministic int8 value */ 287 | function arbitraryInt8() internal returns(int8) { 288 | return arbitraryInt8s[counter++]; 289 | } 290 | 291 | mapping (uint8 => int16) private arbitraryInt16s; 292 | 293 | /** nondeterministic int16 value */ 294 | function arbitraryInt16() internal returns(int16) { 295 | return arbitraryInt16s[counter++]; 296 | } 297 | 298 | mapping (uint8 => int24) private arbitraryInt24s; 299 | 300 | /** nondeterministic int24 value */ 301 | function arbitraryInt24() internal returns(int24) { 302 | return arbitraryInt24s[counter++]; 303 | } 304 | 305 | mapping (uint8 => int32) private arbitraryInt32s; 306 | 307 | /** nondeterministic int32 value */ 308 | function arbitraryInt32() internal returns(int32) { 309 | return arbitraryInt32s[counter++]; 310 | } 311 | 312 | mapping (uint8 => int40) private arbitraryInt40s; 313 | 314 | /** nondeterministic int40 value */ 315 | function arbitraryInt40() internal returns(int40) { 316 | return arbitraryInt40s[counter++]; 317 | } 318 | 319 | mapping (uint8 => int48) private arbitraryInt48s; 320 | 321 | /** nondeterministic int48 value */ 322 | function arbitraryInt48() internal returns(int48) { 323 | return arbitraryInt48s[counter++]; 324 | } 325 | 326 | mapping (uint8 => int56) private arbitraryInt56s; 327 | 328 | /** nondeterministic int56 value */ 329 | function arbitraryInt56() internal returns(int56) { 330 | return arbitraryInt56s[counter++]; 331 | } 332 | 333 | mapping (uint8 => int64) private arbitraryInt64s; 334 | 335 | /** nondeterministic int64 value */ 336 | function arbitraryInt64() internal returns(int64) { 337 | return arbitraryInt64s[counter++]; 338 | } 339 | 340 | mapping (uint8 => int72) private arbitraryInt72s; 341 | 342 | /** nondeterministic int72 value */ 343 | function arbitraryInt72() internal returns(int72) { 344 | return arbitraryInt72s[counter++]; 345 | } 346 | 347 | mapping (uint8 => int80) private arbitraryInt80s; 348 | 349 | /** nondeterministic int80 value */ 350 | function arbitraryInt80() internal returns(int80) { 351 | return arbitraryInt80s[counter++]; 352 | } 353 | 354 | mapping (uint8 => int88) private arbitraryInt88s; 355 | 356 | /** nondeterministic int88 value */ 357 | function arbitraryInt88() internal returns(int88) { 358 | return arbitraryInt88s[counter++]; 359 | } 360 | 361 | mapping (uint8 => int96) private arbitraryInt96s; 362 | 363 | /** nondeterministic int96 value */ 364 | function arbitraryInt96() internal returns(int96) { 365 | return arbitraryInt96s[counter++]; 366 | } 367 | 368 | mapping (uint8 => int104) private arbitraryInt104s; 369 | 370 | /** nondeterministic int104 value */ 371 | function arbitraryInt104() internal returns(int104) { 372 | return arbitraryInt104s[counter++]; 373 | } 374 | 375 | mapping (uint8 => int112) private arbitraryInt112s; 376 | 377 | /** nondeterministic int112 value */ 378 | function arbitraryInt112() internal returns(int112) { 379 | return arbitraryInt112s[counter++]; 380 | } 381 | 382 | mapping (uint8 => int120) private arbitraryInt120s; 383 | 384 | /** nondeterministic int120 value */ 385 | function arbitraryInt120() internal returns(int120) { 386 | return arbitraryInt120s[counter++]; 387 | } 388 | 389 | mapping (uint8 => int128) private arbitraryInt128s; 390 | 391 | /** nondeterministic int128 value */ 392 | function arbitraryInt128() internal returns(int128) { 393 | return arbitraryInt128s[counter++]; 394 | } 395 | 396 | mapping (uint8 => int136) private arbitraryInt136s; 397 | 398 | /** nondeterministic int136 value */ 399 | function arbitraryInt136() internal returns(int136) { 400 | return arbitraryInt136s[counter++]; 401 | } 402 | 403 | mapping (uint8 => int144) private arbitraryInt144s; 404 | 405 | /** nondeterministic int144 value */ 406 | function arbitraryInt144() internal returns(int144) { 407 | return arbitraryInt144s[counter++]; 408 | } 409 | 410 | mapping (uint8 => int152) private arbitraryInt152s; 411 | 412 | /** nondeterministic int152 value */ 413 | function arbitraryInt152() internal returns(int152) { 414 | return arbitraryInt152s[counter++]; 415 | } 416 | 417 | mapping (uint8 => int160) private arbitraryInt160s; 418 | 419 | /** nondeterministic int160 value */ 420 | function arbitraryInt160() internal returns(int160) { 421 | return arbitraryInt160s[counter++]; 422 | } 423 | 424 | mapping (uint8 => int168) private arbitraryInt168s; 425 | 426 | /** nondeterministic int168 value */ 427 | function arbitraryInt168() internal returns(int168) { 428 | return arbitraryInt168s[counter++]; 429 | } 430 | 431 | mapping (uint8 => int176) private arbitraryInt176s; 432 | 433 | /** nondeterministic int176 value */ 434 | function arbitraryInt176() internal returns(int176) { 435 | return arbitraryInt176s[counter++]; 436 | } 437 | 438 | mapping (uint8 => int184) private arbitraryInt184s; 439 | 440 | /** nondeterministic int184 value */ 441 | function arbitraryInt184() internal returns(int184) { 442 | return arbitraryInt184s[counter++]; 443 | } 444 | 445 | mapping (uint8 => int192) private arbitraryInt192s; 446 | 447 | /** nondeterministic int192 value */ 448 | function arbitraryInt192() internal returns(int192) { 449 | return arbitraryInt192s[counter++]; 450 | } 451 | 452 | mapping (uint8 => int200) private arbitraryInt200s; 453 | 454 | /** nondeterministic int200 value */ 455 | function arbitraryInt200() internal returns(int200) { 456 | return arbitraryInt200s[counter++]; 457 | } 458 | 459 | mapping (uint8 => int208) private arbitraryInt208s; 460 | 461 | /** nondeterministic int208 value */ 462 | function arbitraryInt208() internal returns(int208) { 463 | return arbitraryInt208s[counter++]; 464 | } 465 | 466 | mapping (uint8 => int216) private arbitraryInt216s; 467 | 468 | /** nondeterministic int216 value */ 469 | function arbitraryInt216() internal returns(int216) { 470 | return arbitraryInt216s[counter++]; 471 | } 472 | 473 | mapping (uint8 => int224) private arbitraryInt224s; 474 | 475 | /** nondeterministic int224 value */ 476 | function arbitraryInt224() internal returns(int224) { 477 | return arbitraryInt224s[counter++]; 478 | } 479 | 480 | mapping (uint8 => int232) private arbitraryInt232s; 481 | 482 | /** nondeterministic int232 value */ 483 | function arbitraryInt232() internal returns(int232) { 484 | return arbitraryInt232s[counter++]; 485 | } 486 | 487 | mapping (uint8 => int240) private arbitraryInt240s; 488 | 489 | /** nondeterministic int240 value */ 490 | function arbitraryInt240() internal returns(int240) { 491 | return arbitraryInt240s[counter++]; 492 | } 493 | 494 | mapping (uint8 => int248) private arbitraryInt248s; 495 | 496 | /** nondeterministic int248 value */ 497 | function arbitraryInt248() internal returns(int248) { 498 | return arbitraryInt248s[counter++]; 499 | } 500 | 501 | mapping (uint8 => int256) private arbitraryInt256s; 502 | 503 | /** nondeterministic int256 value */ 504 | function arbitraryInt256() internal returns(int256) { 505 | return arbitraryInt256s[counter++]; 506 | } 507 | 508 | 509 | mapping (uint8 => bytes1) private arbitraryBytes1s; 510 | 511 | /** nondeterministic bytes1 value */ 512 | function arbitraryBytes1() internal returns(bytes1) { 513 | return arbitraryBytes1s[counter++]; 514 | } 515 | 516 | mapping (uint8 => bytes2) private arbitraryBytes2s; 517 | 518 | /** nondeterministic bytes2 value */ 519 | function arbitraryBytes2() internal returns(bytes2) { 520 | return arbitraryBytes2s[counter++]; 521 | } 522 | 523 | mapping (uint8 => bytes3) private arbitraryBytes3s; 524 | 525 | /** nondeterministic bytes3 value */ 526 | function arbitraryBytes3() internal returns(bytes3) { 527 | return arbitraryBytes3s[counter++]; 528 | } 529 | 530 | mapping (uint8 => bytes4) private arbitraryBytes4s; 531 | 532 | /** nondeterministic bytes4 value */ 533 | function arbitraryBytes4() internal returns(bytes4) { 534 | return arbitraryBytes4s[counter++]; 535 | } 536 | 537 | mapping (uint8 => bytes5) private arbitraryBytes5s; 538 | 539 | /** nondeterministic bytes5 value */ 540 | function arbitraryBytes5() internal returns(bytes5) { 541 | return arbitraryBytes5s[counter++]; 542 | } 543 | 544 | mapping (uint8 => bytes6) private arbitraryBytes6s; 545 | 546 | /** nondeterministic bytes6 value */ 547 | function arbitraryBytes6() internal returns(bytes6) { 548 | return arbitraryBytes6s[counter++]; 549 | } 550 | 551 | mapping (uint8 => bytes7) private arbitraryBytes7s; 552 | 553 | /** nondeterministic bytes7 value */ 554 | function arbitraryBytes7() internal returns(bytes7) { 555 | return arbitraryBytes7s[counter++]; 556 | } 557 | 558 | mapping (uint8 => bytes8) private arbitraryBytes8s; 559 | 560 | /** nondeterministic bytes8 value */ 561 | function arbitraryBytes8() internal returns(bytes8) { 562 | return arbitraryBytes8s[counter++]; 563 | } 564 | 565 | mapping (uint8 => bytes9) private arbitraryBytes9s; 566 | 567 | /** nondeterministic bytes9 value */ 568 | function arbitraryBytes9() internal returns(bytes9) { 569 | return arbitraryBytes9s[counter++]; 570 | } 571 | 572 | mapping (uint8 => bytes10) private arbitraryBytes10s; 573 | 574 | /** nondeterministic bytes10 value */ 575 | function arbitraryBytes10() internal returns(bytes10) { 576 | return arbitraryBytes10s[counter++]; 577 | } 578 | 579 | mapping (uint8 => bytes11) private arbitraryBytes11s; 580 | 581 | /** nondeterministic bytes11 value */ 582 | function arbitraryBytes11() internal returns(bytes11) { 583 | return arbitraryBytes11s[counter++]; 584 | } 585 | 586 | mapping (uint8 => bytes12) private arbitraryBytes12s; 587 | 588 | /** nondeterministic bytes12 value */ 589 | function arbitraryBytes12() internal returns(bytes12) { 590 | return arbitraryBytes12s[counter++]; 591 | } 592 | 593 | mapping (uint8 => bytes13) private arbitraryBytes13s; 594 | 595 | /** nondeterministic bytes13 value */ 596 | function arbitraryBytes13() internal returns(bytes13) { 597 | return arbitraryBytes13s[counter++]; 598 | } 599 | 600 | mapping (uint8 => bytes14) private arbitraryBytes14s; 601 | 602 | /** nondeterministic bytes14 value */ 603 | function arbitraryBytes14() internal returns(bytes14) { 604 | return arbitraryBytes14s[counter++]; 605 | } 606 | 607 | mapping (uint8 => bytes15) private arbitraryBytes15s; 608 | 609 | /** nondeterministic bytes15 value */ 610 | function arbitraryBytes15() internal returns(bytes15) { 611 | return arbitraryBytes15s[counter++]; 612 | } 613 | 614 | mapping (uint8 => bytes16) private arbitraryBytes16s; 615 | 616 | /** nondeterministic bytes16 value */ 617 | function arbitraryBytes16() internal returns(bytes16) { 618 | return arbitraryBytes16s[counter++]; 619 | } 620 | 621 | mapping (uint8 => bytes17) private arbitraryBytes17s; 622 | 623 | /** nondeterministic bytes17 value */ 624 | function arbitraryBytes17() internal returns(bytes17) { 625 | return arbitraryBytes17s[counter++]; 626 | } 627 | 628 | mapping (uint8 => bytes18) private arbitraryBytes18s; 629 | 630 | /** nondeterministic bytes18 value */ 631 | function arbitraryBytes18() internal returns(bytes18) { 632 | return arbitraryBytes18s[counter++]; 633 | } 634 | 635 | mapping (uint8 => bytes19) private arbitraryBytes19s; 636 | 637 | /** nondeterministic bytes19 value */ 638 | function arbitraryBytes19() internal returns(bytes19) { 639 | return arbitraryBytes19s[counter++]; 640 | } 641 | 642 | mapping (uint8 => bytes20) private arbitraryBytes20s; 643 | 644 | /** nondeterministic bytes20 value */ 645 | function arbitraryBytes20() internal returns(bytes20) { 646 | return arbitraryBytes20s[counter++]; 647 | } 648 | 649 | mapping (uint8 => bytes21) private arbitraryBytes21s; 650 | 651 | /** nondeterministic bytes21 value */ 652 | function arbitraryBytes21() internal returns(bytes21) { 653 | return arbitraryBytes21s[counter++]; 654 | } 655 | 656 | mapping (uint8 => bytes22) private arbitraryBytes22s; 657 | 658 | /** nondeterministic bytes22 value */ 659 | function arbitraryBytes22() internal returns(bytes22) { 660 | return arbitraryBytes22s[counter++]; 661 | } 662 | 663 | mapping (uint8 => bytes23) private arbitraryBytes23s; 664 | 665 | /** nondeterministic bytes23 value */ 666 | function arbitraryBytes23() internal returns(bytes23) { 667 | return arbitraryBytes23s[counter++]; 668 | } 669 | 670 | mapping (uint8 => bytes24) private arbitraryBytes24s; 671 | 672 | /** nondeterministic bytes24 value */ 673 | function arbitraryBytes24() internal returns(bytes24) { 674 | return arbitraryBytes24s[counter++]; 675 | } 676 | 677 | mapping (uint8 => bytes25) private arbitraryBytes25s; 678 | 679 | /** nondeterministic bytes25 value */ 680 | function arbitraryBytes25() internal returns(bytes25) { 681 | return arbitraryBytes25s[counter++]; 682 | } 683 | 684 | mapping (uint8 => bytes26) private arbitraryBytes26s; 685 | 686 | /** nondeterministic bytes26 value */ 687 | function arbitraryBytes26() internal returns(bytes26) { 688 | return arbitraryBytes26s[counter++]; 689 | } 690 | 691 | mapping (uint8 => bytes27) private arbitraryBytes27s; 692 | 693 | /** nondeterministic bytes27 value */ 694 | function arbitraryBytes27() internal returns(bytes27) { 695 | return arbitraryBytes27s[counter++]; 696 | } 697 | 698 | mapping (uint8 => bytes28) private arbitraryBytes28s; 699 | 700 | /** nondeterministic bytes28 value */ 701 | function arbitraryBytes28() internal returns(bytes28) { 702 | return arbitraryBytes28s[counter++]; 703 | } 704 | 705 | mapping (uint8 => bytes29) private arbitraryBytes29s; 706 | 707 | /** nondeterministic bytes29 value */ 708 | function arbitraryBytes29() internal returns(bytes29) { 709 | return arbitraryBytes29s[counter++]; 710 | } 711 | 712 | mapping (uint8 => bytes30) private arbitraryBytes30s; 713 | 714 | /** nondeterministic bytes30 value */ 715 | function arbitraryBytes30() internal returns(bytes30) { 716 | return arbitraryBytes30s[counter++]; 717 | } 718 | 719 | mapping (uint8 => bytes31) private arbitraryBytes31s; 720 | 721 | /** nondeterministic bytes31 value */ 722 | function arbitraryBytes31() internal returns(bytes31) { 723 | return arbitraryBytes31s[counter++]; 724 | } 725 | 726 | mapping (uint8 => bytes32) private arbitraryBytes32s; 727 | 728 | /** nondeterministic bytes32 value */ 729 | function arbitraryBytes32() internal returns(bytes32) { 730 | return arbitraryBytes32s[counter++]; 731 | } 732 | } 733 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/erc20.spec: -------------------------------------------------------------------------------- 1 | /*** 2 | * This spec file adds `DISPATCHER` summaries for the methods in the ERC20 3 | * specification. It is useful for writing specifications that work with 4 | * arbitrary ERC20 contracts; if using it, you should add a variety of ERC20 5 | * implementations to the scene. 6 | * 7 | * See [Using DISPATCHER for ERC20 contracts][guide] in the user guide for more 8 | * information. 9 | * 10 | * [guide]: https://docs.certora.com/en/latest/docs/user-guide/multicontract/index.html#using-dispatcher-for-erc20-contracts 11 | */ 12 | 13 | methods { 14 | function _.name() external => DISPATCHER(true); 15 | function _.symbol() external => DISPATCHER(true); 16 | function _.decimals() external => DISPATCHER(true); 17 | function _.totalSupply() external => DISPATCHER(true); 18 | function _.balanceOf(address) external => DISPATCHER(true); 19 | function _.allowance(address,address) external => DISPATCHER(true); 20 | function _.approve(address,uint256) external => DISPATCHER(true); 21 | function _.transfer(address,uint256) external => DISPATCHER(true); 22 | function _.transferFrom(address,address,uint256) external => DISPATCHER(true); 23 | } 24 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/DummyERC20A.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity >= 0.8.0; 3 | import "./DummyERC20Impl.sol"; 4 | 5 | contract DummyERC20A is DummyERC20Impl {} 6 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/DummyERC20B.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity >= 0.8.0; 3 | import "./DummyERC20Impl.sol"; 4 | 5 | contract DummyERC20B is DummyERC20Impl {} 6 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/DummyERC20Impl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity >= 0.8.0; 3 | 4 | // with mint 5 | contract DummyERC20Impl { 6 | uint256 t; 7 | mapping (address => uint256) b; 8 | mapping (address => mapping (address => uint256)) a; 9 | 10 | string public name; 11 | string public symbol; 12 | uint public decimals; 13 | 14 | function myAddress() public returns (address) { 15 | return address(this); 16 | } 17 | 18 | function add(uint a, uint b) internal pure returns (uint256) { 19 | uint c = a +b; 20 | require (c >= a); 21 | return c; 22 | } 23 | function sub(uint a, uint b) internal pure returns (uint256) { 24 | require (a>=b); 25 | return a-b; 26 | } 27 | 28 | function totalSupply() external view returns (uint256) { 29 | return t; 30 | } 31 | function balanceOf(address account) external view returns (uint256) { 32 | return b[account]; 33 | } 34 | function transfer(address recipient, uint256 amount) external returns (bool) { 35 | b[msg.sender] = sub(b[msg.sender], amount); 36 | b[recipient] = add(b[recipient], amount); 37 | return true; 38 | } 39 | function allowance(address owner, address spender) external view returns (uint256) { 40 | return a[owner][spender]; 41 | } 42 | function approve(address spender, uint256 amount) external returns (bool) { 43 | a[msg.sender][spender] = amount; 44 | return true; 45 | } 46 | 47 | function transferFrom( 48 | address sender, 49 | address recipient, 50 | uint256 amount 51 | ) external returns (bool) { 52 | b[sender] = sub(b[sender], amount); 53 | b[recipient] = add(b[recipient], amount); 54 | a[sender][msg.sender] = sub(a[sender][msg.sender], amount); 55 | return true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/DummyWeth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity >=0.8.0; 3 | 4 | /** 5 | * Dummy Weth token. 6 | */ 7 | contract DummyWeth { 8 | uint256 t; 9 | 10 | mapping(address => uint256) b; 11 | mapping(address => mapping(address => uint256)) a; 12 | 13 | string public name; 14 | string public symbol; 15 | uint public decimals; 16 | 17 | function myAddress() public returns (address) { 18 | return address(this); 19 | } 20 | 21 | function add(uint a, uint b) internal pure returns (uint256) { 22 | uint c = a + b; 23 | require (c >= a); 24 | return c; 25 | } 26 | function sub(uint a, uint b) internal pure returns (uint256) { 27 | require (a >= b); 28 | return a - b; 29 | } 30 | 31 | function totalSupply() external view returns (uint256) { 32 | return t; 33 | } 34 | 35 | function balanceOf(address account) external view returns (uint256) { 36 | return b[account]; 37 | } 38 | 39 | function transfer(address recipient, uint256 amount) external returns (bool) { 40 | b[msg.sender] = sub(b[msg.sender], amount); 41 | b[recipient] = add(b[recipient], amount); 42 | return true; 43 | } 44 | 45 | function allowance(address owner, address spender) external view returns (uint256) { 46 | return a[owner][spender]; 47 | } 48 | 49 | function approve(address spender, uint256 amount) external returns (bool) { 50 | a[msg.sender][spender] = amount; 51 | return true; 52 | } 53 | 54 | function transferFrom( 55 | address sender, 56 | address recipient, 57 | uint256 amount 58 | ) external returns (bool) { 59 | b[sender] = sub(b[sender], amount); 60 | b[recipient] = add(b[recipient], amount); 61 | a[sender][msg.sender] = sub(a[sender][msg.sender], amount); 62 | return true; 63 | } 64 | 65 | // WETH 66 | function deposit() external payable { 67 | // assume succeeds 68 | } 69 | 70 | function withdraw(uint256) external { 71 | // assume succeeds 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/ERC20Basic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.8.0; 2 | 3 | interface IERC20 { 4 | 5 | function totalSupply() external view returns (uint256); 6 | function balanceOf(address account) external view returns (uint256); 7 | function allowance(address owner, address spender) external view returns (uint256); 8 | 9 | function transfer(address recipient, uint256 amount) external returns (bool); 10 | function approve(address spender, uint256 amount) external returns (bool); 11 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 12 | 13 | 14 | event Transfer(address indexed from, address indexed to, uint256 value); 15 | event Approval(address indexed owner, address indexed spender, uint256 value); 16 | } 17 | 18 | 19 | contract ERC20Basic is IERC20 { 20 | 21 | string public constant name = "ERC20Basic"; 22 | string public constant symbol = "ERC"; 23 | uint8 public constant decimals = 18; 24 | address private _owner; 25 | 26 | 27 | mapping(address => uint256) balances; 28 | 29 | mapping(address => mapping (address => uint256)) allowed; 30 | 31 | uint256 totalSupply_ = 10 ether; 32 | 33 | 34 | constructor() { 35 | require(msg.sender != address(0)); 36 | balances[msg.sender] = totalSupply_; 37 | _owner = msg.sender; 38 | } 39 | 40 | function totalSupply() public override view returns (uint256) { 41 | return totalSupply_; 42 | } 43 | 44 | function balanceOf(address tokenOwner) public override view returns (uint256) { 45 | return balances[tokenOwner]; 46 | } 47 | 48 | function transfer(address receiver, uint256 numTokens) public override returns (bool) { 49 | require(receiver != address(0)); 50 | require(numTokens <= balances[msg.sender]); 51 | balances[msg.sender] = balances[msg.sender]-numTokens; 52 | balances[receiver] = balances[receiver]+numTokens; 53 | emit Transfer(msg.sender, receiver, numTokens); 54 | return true; 55 | } 56 | 57 | function approve(address delegate, uint256 numTokens) public override returns (bool) { 58 | allowed[msg.sender][delegate] = numTokens; 59 | emit Approval(msg.sender, delegate, numTokens); 60 | return true; 61 | } 62 | 63 | function allowance(address owner, address delegate) public override view returns (uint) { 64 | return allowed[owner][delegate]; 65 | } 66 | 67 | function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) { 68 | require(buyer != address(0)); 69 | require(numTokens <= balances[owner]); 70 | require(numTokens <= allowed[owner][msg.sender]); 71 | 72 | balances[owner] = balances[owner]-numTokens; 73 | allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens; 74 | balances[buyer] = balances[buyer]+numTokens; 75 | emit Transfer(owner, buyer, numTokens); 76 | return true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/FTT.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2019-04-21 3 | */ 4 | 5 | // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol 6 | 7 | pragma solidity >= 0.8.0; 8 | 9 | /** 10 | * @title ERC20 interface 11 | * @dev see https://eips.ethereum.org/EIPS/eip-20 12 | */ 13 | interface IERC20 { 14 | function transfer(address to, uint256 value) external returns (bool); 15 | 16 | function approve(address spender, uint256 value) external returns (bool); 17 | 18 | function transferFrom(address from, address to, uint256 value) external returns (bool); 19 | 20 | function totalSupply() external view returns (uint256); 21 | 22 | function balanceOf(address who) external view returns (uint256); 23 | 24 | function allowance(address owner, address spender) external view returns (uint256); 25 | 26 | event Transfer(address indexed from, address indexed to, uint256 value); 27 | 28 | event Approval(address indexed owner, address indexed spender, uint256 value); 29 | } 30 | 31 | // File: openzeppelin-solidity/contracts/math/SafeMath.sol 32 | 33 | pragma solidity >= 0.8.0; 34 | 35 | /** 36 | * @title SafeMath 37 | * @dev Unsigned math operations with safety checks that revert on error 38 | */ 39 | library SafeMath { 40 | /** 41 | * @dev Multiplies two unsigned integers, reverts on overflow. 42 | */ 43 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 44 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 45 | // benefit is lost if 'b' is also tested. 46 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 47 | if (a == 0) { 48 | return 0; 49 | } 50 | 51 | uint256 c = a * b; 52 | require(c / a == b); 53 | 54 | return c; 55 | } 56 | 57 | /** 58 | * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. 59 | */ 60 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 61 | // Solidity only automatically asserts when dividing by 0 62 | require(b > 0); 63 | uint256 c = a / b; 64 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 65 | 66 | return c; 67 | } 68 | 69 | /** 70 | * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). 71 | */ 72 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 73 | require(b <= a); 74 | uint256 c = a - b; 75 | 76 | return c; 77 | } 78 | 79 | /** 80 | * @dev Adds two unsigned integers, reverts on overflow. 81 | */ 82 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 83 | uint256 c = a + b; 84 | require(c >= a); 85 | 86 | return c; 87 | } 88 | 89 | /** 90 | * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), 91 | * reverts when dividing by zero. 92 | */ 93 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 94 | require(b != 0); 95 | return a % b; 96 | } 97 | } 98 | 99 | // File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol 100 | 101 | pragma solidity >= 0.8.0; 102 | 103 | /** 104 | * @title Standard ERC20 token 105 | * 106 | * @dev Implementation of the basic standard token. 107 | * https://eips.ethereum.org/EIPS/eip-20 108 | * 109 | * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for 110 | * all accounts just by listening to said events. Note that this isn't required by the specification, and other 111 | * compliant implementations may not do it. 112 | */ 113 | contract ERC20 is IERC20 { 114 | using SafeMath for uint256; 115 | 116 | mapping (address => uint256) private _balances; 117 | 118 | mapping (address => mapping (address => uint256)) private _allowed; 119 | 120 | uint256 private _totalSupply; 121 | 122 | /** 123 | * @dev Total number of tokens in existence 124 | */ 125 | function totalSupply() public view override(IERC20) returns (uint256) { 126 | return _totalSupply; 127 | } 128 | 129 | /** 130 | * @dev Gets the balance of the specified address. 131 | * @param owner The address to query the balance of. 132 | * @return A uint256 representing the amount owned by the passed address. 133 | */ 134 | function balanceOf(address owner) public view override(IERC20) returns (uint256) { 135 | return _balances[owner]; 136 | } 137 | 138 | /** 139 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 140 | * @param owner address The address which owns the funds. 141 | * @param spender address The address which will spend the funds. 142 | * @return A uint256 specifying the amount of tokens still available for the spender. 143 | */ 144 | function allowance(address owner, address spender) public view override(IERC20) returns (uint256) { 145 | return _allowed[owner][spender]; 146 | } 147 | 148 | /** 149 | * @dev Transfer token to a specified address 150 | * @param to The address to transfer to. 151 | * @param value The amount to be transferred. 152 | */ 153 | function transfer(address to, uint256 value) public override(IERC20) returns (bool) { 154 | _transfer(msg.sender, to, value); 155 | return true; 156 | } 157 | 158 | /** 159 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 160 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 161 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 162 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 163 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 164 | * @param spender The address which will spend the funds. 165 | * @param value The amount of tokens to be spent. 166 | */ 167 | function approve(address spender, uint256 value) public override(IERC20) returns (bool) { 168 | _approve(msg.sender, spender, value); 169 | return true; 170 | } 171 | 172 | /** 173 | * @dev Transfer tokens from one address to another. 174 | * Note that while this function emits an Approval event, this is not required as per the specification, 175 | * and other compliant implementations may not emit the event. 176 | * @param from address The address which you want to send tokens from 177 | * @param to address The address which you want to transfer to 178 | * @param value uint256 the amount of tokens to be transferred 179 | */ 180 | function transferFrom(address from, address to, uint256 value) public override(IERC20) returns (bool) { 181 | _transfer(from, to, value); 182 | _approve(from, msg.sender, _allowed[from][msg.sender].sub(value)); 183 | return true; 184 | } 185 | 186 | /** 187 | * @dev Increase the amount of tokens that an owner allowed to a spender. 188 | * approve should be called when _allowed[msg.sender][spender] == 0. To increment 189 | * allowed value is better to use this function to avoid 2 calls (and wait until 190 | * the first transaction is mined) 191 | * From MonolithDAO Token.sol 192 | * Emits an Approval event. 193 | * @param spender The address which will spend the funds. 194 | * @param addedValue The amount of tokens to increase the allowance by. 195 | */ 196 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 197 | _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue)); 198 | return true; 199 | } 200 | 201 | /** 202 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 203 | * approve should be called when _allowed[msg.sender][spender] == 0. To decrement 204 | * allowed value is better to use this function to avoid 2 calls (and wait until 205 | * the first transaction is mined) 206 | * From MonolithDAO Token.sol 207 | * Emits an Approval event. 208 | * @param spender The address which will spend the funds. 209 | * @param subtractedValue The amount of tokens to decrease the allowance by. 210 | */ 211 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 212 | _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue)); 213 | return true; 214 | } 215 | 216 | /** 217 | * @dev Transfer token for a specified addresses 218 | * @param from The address to transfer from. 219 | * @param to The address to transfer to. 220 | * @param value The amount to be transferred. 221 | */ 222 | function _transfer(address from, address to, uint256 value) internal { 223 | require(to != address(0)); 224 | 225 | _balances[from] = _balances[from].sub(value); 226 | _balances[to] = _balances[to].add(value); 227 | emit Transfer(from, to, value); 228 | } 229 | 230 | /** 231 | * @dev Internal function that mints an amount of the token and assigns it to 232 | * an account. This encapsulates the modification of balances such that the 233 | * proper events are emitted. 234 | * @param account The account that will receive the created tokens. 235 | * @param value The amount that will be created. 236 | */ 237 | function _mint(address account, uint256 value) internal { 238 | require(account != address(0)); 239 | 240 | _totalSupply = _totalSupply.add(value); 241 | _balances[account] = _balances[account].add(value); 242 | emit Transfer(address(0), account, value); 243 | } 244 | 245 | /** 246 | * @dev Internal function that burns an amount of the token of a given 247 | * account. 248 | * @param account The account whose tokens will be burnt. 249 | * @param value The amount that will be burnt. 250 | */ 251 | function _burn(address account, uint256 value) internal { 252 | require(account != address(0)); 253 | 254 | _totalSupply = _totalSupply.sub(value); 255 | _balances[account] = _balances[account].sub(value); 256 | emit Transfer(account, address(0), value); 257 | } 258 | 259 | /** 260 | * @dev Approve an address to spend another addresses' tokens. 261 | * @param owner The address that owns the tokens. 262 | * @param spender The address that will spend the tokens. 263 | * @param value The number of tokens that can be spent. 264 | */ 265 | function _approve(address owner, address spender, uint256 value) internal { 266 | require(spender != address(0)); 267 | require(owner != address(0)); 268 | 269 | _allowed[owner][spender] = value; 270 | emit Approval(owner, spender, value); 271 | } 272 | 273 | /** 274 | * @dev Internal function that burns an amount of the token of a given 275 | * account, deducting from the sender's allowance for said account. Uses the 276 | * internal burn function. 277 | * Emits an Approval event (reflecting the reduced allowance). 278 | * @param account The account whose tokens will be burnt. 279 | * @param value The amount that will be burnt. 280 | */ 281 | function _burnFrom(address account, uint256 value) internal { 282 | _burn(account, value); 283 | _approve(account, msg.sender, _allowed[account][msg.sender].sub(value)); 284 | } 285 | } 286 | 287 | // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol 288 | 289 | pragma solidity >= 0.8.0; 290 | 291 | 292 | /** 293 | * @title ERC20Detailed token 294 | * @dev The decimals are only for visualization purposes. 295 | * All the operations are done using the smallest and indivisible token unit, 296 | * just as on Ethereum all the operations are done in wei. 297 | */ 298 | abstract contract ERC20Detailed is IERC20 { 299 | string private _name; 300 | string private _symbol; 301 | uint8 private _decimals; 302 | 303 | constructor (string memory name, string memory symbol, uint8 decimals) IERC20() { 304 | _name = name; 305 | _symbol = symbol; 306 | _decimals = decimals; 307 | } 308 | 309 | /** 310 | * @return the name of the token. 311 | */ 312 | function name() public view returns (string memory) { 313 | return _name; 314 | } 315 | 316 | /** 317 | * @return the symbol of the token. 318 | */ 319 | function symbol() public view returns (string memory) { 320 | return _symbol; 321 | } 322 | 323 | /** 324 | * @return the number of decimals of the token. 325 | */ 326 | function decimals() public view returns (uint8) { 327 | return _decimals; 328 | } 329 | } 330 | 331 | // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Burnable.sol 332 | 333 | pragma solidity >= 0.8.0; 334 | 335 | 336 | /** 337 | * @title Burnable Token 338 | * @dev Token that can be irreversibly burned (destroyed). 339 | */ 340 | contract ERC20Burnable is ERC20 { 341 | /** 342 | * @dev Burns a specific amount of tokens. 343 | * @param value The amount of token to be burned. 344 | */ 345 | function burn(uint256 value) public { 346 | _burn(msg.sender, value); 347 | } 348 | 349 | /** 350 | * @dev Burns a specific amount of tokens from the target address and decrements allowance 351 | * @param from address The account whose tokens will be burned. 352 | * @param value uint256 The amount of token to be burned. 353 | */ 354 | function burnFrom(address from, uint256 value) public { 355 | _burnFrom(from, value); 356 | } 357 | } 358 | 359 | // File: contracts/FTT.sol 360 | 361 | pragma solidity >= 0.8.0; 362 | 363 | contract FTT is ERC20, ERC20Detailed, ERC20Burnable { 364 | constructor() ERC20Detailed('FTT', 'FTX Token', 18) public { 365 | _mint(msg.sender, 350_000_000 * 10 ** 18); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/SushiToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2020-08-26 3 | */ 4 | 5 | // File: @openzeppelin/contracts/GSN/Context.sol 6 | 7 | 8 | 9 | pragma solidity >= 0.8.0; 10 | 11 | /* 12 | * @dev Provides information about the current execution context, including the 13 | * sender of the transaction and its data. While these are generally available 14 | * via msg.sender and msg.data, they should not be accessed in such a direct 15 | * manner, since when dealing with GSN meta-transactions the account sending and 16 | * paying for execution may not be the actual sender (as far as an application 17 | * is concerned). 18 | * 19 | * This contract is only required for intermediate, library-like contracts. 20 | */ 21 | abstract contract Context { 22 | function _msgSender() internal view virtual returns (address payable) { 23 | return payable(msg.sender); 24 | } 25 | 26 | function _msgData() internal view virtual returns (bytes memory) { 27 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 28 | return msg.data; 29 | } 30 | } 31 | 32 | // File: @openzeppelin/contracts/token/ERC20/IERC20.sol 33 | 34 | 35 | 36 | pragma solidity >= 0.8.0; 37 | 38 | /** 39 | * @dev Interface of the ERC20 standard as defined in the EIP. 40 | */ 41 | interface IERC20 { 42 | /** 43 | * @dev Returns the amount of tokens in existence. 44 | */ 45 | function totalSupply() external view returns (uint256); 46 | 47 | /** 48 | * @dev Returns the amount of tokens owned by `account`. 49 | */ 50 | function balanceOf(address account) external view returns (uint256); 51 | 52 | /** 53 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 54 | * 55 | * Returns a boolean value indicating whether the operation succeeded. 56 | * 57 | * Emits a {Transfer} event. 58 | */ 59 | function transfer(address recipient, uint256 amount) external returns (bool); 60 | 61 | /** 62 | * @dev Returns the remaining number of tokens that `spender` will be 63 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 64 | * zero by default. 65 | * 66 | * This value changes when {approve} or {transferFrom} are called. 67 | */ 68 | function allowance(address owner, address spender) external view returns (uint256); 69 | 70 | /** 71 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 72 | * 73 | * Returns a boolean value indicating whether the operation succeeded. 74 | * 75 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 76 | * that someone may use both the old and the new allowance by unfortunate 77 | * transaction ordering. One possible solution to mitigate this race 78 | * condition is to first reduce the spender's allowance to 0 and set the 79 | * desired value afterwards: 80 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 81 | * 82 | * Emits an {Approval} event. 83 | */ 84 | function approve(address spender, uint256 amount) external returns (bool); 85 | 86 | /** 87 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 88 | * allowance mechanism. `amount` is then deducted from the caller's 89 | * allowance. 90 | * 91 | * Returns a boolean value indicating whether the operation succeeded. 92 | * 93 | * Emits a {Transfer} event. 94 | */ 95 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 96 | 97 | /** 98 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 99 | * another (`to`). 100 | * 101 | * Note that `value` may be zero. 102 | */ 103 | event Transfer(address indexed from, address indexed to, uint256 value); 104 | 105 | /** 106 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 107 | * a call to {approve}. `value` is the new allowance. 108 | */ 109 | event Approval(address indexed owner, address indexed spender, uint256 value); 110 | } 111 | 112 | // File: @openzeppelin/contracts/math/SafeMath.sol 113 | 114 | 115 | 116 | pragma solidity >= 0.8.0; 117 | 118 | /** 119 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 120 | * checks. 121 | * 122 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 123 | * in bugs, because programmers usually assume that an overflow raises an 124 | * error, which is the standard behavior in high level programming languages. 125 | * `SafeMath` restores this intuition by reverting the transaction when an 126 | * operation overflows. 127 | * 128 | * Using this library instead of the unchecked operations eliminates an entire 129 | * class of bugs, so it's recommended to use it always. 130 | */ 131 | library SafeMath { 132 | /** 133 | * @dev Returns the addition of two unsigned integers, reverting on 134 | * overflow. 135 | * 136 | * Counterpart to Solidity's `+` operator. 137 | * 138 | * Requirements: 139 | * 140 | * - Addition cannot overflow. 141 | */ 142 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 143 | uint256 c = a + b; 144 | require(c >= a, "SafeMath: addition overflow"); 145 | 146 | return c; 147 | } 148 | 149 | /** 150 | * @dev Returns the subtraction of two unsigned integers, reverting on 151 | * overflow (when the result is negative). 152 | * 153 | * Counterpart to Solidity's `-` operator. 154 | * 155 | * Requirements: 156 | * 157 | * - Subtraction cannot overflow. 158 | */ 159 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 160 | return sub(a, b, "SafeMath: subtraction overflow"); 161 | } 162 | 163 | /** 164 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 165 | * overflow (when the result is negative). 166 | * 167 | * Counterpart to Solidity's `-` operator. 168 | * 169 | * Requirements: 170 | * 171 | * - Subtraction cannot overflow. 172 | */ 173 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 174 | require(b <= a, errorMessage); 175 | uint256 c = a - b; 176 | 177 | return c; 178 | } 179 | 180 | /** 181 | * @dev Returns the multiplication of two unsigned integers, reverting on 182 | * overflow. 183 | * 184 | * Counterpart to Solidity's `*` operator. 185 | * 186 | * Requirements: 187 | * 188 | * - Multiplication cannot overflow. 189 | */ 190 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 191 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 192 | // benefit is lost if 'b' is also tested. 193 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 194 | if (a == 0) { 195 | return 0; 196 | } 197 | 198 | uint256 c = a * b; 199 | require(c / a == b, "SafeMath: multiplication overflow"); 200 | 201 | return c; 202 | } 203 | 204 | /** 205 | * @dev Returns the integer division of two unsigned integers. Reverts on 206 | * division by zero. The result is rounded towards zero. 207 | * 208 | * Counterpart to Solidity's `/` operator. Note: this function uses a 209 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 210 | * uses an invalid opcode to revert (consuming all remaining gas). 211 | * 212 | * Requirements: 213 | * 214 | * - The divisor cannot be zero. 215 | */ 216 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 217 | return div(a, b, "SafeMath: division by zero"); 218 | } 219 | 220 | /** 221 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 222 | * division by zero. The result is rounded towards zero. 223 | * 224 | * Counterpart to Solidity's `/` operator. Note: this function uses a 225 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 226 | * uses an invalid opcode to revert (consuming all remaining gas). 227 | * 228 | * Requirements: 229 | * 230 | * - The divisor cannot be zero. 231 | */ 232 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 233 | require(b > 0, errorMessage); 234 | uint256 c = a / b; 235 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 236 | 237 | return c; 238 | } 239 | 240 | /** 241 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 242 | * Reverts when dividing by zero. 243 | * 244 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 245 | * opcode (which leaves remaining gas untouched) while Solidity uses an 246 | * invalid opcode to revert (consuming all remaining gas). 247 | * 248 | * Requirements: 249 | * 250 | * - The divisor cannot be zero. 251 | */ 252 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 253 | return mod(a, b, "SafeMath: modulo by zero"); 254 | } 255 | 256 | /** 257 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 258 | * Reverts with custom message when dividing by zero. 259 | * 260 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 261 | * opcode (which leaves remaining gas untouched) while Solidity uses an 262 | * invalid opcode to revert (consuming all remaining gas). 263 | * 264 | * Requirements: 265 | * 266 | * - The divisor cannot be zero. 267 | */ 268 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 269 | require(b != 0, errorMessage); 270 | return a % b; 271 | } 272 | } 273 | 274 | // File: @openzeppelin/contracts/utils/Address.sol 275 | 276 | 277 | 278 | pragma solidity >= 0.8.0; 279 | 280 | /** 281 | * @dev Collection of functions related to the address type 282 | */ 283 | library Address { 284 | /** 285 | * @dev Returns true if `account` is a contract. 286 | * 287 | * [IMPORTANT] 288 | * ==== 289 | * It is unsafe to assume that an address for which this function returns 290 | * false is an externally-owned account (EOA) and not a contract. 291 | * 292 | * Among others, `isContract` will return false for the following 293 | * types of addresses: 294 | * 295 | * - an externally-owned account 296 | * - a contract in construction 297 | * - an address where a contract will be created 298 | * - an address where a contract lived, but was destroyed 299 | * ==== 300 | */ 301 | function isContract(address account) internal view returns (bool) { 302 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts 303 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned 304 | // for accounts without code, i.e. `keccak256('')` 305 | bytes32 codehash; 306 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 307 | // solhint-disable-next-line no-inline-assembly 308 | assembly { codehash := extcodehash(account) } 309 | return (codehash != accountHash && codehash != 0x0); 310 | } 311 | 312 | /** 313 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 314 | * `recipient`, forwarding all available gas and reverting on errors. 315 | * 316 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 317 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 318 | * imposed by `transfer`, making them unable to receive funds via 319 | * `transfer`. {sendValue} removes this limitation. 320 | * 321 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 322 | * 323 | * IMPORTANT: because control is transferred to `recipient`, care must be 324 | * taken to not create reentrancy vulnerabilities. Consider using 325 | * {ReentrancyGuard} or the 326 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 327 | */ 328 | function sendValue(address payable recipient, uint256 amount) internal { 329 | require(address(this).balance >= amount, "Address: insufficient balance"); 330 | 331 | // solhint-disable-next-line avoid-low-level-calls, avoid-call-value 332 | (bool success, ) = recipient.call{ value: amount }(""); 333 | require(success, "Address: unable to send value, recipient may have reverted"); 334 | } 335 | 336 | /** 337 | * @dev Performs a Solidity function call using a low level `call`. A 338 | * plain`call` is an unsafe replacement for a function call: use this 339 | * function instead. 340 | * 341 | * If `target` reverts with a revert reason, it is bubbled up by this 342 | * function (like regular Solidity function calls). 343 | * 344 | * Returns the raw returned data. To convert to the expected return value, 345 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 346 | * 347 | * Requirements: 348 | * 349 | * - `target` must be a contract. 350 | * - calling `target` with `data` must not revert. 351 | * 352 | * _Available since v3.1._ 353 | */ 354 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 355 | return functionCall(target, data, "Address: low-level call failed"); 356 | } 357 | 358 | /** 359 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 360 | * `errorMessage` as a fallback revert reason when `target` reverts. 361 | * 362 | * _Available since v3.1._ 363 | */ 364 | function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { 365 | return _functionCallWithValue(target, data, 0, errorMessage); 366 | } 367 | 368 | /** 369 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 370 | * but also transferring `value` wei to `target`. 371 | * 372 | * Requirements: 373 | * 374 | * - the calling contract must have an ETH balance of at least `value`. 375 | * - the called Solidity function must be `payable`. 376 | * 377 | * _Available since v3.1._ 378 | */ 379 | function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { 380 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 381 | } 382 | 383 | /** 384 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 385 | * with `errorMessage` as a fallback revert reason when `target` reverts. 386 | * 387 | * _Available since v3.1._ 388 | */ 389 | function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { 390 | require(address(this).balance >= value, "Address: insufficient balance for call"); 391 | return _functionCallWithValue(target, data, value, errorMessage); 392 | } 393 | 394 | function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { 395 | require(isContract(target), "Address: call to non-contract"); 396 | 397 | // solhint-disable-next-line avoid-low-level-calls 398 | (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); 399 | if (success) { 400 | return returndata; 401 | } else { 402 | // Look for revert reason and bubble it up if present 403 | if (returndata.length > 0) { 404 | // The easiest way to bubble the revert reason is using memory via assembly 405 | 406 | // solhint-disable-next-line no-inline-assembly 407 | assembly { 408 | let returndata_size := mload(returndata) 409 | revert(add(32, returndata), returndata_size) 410 | } 411 | } else { 412 | revert(errorMessage); 413 | } 414 | } 415 | } 416 | } 417 | 418 | // File: @openzeppelin/contracts/token/ERC20/ERC20.sol 419 | 420 | 421 | 422 | pragma solidity >= 0.8.0; 423 | 424 | 425 | 426 | 427 | 428 | /** 429 | * @dev Implementation of the {IERC20} interface. 430 | * 431 | * This implementation is agnostic to the way tokens are created. This means 432 | * that a supply mechanism has to be added in a derived contract using {_mint}. 433 | * For a generic mechanism see {ERC20PresetMinterPauser}. 434 | * 435 | * TIP: For a detailed writeup see our guide 436 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How 437 | * to implement supply mechanisms]. 438 | * 439 | * We have followed general OpenZeppelin guidelines: functions revert instead 440 | * of returning `false` on failure. This behavior is nonetheless conventional 441 | * and does not conflict with the expectations of ERC20 applications. 442 | * 443 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 444 | * This allows applications to reconstruct the allowance for all accounts just 445 | * by listening to said events. Other implementations of the EIP may not emit 446 | * these events, as it isn't required by the specification. 447 | * 448 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 449 | * functions have been added to mitigate the well-known issues around setting 450 | * allowances. See {IERC20-approve}. 451 | */ 452 | contract ERC20 is Context, IERC20 { 453 | using SafeMath for uint256; 454 | using Address for address; 455 | 456 | mapping (address => uint256) private _balances; 457 | 458 | mapping (address => mapping (address => uint256)) private _allowances; 459 | 460 | uint256 private _totalSupply; 461 | 462 | string private _name; 463 | string private _symbol; 464 | uint8 private _decimals; 465 | 466 | /** 467 | * @dev Sets the values for {name} and {symbol}, initializes {decimals} with 468 | * a default value of 18. 469 | * 470 | * To select a different value for {decimals}, use {_setupDecimals}. 471 | * 472 | * All three of these values are immutable: they can only be set once during 473 | * construction. 474 | */ 475 | constructor (string memory name, string memory symbol) public { 476 | _name = name; 477 | _symbol = symbol; 478 | _decimals = 18; 479 | } 480 | 481 | /** 482 | * @dev Returns the name of the token. 483 | */ 484 | function name() public view returns (string memory) { 485 | return _name; 486 | } 487 | 488 | /** 489 | * @dev Returns the symbol of the token, usually a shorter version of the 490 | * name. 491 | */ 492 | function symbol() public view returns (string memory) { 493 | return _symbol; 494 | } 495 | 496 | /** 497 | * @dev Returns the number of decimals used to get its user representation. 498 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 499 | * be displayed to a user as `5,05` (`505 / 10 ** 2`). 500 | * 501 | * Tokens usually opt for a value of 18, imitating the relationship between 502 | * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is 503 | * called. 504 | * 505 | * NOTE: This information is only used for _display_ purposes: it in 506 | * no way affects any of the arithmetic of the contract, including 507 | * {IERC20-balanceOf} and {IERC20-transfer}. 508 | */ 509 | function decimals() public view returns (uint8) { 510 | return _decimals; 511 | } 512 | 513 | /** 514 | * @dev See {IERC20-totalSupply}. 515 | */ 516 | function totalSupply() public view override returns (uint256) { 517 | return _totalSupply; 518 | } 519 | 520 | /** 521 | * @dev See {IERC20-balanceOf}. 522 | */ 523 | function balanceOf(address account) public view override returns (uint256) { 524 | return _balances[account]; 525 | } 526 | 527 | /** 528 | * @dev See {IERC20-transfer}. 529 | * 530 | * Requirements: 531 | * 532 | * - `recipient` cannot be the zero address. 533 | * - the caller must have a balance of at least `amount`. 534 | */ 535 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) { 536 | _transfer(_msgSender(), recipient, amount); 537 | return true; 538 | } 539 | 540 | /** 541 | * @dev See {IERC20-allowance}. 542 | */ 543 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 544 | return _allowances[owner][spender]; 545 | } 546 | 547 | /** 548 | * @dev See {IERC20-approve}. 549 | * 550 | * Requirements: 551 | * 552 | * - `spender` cannot be the zero address. 553 | */ 554 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 555 | _approve(_msgSender(), spender, amount); 556 | return true; 557 | } 558 | 559 | /** 560 | * @dev See {IERC20-transferFrom}. 561 | * 562 | * Emits an {Approval} event indicating the updated allowance. This is not 563 | * required by the EIP. See the note at the beginning of {ERC20}; 564 | * 565 | * Requirements: 566 | * - `sender` and `recipient` cannot be the zero address. 567 | * - `sender` must have a balance of at least `amount`. 568 | * - the caller must have allowance for ``sender``'s tokens of at least 569 | * `amount`. 570 | */ 571 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 572 | _transfer(sender, recipient, amount); 573 | _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); 574 | return true; 575 | } 576 | 577 | /** 578 | * @dev Atomically increases the allowance granted to `spender` by the caller. 579 | * 580 | * This is an alternative to {approve} that can be used as a mitigation for 581 | * problems described in {IERC20-approve}. 582 | * 583 | * Emits an {Approval} event indicating the updated allowance. 584 | * 585 | * Requirements: 586 | * 587 | * - `spender` cannot be the zero address. 588 | */ 589 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { 590 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); 591 | return true; 592 | } 593 | 594 | /** 595 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 596 | * 597 | * This is an alternative to {approve} that can be used as a mitigation for 598 | * problems described in {IERC20-approve}. 599 | * 600 | * Emits an {Approval} event indicating the updated allowance. 601 | * 602 | * Requirements: 603 | * 604 | * - `spender` cannot be the zero address. 605 | * - `spender` must have allowance for the caller of at least 606 | * `subtractedValue`. 607 | */ 608 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { 609 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); 610 | return true; 611 | } 612 | 613 | /** 614 | * @dev Moves tokens `amount` from `sender` to `recipient`. 615 | * 616 | * This is internal function is equivalent to {transfer}, and can be used to 617 | * e.g. implement automatic token fees, slashing mechanisms, etc. 618 | * 619 | * Emits a {Transfer} event. 620 | * 621 | * Requirements: 622 | * 623 | * - `sender` cannot be the zero address. 624 | * - `recipient` cannot be the zero address. 625 | * - `sender` must have a balance of at least `amount`. 626 | */ 627 | function _transfer(address sender, address recipient, uint256 amount) internal virtual { 628 | require(sender != address(0), "ERC20: transfer from the zero address"); 629 | require(recipient != address(0), "ERC20: transfer to the zero address"); 630 | 631 | _beforeTokenTransfer(sender, recipient, amount); 632 | 633 | _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); 634 | _balances[recipient] = _balances[recipient].add(amount); 635 | emit Transfer(sender, recipient, amount); 636 | } 637 | 638 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 639 | * the total supply. 640 | * 641 | * Emits a {Transfer} event with `from` set to the zero address. 642 | * 643 | * Requirements 644 | * 645 | * - `to` cannot be the zero address. 646 | */ 647 | function _mint(address account, uint256 amount) internal virtual { 648 | require(account != address(0), "ERC20: mint to the zero address"); 649 | 650 | _beforeTokenTransfer(address(0), account, amount); 651 | 652 | _totalSupply = _totalSupply.add(amount); 653 | _balances[account] = _balances[account].add(amount); 654 | emit Transfer(address(0), account, amount); 655 | } 656 | 657 | /** 658 | * @dev Destroys `amount` tokens from `account`, reducing the 659 | * total supply. 660 | * 661 | * Emits a {Transfer} event with `to` set to the zero address. 662 | * 663 | * Requirements 664 | * 665 | * - `account` cannot be the zero address. 666 | * - `account` must have at least `amount` tokens. 667 | */ 668 | function _burn(address account, uint256 amount) internal virtual { 669 | require(account != address(0), "ERC20: burn from the zero address"); 670 | 671 | _beforeTokenTransfer(account, address(0), amount); 672 | 673 | _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); 674 | _totalSupply = _totalSupply.sub(amount); 675 | emit Transfer(account, address(0), amount); 676 | } 677 | 678 | /** 679 | * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. 680 | * 681 | * This is internal function is equivalent to `approve`, and can be used to 682 | * e.g. set automatic allowances for certain subsystems, etc. 683 | * 684 | * Emits an {Approval} event. 685 | * 686 | * Requirements: 687 | * 688 | * - `owner` cannot be the zero address. 689 | * - `spender` cannot be the zero address. 690 | */ 691 | function _approve(address owner, address spender, uint256 amount) internal virtual { 692 | require(owner != address(0), "ERC20: approve from the zero address"); 693 | require(spender != address(0), "ERC20: approve to the zero address"); 694 | 695 | _allowances[owner][spender] = amount; 696 | emit Approval(owner, spender, amount); 697 | } 698 | 699 | /** 700 | * @dev Sets {decimals} to a value other than the default one of 18. 701 | * 702 | * WARNING: This function should only be called from the constructor. Most 703 | * applications that interact with token contracts will not expect 704 | * {decimals} to ever change, and may work incorrectly if it does. 705 | */ 706 | function _setupDecimals(uint8 decimals_) internal { 707 | _decimals = decimals_; 708 | } 709 | 710 | /** 711 | * @dev Hook that is called before any transfer of tokens. This includes 712 | * minting and burning. 713 | * 714 | * Calling conditions: 715 | * 716 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 717 | * will be to transferred to `to`. 718 | * - when `from` is zero, `amount` tokens will be minted for `to`. 719 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 720 | * - `from` and `to` are never both zero. 721 | * 722 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 723 | */ 724 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } 725 | } 726 | 727 | // File: @openzeppelin/contracts/access/Ownable.sol 728 | 729 | 730 | 731 | pragma solidity >= 0.8.0; 732 | 733 | /** 734 | * @dev Contract module which provides a basic access control mechanism, where 735 | * there is an account (an owner) that can be granted exclusive access to 736 | * specific functions. 737 | * 738 | * By default, the owner account will be the one that deploys the contract. This 739 | * can later be changed with {transferOwnership}. 740 | * 741 | * This module is used through inheritance. It will make available the modifier 742 | * `onlyOwner`, which can be applied to your functions to restrict their use to 743 | * the owner. 744 | */ 745 | contract Ownable is Context { 746 | address private _owner; 747 | 748 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 749 | 750 | /** 751 | * @dev Initializes the contract setting the deployer as the initial owner. 752 | */ 753 | constructor () { 754 | address msgSender = _msgSender(); 755 | _owner = msgSender; 756 | emit OwnershipTransferred(address(0), msgSender); 757 | } 758 | 759 | /** 760 | * @dev Returns the address of the current owner. 761 | */ 762 | function owner() public view returns (address) { 763 | return _owner; 764 | } 765 | 766 | /** 767 | * @dev Throws if called by any account other than the owner. 768 | */ 769 | modifier onlyOwner() { 770 | require(_owner == _msgSender(), "Ownable: caller is not the owner"); 771 | _; 772 | } 773 | 774 | /** 775 | * @dev Leaves the contract without owner. It will not be possible to call 776 | * `onlyOwner` functions anymore. Can only be called by the current owner. 777 | * 778 | * NOTE: Renouncing ownership will leave the contract without an owner, 779 | * thereby removing any functionality that is only available to the owner. 780 | */ 781 | function renounceOwnership() public virtual onlyOwner { 782 | emit OwnershipTransferred(_owner, address(0)); 783 | _owner = address(0); 784 | } 785 | 786 | /** 787 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 788 | * Can only be called by the current owner. 789 | */ 790 | function transferOwnership(address newOwner) public virtual onlyOwner { 791 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 792 | emit OwnershipTransferred(_owner, newOwner); 793 | _owner = newOwner; 794 | } 795 | } 796 | 797 | // File: contracts/SushiToken.sol 798 | 799 | pragma solidity >= 0.8.0; 800 | 801 | 802 | 803 | 804 | // SushiToken with Governance. 805 | contract SushiToken is ERC20("SushiToken", "SUSHI"), Ownable { 806 | /// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef). 807 | function mint(address _to, uint256 _amount) public onlyOwner { 808 | _mint(_to, _amount); 809 | _moveDelegates(address(0), _delegates[_to], _amount); 810 | } 811 | 812 | // Copied and modified from YAM code: 813 | // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol 814 | // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol 815 | // Which is copied and modified from COMPOUND: 816 | // https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol 817 | 818 | mapping (address => address) internal _delegates; 819 | 820 | /// @notice A checkpoint for marking number of votes from a given block 821 | struct Checkpoint { 822 | uint32 fromBlock; 823 | uint256 votes; 824 | } 825 | 826 | /// @notice A record of votes checkpoints for each account, by index 827 | mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; 828 | 829 | /// @notice The number of checkpoints for each account 830 | mapping (address => uint32) public numCheckpoints; 831 | 832 | /// @notice The EIP-712 typehash for the contract's domain 833 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 834 | 835 | /// @notice The EIP-712 typehash for the delegation struct used by the contract 836 | bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); 837 | 838 | /// @notice A record of states for signing / validating signatures 839 | mapping (address => uint) public nonces; 840 | 841 | /// @notice An event thats emitted when an account changes its delegate 842 | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); 843 | 844 | /// @notice An event thats emitted when a delegate account's vote balance changes 845 | event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); 846 | 847 | /** 848 | * @notice Delegate votes from `msg.sender` to `delegatee` 849 | * @param delegator The address to get delegatee for 850 | */ 851 | function delegates(address delegator) 852 | external 853 | view 854 | returns (address) 855 | { 856 | return _delegates[delegator]; 857 | } 858 | 859 | /** 860 | * @notice Delegate votes from `msg.sender` to `delegatee` 861 | * @param delegatee The address to delegate votes to 862 | */ 863 | function delegate(address delegatee) external { 864 | return _delegate(msg.sender, delegatee); 865 | } 866 | 867 | /** 868 | * @notice Delegates votes from signatory to `delegatee` 869 | * @param delegatee The address to delegate votes to 870 | * @param nonce The contract state required to match the signature 871 | * @param expiry The time at which to expire the signature 872 | * @param v The recovery byte of the signature 873 | * @param r Half of the ECDSA signature pair 874 | * @param s Half of the ECDSA signature pair 875 | */ 876 | function delegateBySig( 877 | address delegatee, 878 | uint nonce, 879 | uint expiry, 880 | uint8 v, 881 | bytes32 r, 882 | bytes32 s 883 | ) 884 | external 885 | { 886 | bytes32 domainSeparator = keccak256( 887 | abi.encode( 888 | DOMAIN_TYPEHASH, 889 | keccak256(bytes(name())), 890 | getChainId(), 891 | address(this) 892 | ) 893 | ); 894 | 895 | bytes32 structHash = keccak256( 896 | abi.encode( 897 | DELEGATION_TYPEHASH, 898 | delegatee, 899 | nonce, 900 | expiry 901 | ) 902 | ); 903 | 904 | bytes32 digest = keccak256( 905 | abi.encodePacked( 906 | "\x19\x01", 907 | domainSeparator, 908 | structHash 909 | ) 910 | ); 911 | 912 | address signatory = ecrecover(digest, v, r, s); 913 | require(signatory != address(0), "SUSHI::delegateBySig: invalid signature"); 914 | require(nonce == nonces[signatory]++, "SUSHI::delegateBySig: invalid nonce"); 915 | require(block.timestamp <= expiry, "SUSHI::delegateBySig: signature expired"); 916 | return _delegate(signatory, delegatee); 917 | } 918 | 919 | /** 920 | * @notice Gets the current votes balance for `account` 921 | * @param account The address to get votes balance 922 | * @return The number of current votes for `account` 923 | */ 924 | function getCurrentVotes(address account) 925 | external 926 | view 927 | returns (uint256) 928 | { 929 | uint32 nCheckpoints = numCheckpoints[account]; 930 | return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; 931 | } 932 | 933 | /** 934 | * @notice Determine the prior number of votes for an account as of a block number 935 | * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. 936 | * @param account The address of the account to check 937 | * @param blockNumber The block number to get the vote balance at 938 | * @return The number of votes the account had as of the given block 939 | */ 940 | function getPriorVotes(address account, uint blockNumber) 941 | external 942 | view 943 | returns (uint256) 944 | { 945 | require(blockNumber < block.number, "SUSHI::getPriorVotes: not yet determined"); 946 | 947 | uint32 nCheckpoints = numCheckpoints[account]; 948 | if (nCheckpoints == 0) { 949 | return 0; 950 | } 951 | 952 | // First check most recent balance 953 | if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { 954 | return checkpoints[account][nCheckpoints - 1].votes; 955 | } 956 | 957 | // Next check implicit zero balance 958 | if (checkpoints[account][0].fromBlock > blockNumber) { 959 | return 0; 960 | } 961 | 962 | uint32 lower = 0; 963 | uint32 upper = nCheckpoints - 1; 964 | while (upper > lower) { 965 | uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow 966 | Checkpoint memory cp = checkpoints[account][center]; 967 | if (cp.fromBlock == blockNumber) { 968 | return cp.votes; 969 | } else if (cp.fromBlock < blockNumber) { 970 | lower = center; 971 | } else { 972 | upper = center - 1; 973 | } 974 | } 975 | return checkpoints[account][lower].votes; 976 | } 977 | 978 | function _delegate(address delegator, address delegatee) 979 | internal 980 | { 981 | address currentDelegate = _delegates[delegator]; 982 | uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled); 983 | _delegates[delegator] = delegatee; 984 | 985 | emit DelegateChanged(delegator, currentDelegate, delegatee); 986 | 987 | _moveDelegates(currentDelegate, delegatee, delegatorBalance); 988 | } 989 | 990 | using SafeMath for uint256; 991 | 992 | function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal { 993 | if (srcRep != dstRep && amount > 0) { 994 | if (srcRep != address(0)) { 995 | // decrease old representative 996 | uint32 srcRepNum = numCheckpoints[srcRep]; 997 | uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; 998 | uint256 srcRepNew = srcRepOld.sub(amount); 999 | _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); 1000 | } 1001 | 1002 | if (dstRep != address(0)) { 1003 | // increase new representative 1004 | uint32 dstRepNum = numCheckpoints[dstRep]; 1005 | uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; 1006 | uint256 dstRepNew = dstRepOld.add(amount); 1007 | _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); 1008 | } 1009 | } 1010 | } 1011 | 1012 | function _writeCheckpoint( 1013 | address delegatee, 1014 | uint32 nCheckpoints, 1015 | uint256 oldVotes, 1016 | uint256 newVotes 1017 | ) 1018 | internal 1019 | { 1020 | uint32 blockNumber = safe32(block.number, "SUSHI::_writeCheckpoint: block number exceeds 32 bits"); 1021 | 1022 | if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) { 1023 | checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; 1024 | } else { 1025 | checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes); 1026 | numCheckpoints[delegatee] = nCheckpoints + 1; 1027 | } 1028 | 1029 | emit DelegateVotesChanged(delegatee, oldVotes, newVotes); 1030 | } 1031 | 1032 | function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { 1033 | require(n < 2**32, errorMessage); 1034 | return uint32(n); 1035 | } 1036 | 1037 | function getChainId() internal view returns (uint) { 1038 | uint256 chainId; 1039 | assembly { chainId := chainid() } 1040 | return chainId; 1041 | } 1042 | } 1043 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/helpers/tokens/USDT.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2017-11-28 3 | */ 4 | 5 | pragma solidity >= 0.8.0; 6 | 7 | /** 8 | * @title SafeMath 9 | * @dev Math operations with safety checks that throw on error 10 | */ 11 | library SafeMath { 12 | function mul(uint256 a, uint256 b) internal view returns (uint256) { 13 | if (a == 0) { 14 | return 0; 15 | } 16 | uint256 c = a * b; 17 | assert(c / a == b); 18 | return c; 19 | } 20 | 21 | function div(uint256 a, uint256 b) internal view returns (uint256) { 22 | // assert(b > 0); // Solidity automatically throws when dividing by 0 23 | uint256 c = a / b; 24 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 25 | return c; 26 | } 27 | 28 | function sub(uint256 a, uint256 b) internal view returns (uint256) { 29 | assert(b <= a); 30 | return a - b; 31 | } 32 | 33 | function add(uint256 a, uint256 b) internal view returns (uint256) { 34 | uint256 c = a + b; 35 | assert(c >= a); 36 | return c; 37 | } 38 | } 39 | 40 | /** 41 | * @title Ownable 42 | * @dev The Ownable contract has an owner address, and provides basic authorization control 43 | * functions, this simplifies the implementation of "user permissions". 44 | */ 45 | contract Ownable { 46 | address public owner; 47 | 48 | /** 49 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 50 | * account. 51 | */ 52 | constructor() { 53 | owner = msg.sender; 54 | } 55 | 56 | /** 57 | * @dev Throws if called by any account other than the owner. 58 | */ 59 | modifier onlyOwner() { 60 | require(msg.sender == owner); 61 | _; 62 | } 63 | 64 | /** 65 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 66 | * @param newOwner The address to transfer ownership to. 67 | */ 68 | function transferOwnership(address newOwner) public onlyOwner { 69 | if (newOwner != address(0)) { 70 | owner = newOwner; 71 | } 72 | } 73 | 74 | } 75 | 76 | /** 77 | * @title ERC20Basic 78 | * @dev Simpler version of ERC20 interface 79 | * @dev see https://github.com/ethereum/EIPs/issues/20 80 | */ 81 | abstract contract ERC20Basic { 82 | uint public _totalSupply; 83 | function totalSupply() external view virtual returns (uint); 84 | function balanceOf(address who) external view virtual returns (uint); 85 | function transfer(address to, uint value) external virtual; 86 | event Transfer(address indexed from, address indexed to, uint value); 87 | } 88 | 89 | /** 90 | * @title ERC20 interface 91 | * @dev see https://github.com/ethereum/EIPs/issues/20 92 | */ 93 | abstract contract ERC20 is ERC20Basic { 94 | function allowance(address owner, address spender) external view virtual returns (uint); 95 | function transferFrom(address from, address to, uint value) external virtual; 96 | function approve(address spender, uint value) external virtual; 97 | event Approval(address indexed owner, address indexed spender, uint value); 98 | } 99 | 100 | /** 101 | * @title Basic token 102 | * @dev Basic version of StandardToken, with no allowances. 103 | */ 104 | abstract contract BasicToken is Ownable, ERC20Basic { 105 | using SafeMath for uint; 106 | 107 | mapping(address => uint) public balances; 108 | 109 | // additional variables for use if transaction fees ever became necessary 110 | uint public basisPointsRate = 0; 111 | uint public maximumFee = 0; 112 | 113 | /** 114 | * @dev Fix for the ERC20 short address attack. 115 | */ 116 | modifier onlyPayloadSize(uint size) { 117 | require(!(msg.data.length < size + 4)); 118 | _; 119 | } 120 | 121 | /** 122 | * @dev transfer token for a specified address 123 | * @param _to The address to transfer to. 124 | * @param _value The amount to be transferred. 125 | */ 126 | function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) virtual override(ERC20Basic) { 127 | uint fee = (_value.mul(basisPointsRate)).div(10000); 128 | if (fee > maximumFee) { 129 | fee = maximumFee; 130 | } 131 | uint sendAmount = _value.sub(fee); 132 | balances[msg.sender] = balances[msg.sender].sub(_value); 133 | balances[_to] = balances[_to].add(sendAmount); 134 | if (fee > 0) { 135 | balances[owner] = balances[owner].add(fee); 136 | emit Transfer(msg.sender, owner, fee); 137 | } 138 | emit Transfer(msg.sender, _to, sendAmount); 139 | } 140 | 141 | /** 142 | * @dev Gets the balance of the specified address. 143 | * @param _owner The address to query the the balance of. 144 | * @return balance An uint representing the amount owned by the passed address. 145 | */ 146 | function balanceOf(address _owner) public view override(ERC20Basic) virtual returns (uint balance) { 147 | return balances[_owner]; 148 | } 149 | 150 | } 151 | 152 | /** 153 | * @title Standard ERC20 token 154 | * 155 | * @dev Implementation of the basic standard token. 156 | * @dev https://github.com/ethereum/EIPs/issues/20 157 | * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 158 | */ 159 | abstract contract StandardToken is BasicToken, ERC20 { 160 | 161 | mapping (address => mapping (address => uint)) public allowed; 162 | 163 | uint public constant MAX_UINT = 2**256 - 1; 164 | 165 | using SafeMath for uint256; 166 | 167 | /** 168 | * @dev Transfer tokens from one address to another 169 | * @param _from address The address which you want to send tokens from 170 | * @param _to address The address which you want to transfer to 171 | * @param _value uint the amount of tokens to be transferred 172 | */ 173 | function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) override(ERC20) virtual { 174 | uint _allowance = allowed[_from][msg.sender]; 175 | 176 | // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met 177 | // if (_value > _allowance) throw; 178 | 179 | uint fee = (_value.mul(basisPointsRate)).div(10000); 180 | if (fee > maximumFee) { 181 | fee = maximumFee; 182 | } 183 | if (_allowance < MAX_UINT) { 184 | allowed[_from][msg.sender] = _allowance.sub(_value); 185 | } 186 | uint sendAmount = _value.sub(fee); 187 | balances[_from] = balances[_from].sub(_value); 188 | balances[_to] = balances[_to].add(sendAmount); 189 | if (fee > 0) { 190 | balances[owner] = balances[owner].add(fee); 191 | emit Transfer(_from, owner, fee); 192 | } 193 | emit Transfer(_from, _to, sendAmount); 194 | } 195 | 196 | /** 197 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 198 | * @param _spender The address which will spend the funds. 199 | * @param _value The amount of tokens to be spent. 200 | */ 201 | function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) override(ERC20) virtual { 202 | 203 | // To change the approve amount you first have to reduce the addresses` 204 | // allowance to zero by calling `approve(_spender, 0)` if it is not 205 | // already 0 to mitigate the race condition described here: 206 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 207 | require(!((_value != 0) && (allowed[msg.sender][_spender] != 0))); 208 | 209 | allowed[msg.sender][_spender] = _value; 210 | emit Approval(msg.sender, _spender, _value); 211 | } 212 | 213 | /** 214 | * @dev Function to check the amount of tokens than an owner allowed to a spender. 215 | * @param _owner address The address which owns the funds. 216 | * @param _spender address The address which will spend the funds. 217 | * @return remaining A uint specifying the amount of tokens still available for the spender. 218 | */ 219 | function allowance(address _owner, address _spender) public view override(ERC20) virtual returns (uint remaining) { 220 | return allowed[_owner][_spender]; 221 | } 222 | 223 | } 224 | 225 | 226 | /** 227 | * @title Pausable 228 | * @dev Base contract which allows children to implement an emergency stop mechanism. 229 | */ 230 | contract Pausable is Ownable { 231 | event Pause(); 232 | event Unpause(); 233 | 234 | bool public paused = false; 235 | 236 | 237 | /** 238 | * @dev Modifier to make a function callable only when the contract is not paused. 239 | */ 240 | modifier whenNotPaused() { 241 | require(!paused); 242 | _; 243 | } 244 | 245 | /** 246 | * @dev Modifier to make a function callable only when the contract is paused. 247 | */ 248 | modifier whenPaused() { 249 | require(paused); 250 | _; 251 | } 252 | 253 | /** 254 | * @dev called by the owner to pause, triggers stopped state 255 | */ 256 | function pause() onlyOwner whenNotPaused public { 257 | paused = true; 258 | emit Pause(); 259 | } 260 | 261 | /** 262 | * @dev called by the owner to unpause, returns to normal state 263 | */ 264 | function unpause() onlyOwner whenPaused public { 265 | paused = false; 266 | emit Unpause(); 267 | } 268 | } 269 | 270 | abstract contract BlackList is Ownable, BasicToken { 271 | 272 | /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) /////// 273 | function getBlackListStatus(address _maker) external view returns (bool) { 274 | return isBlackListed[_maker]; 275 | } 276 | 277 | function getOwner() external view returns (address) { 278 | return owner; 279 | } 280 | 281 | mapping (address => bool) public isBlackListed; 282 | 283 | function addBlackList (address _evilUser) public onlyOwner { 284 | isBlackListed[_evilUser] = true; 285 | emit AddedBlackList(_evilUser); 286 | } 287 | 288 | function removeBlackList (address _clearedUser) public onlyOwner { 289 | isBlackListed[_clearedUser] = false; 290 | emit RemovedBlackList(_clearedUser); 291 | } 292 | 293 | function destroyBlackFunds (address _blackListedUser) public onlyOwner { 294 | require(isBlackListed[_blackListedUser]); 295 | uint dirtyFunds = balanceOf(_blackListedUser); 296 | balances[_blackListedUser] = 0; 297 | _totalSupply -= dirtyFunds; 298 | emit DestroyedBlackFunds(_blackListedUser, dirtyFunds); 299 | } 300 | 301 | event DestroyedBlackFunds(address _blackListedUser, uint _balance); 302 | 303 | event AddedBlackList(address _user); 304 | 305 | event RemovedBlackList(address _user); 306 | 307 | } 308 | 309 | abstract contract UpgradedStandardToken is StandardToken{ 310 | // those methods are called by the legacy contract 311 | // and they must ensure msg.sender to be the contract address 312 | function transferByLegacy(address from, address to, uint value) public virtual; 313 | function transferFromByLegacy(address sender, address from, address spender, uint value) public virtual; 314 | function approveByLegacy(address from, address spender, uint value) public virtual; 315 | } 316 | 317 | contract USDT is Pausable, StandardToken, BlackList { 318 | 319 | string public name; 320 | string public symbol; 321 | uint public decimals; 322 | address public upgradedAddress; 323 | bool public constant deprecated = false; 324 | 325 | // The contract can be initialized with a number of tokens 326 | // All the tokens are deposited to the owner address 327 | // 328 | // @param _balance Initial supply of the contract 329 | // @param _name Token Name 330 | // @param _symbol Token symbol 331 | // @param _decimals Token decimals 332 | constructor (uint _initialSupply, string memory _name, string memory _symbol, uint _decimals) { 333 | _totalSupply = _initialSupply; 334 | name = _name; 335 | symbol = _symbol; 336 | decimals = _decimals; 337 | balances[owner] = _initialSupply; 338 | //deprecated = false; 339 | } 340 | 341 | // Forward ERC20 methods to upgraded contract if this one is deprecated 342 | function transfer(address _to, uint _value) public override(BasicToken, ERC20Basic) whenNotPaused { 343 | require(!isBlackListed[msg.sender]); 344 | if (deprecated) { 345 | return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value); 346 | } else { 347 | return super.transfer(_to, _value); 348 | } 349 | } 350 | 351 | // Forward ERC20 methods to upgraded contract if this one is deprecated 352 | function transferFrom(address _from, address _to, uint _value) public override whenNotPaused { 353 | require(!isBlackListed[_from]); 354 | if (deprecated) { 355 | return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value); 356 | } else { 357 | return super.transferFrom(_from, _to, _value); 358 | } 359 | } 360 | 361 | // Forward ERC20 methods to upgraded contract if this one is deprecated 362 | function balanceOf(address who) public view override(BasicToken, ERC20Basic) returns (uint) { 363 | if (deprecated) { 364 | return UpgradedStandardToken(upgradedAddress).balanceOf(who); 365 | } else { 366 | return super.balanceOf(who); 367 | } 368 | } 369 | 370 | // Forward ERC20 methods to upgraded contract if this one is deprecated 371 | function approve(address _spender, uint _value) public override onlyPayloadSize(2 * 32) { 372 | if (deprecated) { 373 | return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value); 374 | } else { 375 | return super.approve(_spender, _value); 376 | } 377 | } 378 | 379 | // Forward ERC20 methods to upgraded contract if this one is deprecated 380 | function allowance(address _owner, address _spender) public view override returns (uint remaining) { 381 | if (deprecated) { 382 | return StandardToken(upgradedAddress).allowance(_owner, _spender); 383 | } else { 384 | return super.allowance(_owner, _spender); 385 | } 386 | } 387 | 388 | // deprecate current contract in favour of a new one 389 | function deprecate(address _upgradedAddress) public onlyOwner { 390 | // deprecated = true; 391 | upgradedAddress = _upgradedAddress; 392 | emit Deprecate(_upgradedAddress); 393 | } 394 | 395 | // deprecate current contract if favour of a new one 396 | function totalSupply() public override view returns (uint) { 397 | if (deprecated) { 398 | return StandardToken(upgradedAddress).totalSupply(); 399 | } else { 400 | return _totalSupply; 401 | } 402 | } 403 | 404 | // Issue a new amount of tokens 405 | // these tokens are deposited into the owner address 406 | // 407 | // @param _amount Number of tokens to be issued 408 | function issue(uint amount) public onlyOwner { 409 | require(_totalSupply + amount > _totalSupply); 410 | require(balances[owner] + amount > balances[owner]); 411 | 412 | balances[owner] += amount; 413 | _totalSupply += amount; 414 | emit Issue(amount); 415 | } 416 | 417 | // Redeem tokens. 418 | // These tokens are withdrawn from the owner address 419 | // if the balance must be enough to cover the redeem 420 | // or the call will fail. 421 | // @param _amount Number of tokens to be issued 422 | function redeem(uint amount) public onlyOwner { 423 | require(_totalSupply >= amount); 424 | require(balances[owner] >= amount); 425 | 426 | _totalSupply -= amount; 427 | balances[owner] -= amount; 428 | emit Redeem(amount); 429 | } 430 | 431 | using SafeMath for uint256; 432 | function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner { 433 | // Ensure transparency by hardcoding limit beyond which fees can never be added 434 | require(newBasisPoints < 20); 435 | require(newMaxFee < 50); 436 | 437 | basisPointsRate = newBasisPoints; 438 | maximumFee = newMaxFee.mul(10**decimals); 439 | 440 | emit Params(basisPointsRate, maximumFee); 441 | } 442 | 443 | // Called when new token are issued 444 | event Issue(uint amount); 445 | 446 | // Called when tokens are redeemed 447 | event Redeem(uint amount); 448 | 449 | // Called when contract is deprecated 450 | event Deprecate(address newAddress); 451 | 452 | // Called if contract ever adds fees 453 | event Params(uint feeBasisPoints, uint maxFee); 454 | } 455 | -------------------------------------------------------------------------------- /formal-verification/certora/specs/methods/IFeeFlowController.spec: -------------------------------------------------------------------------------- 1 | methods { 2 | function buy(address[] assets, address assetsReceiver, uint256 epochId, uint256 deadline, uint256 maxPaymentTokenAmount) external returns(uint256) => HAVOC_ECF; // nonReentrant summary 3 | function getPrice() external returns(uint256); 4 | function paymentReceiver() external returns(address) envfree => CONSTANT; 5 | function reentrancyMock() external envfree; 6 | 7 | // view helpers 8 | function getTokenBalanceOf(address _token, address _account) external returns (uint256); 9 | function getAddressThis() external returns (address); 10 | function getPaymentTokenAllowance(address owner) external returns (uint256); 11 | function getPaymentTokenBalanceOf(address account) external returns (uint256); 12 | function getMinInitPrice() external returns (uint256) envfree; 13 | function getInitPrice() external returns (uint256) envfree; 14 | function getStartTime() external returns (uint256) envfree; 15 | function getEpochId() external returns (uint256) envfree; 16 | function getPaymentReceiver() external returns (address) envfree; 17 | function getPaymentToken() external returns (address) envfree; 18 | function getEpochPeriod() external returns (uint256) envfree; 19 | function getPriceMultiplier() external returns (uint256) envfree; 20 | // function getMsgSender() external returns (address) envfree; 21 | 22 | // constants 23 | function getMAX_EPOCH_PERIOD() external returns (uint256) envfree => CONSTANT; 24 | function getMIN_EPOCH_PERIOD() external returns (uint256) envfree => CONSTANT; 25 | function getMIN_PRICE_MULTIPLIER() external returns (uint256) envfree => CONSTANT; 26 | function getABS_MIN_INIT_PRICE() external returns (uint256) envfree => CONSTANT; 27 | function getABS_MAX_INIT_PRICE() external returns (uint256) envfree => CONSTANT; 28 | function getPRICE_MULTIPLIER_SCALE() external returns (uint256) envfree => CONSTANT; 29 | function getMAX_PRICE_MULTIPLIER() external returns (uint256) envfree => CONSTANT; 30 | } 31 | -------------------------------------------------------------------------------- /formal-verification/diff/MinimalEVCClient.sol.patch: -------------------------------------------------------------------------------- 1 | --- ../src/MinimalEVCClient.sol 2024-01-29 17:02:40 2 | +++ patched/MinimalEVCClient.sol 2024-01-31 15:10:34 3 | @@ -19,11 +19,7 @@ 4 | function _msgSender() internal view returns (address) { 5 | address sender = msg.sender; 6 | 7 | - if (sender == address(evc)) { 8 | - (sender,) = evc.getCurrentOnBehalfOfAccount(address(0)); 9 | - } 10 | - 11 | return sender; 12 | } 13 | 14 | -} 15 | \ No newline at end of file 16 | +} 17 | -------------------------------------------------------------------------------- /formal-verification/make-patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 3 | 4 | FORMAL_VERIFICATION_DIR="$SCRIPT_DIR" 5 | 6 | echo "Deleting old patched directory" 7 | rm -rf "$FORMAL_VERIFICATION_DIR"/patched 8 | echo "-------------------------------------" 9 | 10 | echo 11 | 12 | echo "Recreating patches..." 13 | make -C "$FORMAL_VERIFICATION_DIR" apply 14 | -------------------------------------------------------------------------------- /formal-verification/make-record.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 3 | 4 | FORMAL_VERIFICATION_DIR="$SCRIPT_DIR" 5 | 6 | # This one is used if you want to do some changes to the files in the patched directory 7 | # and then rcord the changes in the diff folder as .patch files 8 | make -C "$FORMAL_VERIFICATION_DIR" record 9 | -------------------------------------------------------------------------------- /formal-verification/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 3 | 4 | echo "Running make record" 5 | 6 | "$SCRIPT_DIR"/make-patch.sh 7 | 8 | # get any args passed to this script 9 | args=("$@") 10 | 11 | # if the arg is only one, then it is the file name 12 | FILE_NAME=$1 13 | # append all the rest of args 14 | REST_OF_ARGS=${args[@]:1} 15 | certoraRun "$SCRIPT_DIR"/certora/conf/"$FILE_NAME".conf $REST_OF_ARGS 16 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc_version = "0.8.24" 3 | src = "src" 4 | out = "out" 5 | libs = ["lib"] 6 | remappings = [ 7 | "evc/=lib/ethereum-vault-connector/src/", 8 | ] 9 | 10 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 11 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | evc/=lib/ethereum-vault-connector/src/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/ 4 | ethereum-vault-connector/=lib/ethereum-vault-connector/ 5 | forge-std/=lib/forge-std/src/ 6 | openzeppelin-contracts/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/ 7 | openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ 8 | solmate/=lib/solmate/src/ 9 | -------------------------------------------------------------------------------- /src/FeeFlowController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.24; 3 | 4 | import {ERC20} from "solmate/tokens/ERC20.sol"; 5 | import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; 6 | import {EVCUtil} from "evc/utils/EVCUtil.sol"; 7 | 8 | /// @title FeeFlowController 9 | /// @author Euler Labs (https://eulerlabs.com) 10 | /// @notice Continuous back to back dutch auctions selling any asset received by this contract 11 | contract FeeFlowController is EVCUtil { 12 | using SafeTransferLib for ERC20; 13 | 14 | uint256 public constant MIN_EPOCH_PERIOD = 1 hours; 15 | uint256 public constant MAX_EPOCH_PERIOD = 365 days; 16 | uint256 public constant MIN_PRICE_MULTIPLIER = 1.1e18; // Should at least be 110% of settlement price 17 | uint256 public constant MAX_PRICE_MULTIPLIER = 3e18; // Should not exceed 300% of settlement price 18 | uint256 public constant ABS_MIN_INIT_PRICE = 1e6; // Minimum sane value for init price 19 | uint256 public constant ABS_MAX_INIT_PRICE = type(uint192).max; // chosen so that initPrice * priceMultiplier does not exceed uint256 20 | uint256 public constant PRICE_MULTIPLIER_SCALE = 1e18; 21 | 22 | ERC20 public immutable paymentToken; 23 | address public immutable paymentReceiver; 24 | uint256 public immutable epochPeriod; 25 | uint256 public immutable priceMultiplier; 26 | uint256 public immutable minInitPrice; 27 | 28 | struct Slot0 { 29 | uint8 locked; // 1 if unlocked, 2 if locked 30 | uint16 epochId; // intentionally overflowable 31 | uint192 initPrice; 32 | uint40 startTime; 33 | } 34 | 35 | Slot0 internal slot0; 36 | 37 | event Buy(address indexed buyer, address indexed assetsReceiver, uint256 paymentAmount); 38 | 39 | error Reentrancy(); 40 | error InitPriceBelowMin(); 41 | error InitPriceExceedsMax(); 42 | error EpochPeriodBelowMin(); 43 | error EpochPeriodExceedsMax(); 44 | error PriceMultiplierBelowMin(); 45 | error PriceMultiplierExceedsMax(); 46 | error MinInitPriceBelowMin(); 47 | error MinInitPriceExceedsAbsMaxInitPrice(); 48 | error DeadlinePassed(); 49 | error EmptyAssets(); 50 | error EpochIdMismatch(); 51 | error MaxPaymentTokenAmountExceeded(); 52 | error PaymentReceiverIsThis(); 53 | 54 | modifier nonReentrant() { 55 | if (slot0.locked == 2) revert Reentrancy(); 56 | slot0.locked = 2; 57 | _; 58 | slot0.locked = 1; 59 | } 60 | 61 | modifier nonReentrantView() { 62 | if (slot0.locked == 2) revert Reentrancy(); 63 | _; 64 | } 65 | 66 | /// @dev Initializes the FeeFlowController contract with the specified parameters. 67 | /// @param evc The address of the Ethereum Vault Connector (EVC) contract. 68 | /// @param initPrice The initial price for the first epoch. 69 | /// @param paymentToken_ The address of the payment token. 70 | /// @param paymentReceiver_ The address of the payment receiver. 71 | /// @param epochPeriod_ The duration of each epoch period. 72 | /// @param priceMultiplier_ The multiplier for adjusting the price from one epoch to the next. 73 | /// @param minInitPrice_ The minimum allowed initial price for an epoch. 74 | /// @notice This constructor performs parameter validation and sets the initial values for the contract. 75 | constructor( 76 | address evc, 77 | uint256 initPrice, 78 | address paymentToken_, 79 | address paymentReceiver_, 80 | uint256 epochPeriod_, 81 | uint256 priceMultiplier_, 82 | uint256 minInitPrice_ 83 | ) EVCUtil(evc) { 84 | if (initPrice < minInitPrice_) revert InitPriceBelowMin(); 85 | if (initPrice > ABS_MAX_INIT_PRICE) revert InitPriceExceedsMax(); 86 | if (epochPeriod_ < MIN_EPOCH_PERIOD) revert EpochPeriodBelowMin(); 87 | if (epochPeriod_ > MAX_EPOCH_PERIOD) revert EpochPeriodExceedsMax(); 88 | if (priceMultiplier_ < MIN_PRICE_MULTIPLIER) revert PriceMultiplierBelowMin(); 89 | if (priceMultiplier_ > MAX_PRICE_MULTIPLIER) revert PriceMultiplierExceedsMax(); 90 | if (minInitPrice_ < ABS_MIN_INIT_PRICE) revert MinInitPriceBelowMin(); 91 | if (minInitPrice_ > ABS_MAX_INIT_PRICE) revert MinInitPriceExceedsAbsMaxInitPrice(); 92 | if (paymentReceiver_ == address(this)) revert PaymentReceiverIsThis(); 93 | 94 | slot0.initPrice = uint192(initPrice); 95 | slot0.startTime = uint40(block.timestamp); 96 | 97 | paymentToken = ERC20(paymentToken_); 98 | paymentReceiver = paymentReceiver_; 99 | epochPeriod = epochPeriod_; 100 | priceMultiplier = priceMultiplier_; 101 | minInitPrice = minInitPrice_; 102 | } 103 | 104 | /// @dev Allows a user to buy assets by transferring payment tokens and receiving the assets. 105 | /// @param assets The addresses of the assets to be bought. 106 | /// @param assetsReceiver The address that will receive the bought assets. 107 | /// @param epochId Id of the epoch to buy from, will revert if not the current epoch 108 | /// @param deadline The deadline timestamp for the purchase. 109 | /// @param maxPaymentTokenAmount The maximum amount of payment tokens the user is willing to spend. 110 | /// @return paymentAmount The amount of payment tokens transferred for the purchase. 111 | /// @notice This function performs various checks and transfers the payment tokens to the payment receiver. 112 | /// It also transfers the assets to the assets receiver and sets up a new auction with an updated initial price. 113 | function buy( 114 | address[] calldata assets, 115 | address assetsReceiver, 116 | uint256 epochId, 117 | uint256 deadline, 118 | uint256 maxPaymentTokenAmount 119 | ) external nonReentrant returns (uint256 paymentAmount) { 120 | if (block.timestamp > deadline) revert DeadlinePassed(); 121 | if (assets.length == 0) revert EmptyAssets(); 122 | 123 | Slot0 memory slot0Cache = slot0; 124 | 125 | if (uint16(epochId) != slot0Cache.epochId) revert EpochIdMismatch(); 126 | 127 | address sender = _msgSender(); 128 | 129 | paymentAmount = getPriceFromCache(slot0Cache); 130 | 131 | if (paymentAmount > maxPaymentTokenAmount) revert MaxPaymentTokenAmountExceeded(); 132 | 133 | if (paymentAmount > 0) { 134 | paymentToken.safeTransferFrom(sender, paymentReceiver, paymentAmount); 135 | } 136 | 137 | for (uint256 i = 0; i < assets.length; ++i) { 138 | // Transfer full balance to buyer 139 | uint256 balance = ERC20(assets[i]).balanceOf(address(this)); 140 | ERC20(assets[i]).safeTransfer(assetsReceiver, balance); 141 | } 142 | 143 | // Setup new auction 144 | uint256 newInitPrice = paymentAmount * priceMultiplier / PRICE_MULTIPLIER_SCALE; 145 | 146 | if (newInitPrice > ABS_MAX_INIT_PRICE) { 147 | newInitPrice = ABS_MAX_INIT_PRICE; 148 | } else if (newInitPrice < minInitPrice) { 149 | newInitPrice = minInitPrice; 150 | } 151 | 152 | // epochID is allowed to overflow, effectively reusing them 153 | unchecked { 154 | slot0Cache.epochId++; 155 | } 156 | slot0Cache.initPrice = uint192(newInitPrice); 157 | slot0Cache.startTime = uint40(block.timestamp); 158 | 159 | // Write cache in single write 160 | slot0 = slot0Cache; 161 | 162 | emit Buy(sender, assetsReceiver, paymentAmount); 163 | 164 | return paymentAmount; 165 | } 166 | 167 | /// @dev Retrieves the current price from the cache based on the elapsed time since the start of the epoch. 168 | /// @param slot0Cache The Slot0 struct containing the initial price and start time of the epoch. 169 | /// @return price The current price calculated based on the elapsed time and the initial price. 170 | /// @notice This function calculates the current price by subtracting a fraction of the initial price based on the elapsed time. 171 | // If the elapsed time exceeds the epoch period, the price will be 0. 172 | function getPriceFromCache(Slot0 memory slot0Cache) internal view returns (uint256) { 173 | uint256 timePassed = block.timestamp - slot0Cache.startTime; 174 | 175 | if (timePassed > epochPeriod) { 176 | return 0; 177 | } 178 | 179 | return slot0Cache.initPrice - slot0Cache.initPrice * timePassed / epochPeriod; 180 | } 181 | 182 | /// @dev Calculates the current price 183 | /// @return price The current price calculated based on the elapsed time and the initial price. 184 | /// @notice Uses the internal function `getPriceFromCache` to calculate the current price. 185 | function getPrice() external view nonReentrantView returns (uint256) { 186 | return getPriceFromCache(slot0); 187 | } 188 | 189 | /// @dev Retrieves Slot0 as a memory struct 190 | /// @return Slot0 The Slot0 value as a Slot0 struct 191 | function getSlot0() external view nonReentrantView returns (Slot0 memory) { 192 | return slot0; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /test/FeeFlowController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "forge-std/Test.sol"; 5 | import "evc/EthereumVaultConnector.sol"; 6 | import "./lib/MockToken.sol"; 7 | import "./lib/ReenteringMockToken.sol"; 8 | import "./lib/PredictAddress.sol"; 9 | import "./lib/OverflowableEpochIdFeeFlowController.sol"; 10 | import "../src/FeeFlowController.sol"; 11 | 12 | contract FeeFlowControllerTest is Test { 13 | uint256 public constant INIT_PRICE = 1e18; 14 | uint256 public constant MIN_INIT_PRICE = 1e6; 15 | uint256 public constant EPOCH_PERIOD = 14 days; 16 | uint256 public constant PRICE_MULTIPLIER = 2e18; 17 | 18 | address public paymentReceiver = makeAddr("paymentReceiver"); 19 | address public buyer = makeAddr("buyer"); 20 | address public assetsReceiver = makeAddr("assetsReceiver"); 21 | 22 | MockToken paymentToken; 23 | MockToken token1; 24 | MockToken token2; 25 | MockToken token3; 26 | MockToken token4; 27 | MockToken[] public tokens; 28 | 29 | IEVC public evc; 30 | FeeFlowController public feeFlowController; 31 | 32 | function setUp() public { 33 | // Deploy tokens 34 | paymentToken = new MockToken("Payment Token", "PAY"); 35 | vm.label(address(paymentToken), "paymentToken"); 36 | token1 = new MockToken("Token 1", "T1"); 37 | vm.label(address(token1), "token1"); 38 | tokens.push(token1); 39 | token2 = new MockToken("Token 2", "T2"); 40 | vm.label(address(token2), "token2"); 41 | tokens.push(token2); 42 | token3 = new MockToken("Token 3", "T3"); 43 | vm.label(address(token3), "token3"); 44 | tokens.push(token3); 45 | token4 = new MockToken("Token 4", "T4"); 46 | vm.label(address(token4), "token4"); 47 | tokens.push(token4); 48 | 49 | // Deploy EVC 50 | evc = new EthereumVaultConnector(); 51 | 52 | // Deploy FeeFlowController 53 | feeFlowController = new FeeFlowController( 54 | address(evc), 55 | INIT_PRICE, 56 | address(paymentToken), 57 | paymentReceiver, 58 | EPOCH_PERIOD, 59 | PRICE_MULTIPLIER, 60 | MIN_INIT_PRICE 61 | ); 62 | 63 | // Mint payment tokens to buyer 64 | paymentToken.mint(buyer, 1000000e18); 65 | // Approve payment token from buyer to FeeFlowController 66 | vm.startPrank(buyer); 67 | paymentToken.approve(address(feeFlowController), type(uint256).max); 68 | vm.stopPrank(); 69 | } 70 | 71 | function testConstructor() public { 72 | FeeFlowController.Slot0 memory slot0 = feeFlowController.getSlot0(); 73 | assertEq(address(feeFlowController.EVC()), address(evc)); 74 | assertEq(slot0.initPrice, uint128(INIT_PRICE)); 75 | assertEq(slot0.startTime, block.timestamp); 76 | assertEq(address(feeFlowController.paymentToken()), address(paymentToken)); 77 | assertEq(feeFlowController.paymentReceiver(), paymentReceiver); 78 | assertEq(feeFlowController.epochPeriod(), EPOCH_PERIOD); 79 | assertEq(feeFlowController.priceMultiplier(), PRICE_MULTIPLIER); 80 | assertEq(feeFlowController.minInitPrice(), MIN_INIT_PRICE); 81 | } 82 | 83 | function testConstructorInitPriceBelowMin() public { 84 | vm.expectRevert(FeeFlowController.InitPriceBelowMin.selector); 85 | new FeeFlowController( 86 | address(evc), 87 | MIN_INIT_PRICE - 1, 88 | address(paymentToken), 89 | paymentReceiver, 90 | EPOCH_PERIOD, 91 | PRICE_MULTIPLIER, 92 | MIN_INIT_PRICE 93 | ); 94 | } 95 | 96 | function testConstructorEpochPeriodBelowMin() public { 97 | uint256 minEpochPeriod = feeFlowController.MIN_EPOCH_PERIOD(); 98 | vm.expectRevert(FeeFlowController.EpochPeriodBelowMin.selector); 99 | new FeeFlowController( 100 | address(evc), 101 | INIT_PRICE, 102 | address(paymentToken), 103 | paymentReceiver, 104 | minEpochPeriod - 1, 105 | PRICE_MULTIPLIER, 106 | MIN_INIT_PRICE 107 | ); 108 | } 109 | 110 | function testConstructorEpochPeriodExceedsMax() public { 111 | uint256 maxEpochPeriod = feeFlowController.MAX_EPOCH_PERIOD(); 112 | vm.expectRevert(FeeFlowController.EpochPeriodExceedsMax.selector); 113 | new FeeFlowController( 114 | address(evc), 115 | INIT_PRICE, 116 | address(paymentToken), 117 | paymentReceiver, 118 | maxEpochPeriod + 1, 119 | PRICE_MULTIPLIER, 120 | MIN_INIT_PRICE 121 | ); 122 | } 123 | 124 | function testConstructorPriceMultiplierBelowMin() public { 125 | uint256 minPriceMultiplier = feeFlowController.MIN_PRICE_MULTIPLIER(); 126 | vm.expectRevert(FeeFlowController.PriceMultiplierBelowMin.selector); 127 | new FeeFlowController( 128 | address(evc), 129 | INIT_PRICE, 130 | address(paymentToken), 131 | paymentReceiver, 132 | EPOCH_PERIOD, 133 | minPriceMultiplier - 1, 134 | MIN_INIT_PRICE 135 | ); 136 | } 137 | 138 | function testConstructorMinInitPriceBelowMin() public { 139 | uint256 absMinInitPrice = feeFlowController.ABS_MIN_INIT_PRICE(); 140 | vm.expectRevert(FeeFlowController.MinInitPriceBelowMin.selector); 141 | new FeeFlowController( 142 | address(evc), 143 | INIT_PRICE, 144 | address(paymentToken), 145 | paymentReceiver, 146 | EPOCH_PERIOD, 147 | PRICE_MULTIPLIER, 148 | absMinInitPrice - 1 149 | ); 150 | } 151 | 152 | function testConstructorMinInitPriceExceedsABSMaxInitPrice() public { 153 | // Fails at init price check 154 | vm.expectRevert(FeeFlowController.InitPriceExceedsMax.selector); 155 | new FeeFlowController( 156 | address(evc), 157 | uint256(type(uint216).max) + 2, 158 | address(paymentToken), 159 | paymentReceiver, 160 | EPOCH_PERIOD, 161 | PRICE_MULTIPLIER, 162 | uint256(type(uint216).max) + 1 163 | ); 164 | } 165 | 166 | function testConstructorPaymentReceiverIsThis() public { 167 | address deployer = makeAddr("deployer"); 168 | address expectedAddress = PredictAddress.calc(deployer, 0); 169 | 170 | vm.startPrank(deployer); 171 | vm.expectRevert(FeeFlowController.PaymentReceiverIsThis.selector); 172 | new FeeFlowController( 173 | address(evc), 174 | INIT_PRICE, 175 | address(paymentToken), 176 | expectedAddress, 177 | EPOCH_PERIOD, 178 | PRICE_MULTIPLIER, 179 | MIN_INIT_PRICE 180 | ); 181 | vm.stopPrank(); 182 | } 183 | 184 | function testBuyStartOfAuction() public { 185 | mintTokensToBatchBuyer(); 186 | 187 | uint256 paymentReceiverBalanceBefore = paymentToken.balanceOf(paymentReceiver); 188 | uint256 buyerBalanceBefore = paymentToken.balanceOf(buyer); 189 | 190 | uint256 expectedPrice = feeFlowController.getPrice(); 191 | 192 | vm.startPrank(buyer); 193 | feeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 194 | vm.stopPrank(); 195 | 196 | uint256 paymentReceiverBalanceAfter = paymentToken.balanceOf(paymentReceiver); 197 | uint256 buyerBalanceAfter = paymentToken.balanceOf(buyer); 198 | FeeFlowController.Slot0 memory slot0 = feeFlowController.getSlot0(); 199 | 200 | // Assert token balances 201 | assert0Balances(address(feeFlowController)); 202 | assertMintBalances(assetsReceiver); 203 | assertEq(expectedPrice, INIT_PRICE); 204 | assertEq(paymentReceiverBalanceAfter, paymentReceiverBalanceBefore + expectedPrice); 205 | assertEq(buyerBalanceAfter, buyerBalanceBefore - expectedPrice); 206 | 207 | // Assert new auctionState 208 | assertEq(slot0.epochId, uint8(1)); 209 | assertEq(slot0.initPrice, uint128(INIT_PRICE * 2)); 210 | assertEq(slot0.startTime, block.timestamp); 211 | } 212 | 213 | function testBuyEndOfAuction() public { 214 | mintTokensToBatchBuyer(); 215 | 216 | uint256 paymentReceiverBalanceBefore = paymentToken.balanceOf(paymentReceiver); 217 | uint256 buyerBalanceBefore = paymentToken.balanceOf(buyer); 218 | 219 | // Skip to end of auction and then some 220 | skip(EPOCH_PERIOD + 1 days); 221 | uint256 expectedPrice = feeFlowController.getPrice(); 222 | 223 | vm.startPrank(buyer); 224 | feeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 225 | vm.stopPrank(); 226 | 227 | uint256 paymentReceiverBalanceAfter = paymentToken.balanceOf(paymentReceiver); 228 | uint256 buyerBalanceAfter = paymentToken.balanceOf(buyer); 229 | FeeFlowController.Slot0 memory slot0 = feeFlowController.getSlot0(); 230 | 231 | // Assert token balances 232 | assert0Balances(address(feeFlowController)); 233 | assertMintBalances(assetsReceiver); 234 | // Should have paid 0 235 | assertEq(expectedPrice, 0); 236 | assertEq(paymentReceiverBalanceAfter, paymentReceiverBalanceBefore); 237 | assertEq(buyerBalanceAfter, buyerBalanceBefore); 238 | 239 | // Assert new auctionState 240 | assertEq(slot0.epochId, uint8(1)); 241 | assertEq(slot0.initPrice, MIN_INIT_PRICE); 242 | assertEq(slot0.startTime, block.timestamp); 243 | } 244 | 245 | function testBuyMiddleOfAuction() public { 246 | mintTokensToBatchBuyer(); 247 | 248 | uint256 paymentReceiverBalanceBefore = paymentToken.balanceOf(paymentReceiver); 249 | uint256 buyerBalanceBefore = paymentToken.balanceOf(buyer); 250 | 251 | // Skip to middle of auction 252 | skip(EPOCH_PERIOD / 2); 253 | uint256 expectedPrice = feeFlowController.getPrice(); 254 | 255 | vm.startPrank(buyer); 256 | feeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 257 | vm.stopPrank(); 258 | 259 | uint256 paymentReceiverBalanceAfter = paymentToken.balanceOf(paymentReceiver); 260 | uint256 buyerBalanceAfter = paymentToken.balanceOf(buyer); 261 | FeeFlowController.Slot0 memory slot0 = feeFlowController.getSlot0(); 262 | 263 | // Assert token balances 264 | assert0Balances(address(feeFlowController)); 265 | assertMintBalances(assetsReceiver); 266 | assertEq(expectedPrice, INIT_PRICE / 2); 267 | assertEq(paymentReceiverBalanceAfter, paymentReceiverBalanceBefore + expectedPrice); 268 | assertEq(buyerBalanceAfter, buyerBalanceBefore - expectedPrice); 269 | 270 | // Assert new auctionState 271 | assertEq(slot0.epochId, uint8(1)); 272 | assertEq(slot0.initPrice, uint128(INIT_PRICE)); 273 | assertEq(slot0.startTime, block.timestamp); 274 | } 275 | 276 | function testBuyDeadlinePassedShouldFail() public { 277 | mintTokensToBatchBuyer(); 278 | skip(365 days); 279 | 280 | vm.startPrank(buyer); 281 | vm.expectRevert(FeeFlowController.DeadlinePassed.selector); 282 | feeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp - 1 days, 1000000e18); 283 | vm.stopPrank(); 284 | 285 | // Double check tokens haven't moved 286 | assertMintBalances(address(feeFlowController)); 287 | } 288 | 289 | function testBuyEmptyAssetsShouldFail() public { 290 | mintTokensToBatchBuyer(); 291 | 292 | vm.startPrank(buyer); 293 | vm.expectRevert(FeeFlowController.EmptyAssets.selector); 294 | feeFlowController.buy(new address[](0), assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 295 | vm.stopPrank(); 296 | 297 | // Double check tokens haven't moved 298 | assertMintBalances(address(feeFlowController)); 299 | } 300 | 301 | function testBuyWrongEpochShouldFail() public { 302 | mintTokensToBatchBuyer(); 303 | 304 | // Is actually at 0 305 | uint256 epochId = 1; 306 | 307 | vm.startPrank(buyer); 308 | vm.expectRevert(FeeFlowController.EpochIdMismatch.selector); 309 | feeFlowController.buy(assetsAddresses(), assetsReceiver, epochId, block.timestamp + 1 days, 1000000e18); 310 | vm.stopPrank(); 311 | 312 | // Double check tokens haven't moved 313 | assertMintBalances(address(feeFlowController)); 314 | } 315 | 316 | function testBuyPaymentAmountExceedsMax() public { 317 | mintTokensToBatchBuyer(); 318 | 319 | vm.startPrank(buyer); 320 | vm.expectRevert(FeeFlowController.MaxPaymentTokenAmountExceeded.selector); 321 | feeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp + 1 days, INIT_PRICE / 2); 322 | vm.stopPrank(); 323 | 324 | // Double check tokens haven't moved 325 | assertMintBalances(address(feeFlowController)); 326 | } 327 | 328 | function testBuyReenter() public { 329 | uint256 mintAmount = 1e18; 330 | 331 | // Setup reentering token 332 | ReenteringMockToken reenterToken = new ReenteringMockToken("ReenteringToken", "RET"); 333 | reenterToken.mint(address(feeFlowController), mintAmount); 334 | reenterToken.setReenterTargetAndData( 335 | address(feeFlowController), 336 | abi.encodeWithSelector( 337 | feeFlowController.buy.selector, assetsAddresses(), assetsReceiver, block.timestamp + 1 days, 1000000e18 338 | ) 339 | ); 340 | 341 | address[] memory assets = new address[](1); 342 | assets[0] = address(reenterToken); 343 | 344 | vm.startPrank(buyer); 345 | // Token does not bubble up error so this is the expected error on reentry 346 | vm.expectRevert("TRANSFER_FAILED"); 347 | feeFlowController.buy(assets, assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 348 | vm.stopPrank(); 349 | } 350 | 351 | function testBuyReenterGetPrice() public { 352 | uint256 mintAmount = 1e18; 353 | 354 | // Setup reentering token 355 | ReenteringMockToken reenterToken = new ReenteringMockToken("ReenteringToken", "RET"); 356 | reenterToken.mint(address(feeFlowController), mintAmount); 357 | reenterToken.setReenterTargetAndData( 358 | address(feeFlowController), abi.encodeWithSelector(feeFlowController.getPrice.selector) 359 | ); 360 | 361 | address[] memory assets = new address[](1); 362 | assets[0] = address(reenterToken); 363 | 364 | vm.startPrank(buyer); 365 | // Token does not bubble up error so this is the expected error on reentry 366 | vm.expectRevert("TRANSFER_FAILED"); 367 | feeFlowController.buy(assets, assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 368 | vm.stopPrank(); 369 | } 370 | 371 | function testBuyReenterGetSlot0() public { 372 | uint256 mintAmount = 1e18; 373 | 374 | // Setup reentering token 375 | ReenteringMockToken reenterToken = new ReenteringMockToken("ReenteringToken", "RET"); 376 | reenterToken.mint(address(feeFlowController), mintAmount); 377 | reenterToken.setReenterTargetAndData( 378 | address(feeFlowController), abi.encodeWithSelector(feeFlowController.getSlot0.selector) 379 | ); 380 | 381 | address[] memory assets = new address[](1); 382 | assets[0] = address(reenterToken); 383 | 384 | vm.startPrank(buyer); 385 | // Token does not bubble up error so this is the expected error on reentry 386 | vm.expectRevert("TRANSFER_FAILED"); 387 | feeFlowController.buy(assets, assetsReceiver, 0, block.timestamp + 1 days, 1000000e18); 388 | vm.stopPrank(); 389 | } 390 | 391 | function testBuyInitPriceExceedingABS_MAX_INIT_PRICE() public { 392 | uint256 absMaxInitPrice = feeFlowController.ABS_MAX_INIT_PRICE(); 393 | 394 | // Deploy with auction at max init price 395 | FeeFlowController tempFeeFlowController = new FeeFlowController( 396 | address(evc), absMaxInitPrice, address(paymentToken), paymentReceiver, EPOCH_PERIOD, 1.1e18, absMaxInitPrice 397 | ); 398 | 399 | // Mint payment tokens to buyer 400 | paymentToken.mint(buyer, type(uint216).max); 401 | 402 | vm.startPrank(buyer); 403 | // Approve payment token from buyer to FeeFlowController 404 | paymentToken.approve(address(tempFeeFlowController), type(uint256).max); 405 | // Buy 406 | tempFeeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp + 1 days, type(uint216).max); 407 | vm.stopPrank(); 408 | 409 | // Assert new init price 410 | FeeFlowController.Slot0 memory slot0 = tempFeeFlowController.getSlot0(); 411 | assertEq(slot0.initPrice, uint216(absMaxInitPrice)); 412 | } 413 | 414 | function testBuyWrapAroundEpochId() public { 415 | // MINT a lot of tokens 416 | paymentToken.mint(buyer, type(uint256).max - paymentToken.balanceOf(buyer)); 417 | 418 | OverflowableEpochIdFeeFlowController tempFeeFlowController = new OverflowableEpochIdFeeFlowController( 419 | address(evc), 420 | MIN_INIT_PRICE, 421 | address(paymentToken), 422 | paymentReceiver, 423 | EPOCH_PERIOD, 424 | PRICE_MULTIPLIER, 425 | MIN_INIT_PRICE 426 | ); 427 | tempFeeFlowController.setEpochId(type(uint16).max); 428 | 429 | vm.startPrank(buyer); 430 | paymentToken.approve(address(tempFeeFlowController), type(uint256).max); 431 | tempFeeFlowController.buy( 432 | assetsAddresses(), assetsReceiver, type(uint16).max, block.timestamp + 1 days, type(uint256).max 433 | ); 434 | vm.stopPrank(); 435 | 436 | FeeFlowController.Slot0 memory slot0 = feeFlowController.getSlot0(); 437 | assertEq(slot0.epochId, uint16(0)); 438 | } 439 | 440 | // Testing for overflows in price calculations -------------------------------- 441 | function testMAX_INIT_PRICEandMAX_EPOCH_PERIODdoNotOverflowPricing() public { 442 | uint256 absMaxInitPrice = feeFlowController.ABS_MAX_INIT_PRICE(); 443 | uint256 maxEpochPeriod = feeFlowController.MAX_EPOCH_PERIOD(); 444 | 445 | FeeFlowController tempFeeFlowController = new FeeFlowController( 446 | address(evc), 447 | absMaxInitPrice, 448 | address(paymentToken), 449 | paymentReceiver, 450 | maxEpochPeriod, 451 | 1.1e18, 452 | absMaxInitPrice 453 | ); 454 | paymentToken.mint(buyer, absMaxInitPrice); 455 | 456 | skip(maxEpochPeriod); 457 | 458 | vm.startPrank(buyer); 459 | paymentToken.approve(address(tempFeeFlowController), type(uint256).max); 460 | 461 | // Since timePassed == epochPeriod, timePassed will be multiplied to epochPeriod. 462 | // Does this not overflow and return zero? 463 | assert(tempFeeFlowController.getPrice() == 0); 464 | vm.stopPrank(); 465 | } 466 | 467 | function testMAX_INIT_PRICEandMAX_EPOCH_PERIODminusOneDoNotOverflowPricing() public { 468 | uint256 absMaxInitPrice = feeFlowController.ABS_MAX_INIT_PRICE(); 469 | uint256 maxEpochPeriod = feeFlowController.MAX_EPOCH_PERIOD(); 470 | 471 | FeeFlowController tempFeeFlowController = new FeeFlowController( 472 | address(evc), 473 | absMaxInitPrice, 474 | address(paymentToken), 475 | paymentReceiver, 476 | maxEpochPeriod, 477 | 1.1e18, 478 | absMaxInitPrice 479 | ); 480 | paymentToken.mint(buyer, absMaxInitPrice); 481 | 482 | skip(maxEpochPeriod - 1); 483 | 484 | vm.startPrank(buyer); 485 | paymentToken.approve(address(tempFeeFlowController), type(uint256).max); 486 | 487 | // Since timePassed < epochPeriod, timePassed will be multiplied to epochPeriod. 488 | // Does this not overflow? 489 | tempFeeFlowController.getPrice(); 490 | vm.stopPrank(); 491 | } 492 | 493 | function testMAX_INIT_PRICEandMAX_PRICE_MULTIPLIERdoNotOverflowNextAuction() public { 494 | uint256 absMaxInitPrice = feeFlowController.ABS_MAX_INIT_PRICE(); 495 | uint256 maxPriceMultiplier = feeFlowController.MAX_PRICE_MULTIPLIER(); 496 | 497 | FeeFlowController tempFeeFlowController = new FeeFlowController( 498 | address(evc), 499 | absMaxInitPrice, 500 | address(paymentToken), 501 | paymentReceiver, 502 | EPOCH_PERIOD, 503 | maxPriceMultiplier, 504 | absMaxInitPrice 505 | ); 506 | paymentToken.mint(buyer, absMaxInitPrice); 507 | 508 | vm.startPrank(buyer); 509 | paymentToken.approve(address(tempFeeFlowController), type(uint256).max); 510 | 511 | // Purchase will initialize the next auction and increase the price by its multiplier. Doesn't it revert? 512 | assert( 513 | tempFeeFlowController.buy(assetsAddresses(), assetsReceiver, 0, block.timestamp + 1 days, type(uint216).max) 514 | == absMaxInitPrice 515 | ); 516 | // Its next price should be capped to the maximum init price 517 | assert(tempFeeFlowController.getPrice() == absMaxInitPrice); 518 | vm.stopPrank(); 519 | } 520 | 521 | // Helper functions ----------------------------------------------------- 522 | function mintTokensToBatchBuyer() public { 523 | for (uint256 i = 0; i < tokens.length; i++) { 524 | tokens[i].mint(address(feeFlowController), 1000000e18 * (i + 1)); 525 | } 526 | } 527 | 528 | function mintAmounts() public view returns (uint256[] memory amounts) { 529 | amounts = new uint256[](tokens.length); 530 | for (uint256 i = 0; i < tokens.length; i++) { 531 | amounts[i] = 1000000e18 * (i + 1); 532 | } 533 | return amounts; 534 | } 535 | 536 | function assetsAddresses() public view returns (address[] memory addresses) { 537 | addresses = new address[](tokens.length); 538 | for (uint256 i = 0; i < tokens.length; i++) { 539 | addresses[i] = address(tokens[i]); 540 | } 541 | return addresses; 542 | } 543 | 544 | function assetsBalances(address who) public view returns (uint256[] memory result) { 545 | result = new uint256[](tokens.length); 546 | 547 | for (uint256 i = 0; i < tokens.length; i++) { 548 | result[i] = tokens[i].balanceOf(who); 549 | } 550 | 551 | return result; 552 | } 553 | 554 | function assertMintBalances(address who) public { 555 | uint256[] memory mintAmounts_ = mintAmounts(); 556 | uint256[] memory balances = assetsBalances(who); 557 | 558 | for (uint256 i = 0; i < tokens.length; i++) { 559 | assertEq(balances[i], mintAmounts_[i]); 560 | } 561 | } 562 | 563 | function assert0Balances(address who) public { 564 | for (uint256 i = 0; i < tokens.length; i++) { 565 | uint256 balance = tokens[i].balanceOf(who); 566 | assertEq(balance, 0); 567 | } 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /test/MinimalEVCClient.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./lib/TestableMinimalEVCClient.sol"; 6 | import "./lib/MockEVC.sol"; 7 | 8 | contract MinimalEVCClientTest is Test { 9 | MockEVC public evc; 10 | TestableMinimalEVCClient public client; 11 | 12 | address caller = makeAddr("caller"); 13 | address onBehalfOf = makeAddr("onBehalfOf"); 14 | 15 | function setUp() public { 16 | evc = new MockEVC(); 17 | client = new TestableMinimalEVCClient(address(evc)); 18 | } 19 | 20 | function testCallingDirectly() public { 21 | vm.prank(caller); 22 | address sender = client.getSender(); 23 | assertEq(sender, caller); 24 | } 25 | 26 | function testCallingThroughEVC() public { 27 | evc.setOnBehalfOfAccount(onBehalfOf); 28 | vm.prank(address(evc)); 29 | address sender = client.getSender(); 30 | assertEq(sender, onBehalfOf); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/lib/MockEVC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | contract MockEVC { 5 | address public onBehalfOfAccount; 6 | 7 | function setOnBehalfOfAccount(address account) external { 8 | onBehalfOfAccount = account; 9 | } 10 | 11 | function getCurrentOnBehalfOfAccount(address) external view returns (address, bool) { 12 | return (onBehalfOfAccount, false); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/lib/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import {ERC20} from "solmate/tokens/ERC20.sol"; 5 | 6 | contract MockToken is ERC20 { 7 | constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_, 18) {} 8 | 9 | function mint(address to, uint256 amount) external { 10 | _mint(to, amount); 11 | } 12 | 13 | function burn(address from, uint256 amount) external { 14 | _burn(from, amount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/lib/OverflowableEpochIdFeeFlowController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "../../src/FeeFlowController.sol"; 5 | 6 | contract OverflowableEpochIdFeeFlowController is FeeFlowController { 7 | constructor( 8 | address evc_, 9 | uint256 initPrice, 10 | address paymentToken_, 11 | address paymentReceiver_, 12 | uint256 epochPeriod_, 13 | uint256 priceMultiplier_, 14 | uint256 minInitPrice_ 15 | ) 16 | FeeFlowController(evc_, initPrice, paymentToken_, paymentReceiver_, epochPeriod_, priceMultiplier_, minInitPrice_) 17 | {} 18 | 19 | function setEpochId(uint16 epochId) public { 20 | slot0.epochId = epochId; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/lib/PredictAddress.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | library PredictAddress { 5 | function calc(address _deployer, uint256 _nonce) public pure returns (address) { 6 | bytes memory data; 7 | if (_nonce == 0x00) { 8 | data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _deployer, bytes1(0x80)); 9 | } else if (_nonce <= 0x7f) { 10 | data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _deployer, uint8(_nonce)); 11 | } else if (_nonce <= 0xff) { 12 | data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _deployer, bytes1(0x81), uint8(_nonce)); 13 | } else if (_nonce <= 0xffff) { 14 | data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _deployer, bytes1(0x82), uint16(_nonce)); 15 | } else if (_nonce <= 0xffffff) { 16 | data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _deployer, bytes1(0x83), uint24(_nonce)); 17 | } else { 18 | data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _deployer, bytes1(0x84), uint32(_nonce)); 19 | } // more than 2^32 nonces is highly unlikely 20 | 21 | return address(uint160(uint256(keccak256(data)))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/lib/ReenteringMockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import {MockToken} from "./MockToken.sol"; 5 | 6 | contract ReenteringMockToken is MockToken { 7 | address public reenterTarget; 8 | bytes public reenterData; 9 | 10 | constructor(string memory name_, string memory symbol_) MockToken(name_, symbol_) {} 11 | 12 | function setReenterTargetAndData(address reenterTarget_, bytes memory reenterData_) external { 13 | reenterTarget = reenterTarget_; 14 | reenterData = reenterData_; 15 | } 16 | 17 | function transfer(address recipient, uint256 amount) public override returns (bool) { 18 | bool success = super.transfer(recipient, amount); 19 | 20 | if (reenterTarget == address(0)) { 21 | return success; 22 | } 23 | 24 | (bool reenterSuccess, bytes memory data) = reenterTarget.call(reenterData); 25 | if (!reenterSuccess) { 26 | // The call failed, bubble up the error data 27 | if (data.length > 0) { 28 | // Decode the revert reason and throw it 29 | assembly { 30 | let data_size := mload(data) 31 | revert(add(32, data), data_size) 32 | } 33 | } else { 34 | revert("Call failed without revert reason"); 35 | } 36 | } 37 | 38 | return success; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/lib/TestableMinimalEVCClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "evc/utils/EVCUtil.sol"; 5 | 6 | contract TestableMinimalEVCClient is EVCUtil { 7 | constructor(address evc_) EVCUtil(evc_) {} 8 | 9 | function getSender() external view returns (address) { 10 | return _msgSender(); 11 | } 12 | } 13 | --------------------------------------------------------------------------------