├── LICENSE ├── README.md └── contracts ├── Distribution.vy ├── Gauge.vy ├── GaugeController.vy ├── Minter.vy └── veSUN.vy /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sun-dao-contracts 2 | Vyper contracts used in the Sun Governance DAO. 3 | 4 | ## Community 5 | 6 | If you have any questions about this project, or wish to engage with us: 7 | 8 | - [Telegram](https://t.me/SunIO_Defi) 9 | - [Twitter](https://twitter.com/defi_sunio) 10 | -------------------------------------------------------------------------------- /contracts/Distribution.vy: -------------------------------------------------------------------------------- 1 | # @version 0.2.7 2 | 3 | from vyper.interfaces import ERC20 4 | 5 | 6 | interface VotingEscrow: 7 | def user_point_epoch(addr: address) -> uint256: view 8 | def epoch() -> uint256: view 9 | def user_point_history(addr: address, loc: uint256) -> Point: view 10 | def point_history(loc: uint256) -> Point: view 11 | def checkpoint(): nonpayable 12 | 13 | 14 | event CommitAdmin: 15 | admin: address 16 | 17 | event ApplyAdmin: 18 | admin: address 19 | 20 | event ToggleAllowCheckpointToken: 21 | toggle_flag: bool 22 | 23 | event CheckpointToken: 24 | time: uint256 25 | tokens: uint256 26 | 27 | event Claimed: 28 | recipient: indexed(address) 29 | amount: uint256 30 | claim_epoch: uint256 31 | max_epoch: uint256 32 | 33 | 34 | struct Point: 35 | bias: int128 36 | slope: int128 # - dweight / dt 37 | ts: uint256 38 | blk: uint256 # block 39 | 40 | 41 | WEEK: constant(uint256) = 7 * 86400 42 | TOKEN_CHECKPOINT_DEADLINE: constant(uint256) = 86400 43 | 44 | start_time: public(uint256) 45 | time_cursor: public(uint256) 46 | time_cursor_of: public(HashMap[address, uint256]) 47 | user_epoch_of: public(HashMap[address, uint256]) 48 | 49 | last_token_time: public(uint256) 50 | tokens_per_week: public(uint256[1000000000000000]) 51 | 52 | voting_escrow: public(address) 53 | token: public(address) 54 | total_received: public(uint256) 55 | token_last_balance: public(uint256) 56 | 57 | ve_supply: public(uint256[1000000000000000]) # VE total supply at week bounds 58 | 59 | admin: public(address) 60 | future_admin: public(address) 61 | can_checkpoint_token: public(bool) 62 | emergency_return: public(address) 63 | is_killed: public(bool) 64 | 65 | initialed: public(bool) 66 | REDUCER: constant(uint256) = 1461501637330902918203684832716283019655932542975 #0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 67 | 68 | @internal 69 | @view 70 | def convert_to_addr(src: bytes32) -> address: 71 | # convert bytes32 to addr,reduce 0x41 72 | return convert(convert(bitwise_and(REDUCER, convert(src, uint256)),bytes32), address) 73 | 74 | 75 | @external 76 | def initial( 77 | _voting_escrow: address, 78 | _start_time: uint256, 79 | _token: address, 80 | _admin: address, 81 | _emergency_return: address 82 | ): 83 | """ 84 | @notice Contract constructor 85 | @param _voting_escrow VotingEscrow contract address 86 | @param _start_time Epoch time for fee distribution to start 87 | @param _token Fee token address (TUSD) 88 | @param _admin Admin address 89 | @param _emergency_return Address to transfer `_token` balance to 90 | if this contract is killed 91 | """ 92 | 93 | assert self.initialed == False 94 | self.initialed = True 95 | 96 | t: uint256 = _start_time / WEEK * WEEK 97 | self.start_time = t 98 | self.last_token_time = t 99 | self.time_cursor = t 100 | self.token = _token 101 | self.voting_escrow = _voting_escrow 102 | self.admin = _admin 103 | self.emergency_return = _emergency_return 104 | self.can_checkpoint_token = True 105 | 106 | 107 | @internal 108 | def _checkpoint_token(): 109 | token_balance: uint256 = ERC20(self.token).balanceOf(self) 110 | to_distribute: uint256 = token_balance - self.token_last_balance 111 | self.token_last_balance = token_balance 112 | 113 | t: uint256 = self.last_token_time 114 | since_last: uint256 = block.timestamp - t 115 | self.last_token_time = block.timestamp 116 | this_week: uint256 = t / WEEK * WEEK 117 | next_week: uint256 = 0 118 | 119 | for i in range(20): 120 | next_week = this_week + WEEK 121 | if block.timestamp < next_week: 122 | if since_last == 0 and block.timestamp == t: 123 | self.tokens_per_week[this_week] += to_distribute 124 | else: 125 | self.tokens_per_week[this_week] += to_distribute * (block.timestamp - t) / since_last 126 | break 127 | else: 128 | if since_last == 0 and next_week == t: 129 | self.tokens_per_week[this_week] += to_distribute 130 | else: 131 | self.tokens_per_week[this_week] += to_distribute * (next_week - t) / since_last 132 | t = next_week 133 | this_week = next_week 134 | 135 | log CheckpointToken(block.timestamp, to_distribute) 136 | 137 | 138 | @external 139 | def checkpoint_token(): 140 | """ 141 | @notice Update the token checkpoint 142 | @dev Calculates the total number of tokens to be distributed in a given week. 143 | During setup for the initial distribution this function is only callable 144 | by the contract owner. Beyond initial distro, it can be enabled for anyone 145 | to call. 146 | """ 147 | assert (msg.sender == self.admin) or\ 148 | (self.can_checkpoint_token and (block.timestamp > self.last_token_time + TOKEN_CHECKPOINT_DEADLINE)) 149 | self._checkpoint_token() 150 | 151 | 152 | @internal 153 | def _find_timestamp_epoch(ve: address, _timestamp: uint256) -> uint256: 154 | _min: uint256 = 0 155 | _max: uint256 = VotingEscrow(ve).epoch() 156 | for i in range(128): 157 | if _min >= _max: 158 | break 159 | _mid: uint256 = (_min + _max + 2) / 2 160 | pt: Point = VotingEscrow(ve).point_history(_mid) 161 | if pt.ts <= _timestamp: 162 | _min = _mid 163 | else: 164 | _max = _mid - 1 165 | return _min 166 | 167 | 168 | @view 169 | @internal 170 | def _find_timestamp_user_epoch(ve: address, user: address, _timestamp: uint256, max_user_epoch: uint256) -> uint256: 171 | _min: uint256 = 0 172 | _max: uint256 = max_user_epoch 173 | for i in range(128): 174 | if _min >= _max: 175 | break 176 | _mid: uint256 = (_min + _max + 2) / 2 177 | pt: Point = VotingEscrow(ve).user_point_history(user, _mid) 178 | if pt.ts <= _timestamp: 179 | _min = _mid 180 | else: 181 | _max = _mid - 1 182 | return _min 183 | 184 | 185 | @view 186 | @external 187 | def ve_for_at(user: bytes32, _timestamp: uint256) -> uint256: 188 | """ 189 | @notice Get the veCRV balance for `_user` at `_timestamp` 190 | @param _user Address to query balance for 191 | @param _timestamp Epoch time 192 | @return uint256 veCRV balance 193 | """ 194 | _user: address = self.convert_to_addr(user) 195 | 196 | ve: address = self.voting_escrow 197 | max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(_user) 198 | epoch: uint256 = self._find_timestamp_user_epoch(ve, _user, _timestamp, max_user_epoch) 199 | pt: Point = VotingEscrow(ve).user_point_history(_user, epoch) 200 | return convert(max(pt.bias - pt.slope * convert(_timestamp - pt.ts, int128), 0), uint256) 201 | 202 | 203 | @internal 204 | def _checkpoint_total_supply(): 205 | ve: address = self.voting_escrow 206 | t: uint256 = self.time_cursor 207 | rounded_timestamp: uint256 = block.timestamp / WEEK * WEEK 208 | VotingEscrow(ve).checkpoint() 209 | 210 | for i in range(20): 211 | if t > rounded_timestamp: 212 | break 213 | else: 214 | epoch: uint256 = self._find_timestamp_epoch(ve, t) 215 | pt: Point = VotingEscrow(ve).point_history(epoch) 216 | dt: int128 = 0 217 | if t > pt.ts: 218 | # If the point is at 0 epoch, it can actually be earlier than the first deposit 219 | # Then make dt 0 220 | dt = convert(t - pt.ts, int128) 221 | self.ve_supply[t] = convert(max(pt.bias - pt.slope * dt, 0), uint256) 222 | t += WEEK 223 | 224 | self.time_cursor = t 225 | 226 | 227 | @external 228 | def checkpoint_total_supply(): 229 | """ 230 | @notice Update the veCRV total supply checkpoint 231 | @dev The checkpoint is also updated by the first claimant each 232 | new epoch week. This function may be called independently 233 | of a claim, to reduce claiming gas costs. 234 | """ 235 | self._checkpoint_total_supply() 236 | 237 | 238 | @internal 239 | def _claim(addr: address, ve: address, _last_token_time: uint256) -> uint256: 240 | # Minimal user_epoch is 0 (if user had no point) 241 | user_epoch: uint256 = 0 242 | to_distribute: uint256 = 0 243 | 244 | max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(addr) 245 | _start_time: uint256 = self.start_time 246 | 247 | if max_user_epoch == 0: 248 | # No lock = no fees 249 | return 0 250 | 251 | week_cursor: uint256 = self.time_cursor_of[addr] 252 | if week_cursor == 0: 253 | # Need to do the initial binary search 254 | user_epoch = self._find_timestamp_user_epoch(ve, addr, _start_time, max_user_epoch) 255 | else: 256 | user_epoch = self.user_epoch_of[addr] 257 | 258 | if user_epoch == 0: 259 | user_epoch = 1 260 | 261 | user_point: Point = VotingEscrow(ve).user_point_history(addr, user_epoch) 262 | 263 | if week_cursor == 0: 264 | week_cursor = (user_point.ts + WEEK - 1) / WEEK * WEEK 265 | 266 | if week_cursor >= _last_token_time: 267 | return 0 268 | 269 | if week_cursor < _start_time: 270 | week_cursor = _start_time 271 | old_user_point: Point = empty(Point) 272 | 273 | # Iterate over weeks 274 | for i in range(50): 275 | if week_cursor >= _last_token_time: 276 | break 277 | 278 | if week_cursor >= user_point.ts and user_epoch <= max_user_epoch: 279 | user_epoch += 1 280 | old_user_point = user_point 281 | if user_epoch > max_user_epoch: 282 | user_point = empty(Point) 283 | else: 284 | user_point = VotingEscrow(ve).user_point_history(addr, user_epoch) 285 | 286 | else: 287 | # Calc 288 | # + i * 2 is for rounding errors 289 | dt: int128 = convert(week_cursor - old_user_point.ts, int128) 290 | balance_of: uint256 = convert(max(old_user_point.bias - dt * old_user_point.slope, 0), uint256) 291 | if balance_of == 0 and user_epoch > max_user_epoch: 292 | break 293 | if balance_of > 0: 294 | to_distribute += balance_of * self.tokens_per_week[week_cursor] / self.ve_supply[week_cursor] 295 | 296 | week_cursor += WEEK 297 | 298 | user_epoch = min(max_user_epoch, user_epoch - 1) 299 | self.user_epoch_of[addr] = user_epoch 300 | self.time_cursor_of[addr] = week_cursor 301 | 302 | log Claimed(addr, to_distribute, user_epoch, max_user_epoch) 303 | 304 | return to_distribute 305 | 306 | 307 | @external 308 | @nonreentrant('lock') 309 | def claim(_addr: address = msg.sender) -> uint256: 310 | """ 311 | @notice Claim fees for `_addr` 312 | @dev Each call to claim look at a maximum of 50 user veCRV points. 313 | For accounts with many veCRV related actions, this function 314 | may need to be called more than once to claim all available 315 | fees. In the `Claimed` event that fires, if `claim_epoch` is 316 | less than `max_epoch`, the account may claim again. 317 | @param _addr Address to claim fees for 318 | @return uint256 Amount of fees claimed in the call 319 | """ 320 | assert not self.is_killed 321 | 322 | if block.timestamp >= self.time_cursor: 323 | self._checkpoint_total_supply() 324 | 325 | last_token_time: uint256 = self.last_token_time 326 | 327 | if self.can_checkpoint_token and (block.timestamp > last_token_time + TOKEN_CHECKPOINT_DEADLINE): 328 | self._checkpoint_token() 329 | last_token_time = block.timestamp 330 | 331 | last_token_time = last_token_time / WEEK * WEEK 332 | 333 | amount: uint256 = self._claim(_addr, self.voting_escrow, last_token_time) 334 | if amount != 0: 335 | token: address = self.token 336 | assert ERC20(token).transfer(_addr, amount) 337 | self.token_last_balance -= amount 338 | 339 | return amount 340 | 341 | 342 | @external 343 | @nonreentrant('lock') 344 | def claim_many(_receivers: bytes32[20]) -> bool: 345 | """ 346 | @notice Make multiple fee claims in a single call 347 | @dev Used to claim for many accounts at once, or to make 348 | multiple claims for the same address when that address 349 | has significant veCRV history 350 | @param _receivers List of addresses to claim for. Claiming 351 | terminates at the first `ZERO_ADDRESS`. 352 | @return bool success 353 | """ 354 | assert not self.is_killed 355 | 356 | if block.timestamp >= self.time_cursor: 357 | self._checkpoint_total_supply() 358 | 359 | last_token_time: uint256 = self.last_token_time 360 | 361 | if self.can_checkpoint_token and (block.timestamp > last_token_time + TOKEN_CHECKPOINT_DEADLINE): 362 | self._checkpoint_token() 363 | last_token_time = block.timestamp 364 | 365 | last_token_time = last_token_time / WEEK * WEEK 366 | voting_escrow: address = self.voting_escrow 367 | token: address = self.token 368 | total: uint256 = 0 369 | 370 | for _addr in _receivers: 371 | addr: address = self.convert_to_addr(_addr) 372 | 373 | if addr == ZERO_ADDRESS: 374 | break 375 | 376 | amount: uint256 = self._claim(addr, voting_escrow, last_token_time) 377 | if amount != 0: 378 | assert ERC20(token).transfer(addr, amount) 379 | total += amount 380 | 381 | if total != 0: 382 | self.token_last_balance -= total 383 | 384 | return True 385 | 386 | 387 | @external 388 | def burn(_coin: address) -> bool: 389 | """ 390 | @notice Receive 3CRV into the contract and trigger a token checkpoint 391 | @param _coin Address of the coin being received (must be 3CRV) 392 | @return bool success 393 | """ 394 | assert _coin == self.token 395 | assert not self.is_killed 396 | 397 | amount: uint256 = ERC20(_coin).balanceOf(msg.sender) 398 | if amount != 0: 399 | ERC20(_coin).transferFrom(msg.sender, self, amount) 400 | if self.can_checkpoint_token and (block.timestamp > self.last_token_time + TOKEN_CHECKPOINT_DEADLINE): 401 | self._checkpoint_token() 402 | 403 | return True 404 | 405 | 406 | @external 407 | def commit_admin(_addr: address): 408 | """ 409 | @notice Commit transfer of ownership 410 | @param _addr New admin address 411 | """ 412 | assert msg.sender == self.admin # dev: access denied 413 | self.future_admin = _addr 414 | log CommitAdmin(_addr) 415 | 416 | 417 | @external 418 | def apply_admin(): 419 | """ 420 | @notice Apply transfer of ownership 421 | """ 422 | assert msg.sender == self.admin 423 | assert self.future_admin != ZERO_ADDRESS 424 | future_admin: address = self.future_admin 425 | self.admin = future_admin 426 | log ApplyAdmin(future_admin) 427 | 428 | 429 | @external 430 | def toggle_allow_checkpoint_token(): 431 | """ 432 | @notice Toggle permission for checkpointing by any account 433 | """ 434 | assert msg.sender == self.admin 435 | flag: bool = not self.can_checkpoint_token 436 | self.can_checkpoint_token = flag 437 | log ToggleAllowCheckpointToken(flag) 438 | 439 | 440 | @external 441 | def kill_me(): 442 | """ 443 | @notice Kill the contract 444 | @dev Killing transfers the entire 3CRV balance to the emergency return address 445 | and blocks the ability to claim or burn. The contract cannot be unkilled. 446 | """ 447 | assert msg.sender == self.admin 448 | 449 | self.is_killed = True 450 | 451 | token: address = self.token 452 | assert ERC20(token).transfer(self.emergency_return, ERC20(token).balanceOf(self)) 453 | 454 | 455 | @external 456 | def recover_balance(_coin: address) -> bool: 457 | """ 458 | @notice Recover ERC20 tokens from this contract 459 | @dev Tokens are sent to the emergency return address. 460 | @param _coin Token address 461 | @return bool success 462 | """ 463 | assert msg.sender == self.admin 464 | assert _coin != self.token 465 | 466 | amount: uint256 = ERC20(_coin).balanceOf(self) 467 | response: Bytes[32] = raw_call( 468 | _coin, 469 | concat( 470 | method_id("transfer(address,uint256)"), 471 | convert(self.emergency_return, bytes32), 472 | convert(amount, bytes32), 473 | ), 474 | max_outsize=32, 475 | ) 476 | if len(response) != 0: 477 | assert convert(response, bool) 478 | 479 | return True -------------------------------------------------------------------------------- /contracts/Gauge.vy: -------------------------------------------------------------------------------- 1 | from vyper.interfaces import ERC20 2 | 3 | interface CRV20: 4 | def future_epoch_time_write() -> uint256: nonpayable 5 | def rate() -> uint256: view 6 | 7 | interface Controller: 8 | def period() -> int128: view 9 | def period_write() -> int128: nonpayable 10 | def period_timestamp(p: int128) -> uint256: view 11 | def gauge_relative_weight(addr: bytes32, time: uint256) -> uint256: view 12 | def voting_escrow() -> address: view 13 | def checkpoint(): nonpayable 14 | def checkpoint_gauge(addr: bytes32): nonpayable 15 | 16 | interface Minter: 17 | def token() -> address: view 18 | def controller() -> address: view 19 | def minted(user: address, gauge: address) -> uint256: view 20 | 21 | interface VotingEscrow: 22 | def balanceOf(addr: bytes32) -> uint256: view 23 | def user_point_epoch(addr: address) -> uint256: view 24 | def user_point_history__ts(addr: bytes32, epoch: uint256) -> uint256: view 25 | 26 | event Check: 27 | provider: indexed(address) 28 | balance_: uint256 29 | 30 | event Deposit: 31 | provider: indexed(address) 32 | value: uint256 33 | 34 | event Withdraw: 35 | provider: indexed(address) 36 | value: uint256 37 | 38 | event UpdateLiquidityLimit: 39 | user: address 40 | original_balance: uint256 41 | original_supply: uint256 42 | working_balance: uint256 43 | working_supply: uint256 44 | 45 | 46 | TOKENLESS_PRODUCTION: constant(uint256) = 40 47 | BOOST_WARMUP: constant(uint256) = 0 # 2 * 7 * 86400 48 | WEEK: constant(uint256) = 604800 49 | 50 | minter: public(address) 51 | crv_token: public(address) 52 | lp_token: public(address) 53 | controller: public(address) 54 | voting_escrow: public(address) 55 | balanceOf: public(HashMap[address, uint256]) 56 | totalSupply: public(uint256) 57 | future_epoch_time: public(uint256) 58 | 59 | # caller -> recipient -> can deposit? 60 | approved_to_deposit: public(HashMap[address, HashMap[address, bool]]) 61 | 62 | working_balances: public(HashMap[address, uint256]) 63 | working_supply: public(uint256) 64 | 65 | # The goal is to be able to calculate ∫(rate * balance / totalSupply dt) from 0 till checkpoint 66 | # All values are kept in units of being multiplied by 1e18 67 | period: public(int128) 68 | period_timestamp: public(uint256[100000000000000000000000000000]) 69 | 70 | # 1e18 * ∫(rate(t) / totalSupply(t) dt) from 0 till checkpoint 71 | integrate_inv_supply: public(uint256[100000000000000000000000000000]) # bump epoch when rate() changes 72 | 73 | # 1e18 * ∫(rate(t) / totalSupply(t) dt) from (last_action) till checkpoint 74 | integrate_inv_supply_of: public(HashMap[address, uint256]) 75 | integrate_checkpoint_of: public(HashMap[address, uint256]) 76 | 77 | 78 | # ∫(balance * rate(t) / totalSupply(t) dt) from 0 till checkpoint 79 | # Units: rate * t = already number of coins per address to issue 80 | integrate_fraction: public(HashMap[address, uint256]) 81 | 82 | inflation_rate: public(uint256) 83 | initialed: public(bool) 84 | REDUCER: constant(uint256) = 1461501637330902918203684832716283019655932542975 #0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 85 | 86 | 87 | @internal 88 | @view 89 | def convert_to_addr(src: bytes32) -> address: 90 | # convert bytes32 to addr,reduce 0x41 91 | return convert(bitwise_and(REDUCER, convert(src, uint256)),address) 92 | 93 | 94 | 95 | @external 96 | def initial(lp_addr: address, _minter: address, _speeder: address, _controller: address): 97 | """ 98 | @notice Contract constructor 99 | @param lp_addr Liquidity Pool contract address 100 | @param _minter Minter contract address 101 | """ 102 | 103 | assert lp_addr != ZERO_ADDRESS 104 | assert _minter != ZERO_ADDRESS 105 | 106 | assert self.initialed == False 107 | self.initialed = True 108 | 109 | self.lp_token = lp_addr 110 | self.minter = _minter 111 | #crv_addr: address = Minter(_minter).token() 112 | crv_addr: address = _speeder 113 | self.crv_token = crv_addr 114 | #controller_addr: address = Minter(_minter).controller() 115 | self.controller = _controller 116 | self.voting_escrow = Controller(_controller).voting_escrow() 117 | self.period_timestamp[0] = block.timestamp 118 | self.inflation_rate = CRV20(crv_addr).rate() 119 | self.future_epoch_time = CRV20(crv_addr).future_epoch_time_write() 120 | 121 | 122 | @internal 123 | def _update_liquidity_limit(addr: address, l: uint256, L: uint256): 124 | """ 125 | @notice Calculate limits `which depend on the amount of CRV token per-user. 126 | Effectively it calculates working balances to apply amplification 127 | of CRV production by CRV 128 | @param addr User address 129 | @param l User's amount of liquidity (LP tokens) 130 | @param L Total amount of liquidity (LP tokens) 131 | """ 132 | # To be called after totalSupply is updated 133 | _voting_escrow: address = self.voting_escrow 134 | voting_balance: uint256 = VotingEscrow(_voting_escrow).balanceOf(convert(addr,bytes32)) 135 | voting_total: uint256 = ERC20(_voting_escrow).totalSupply() 136 | 137 | lim: uint256 = l * TOKENLESS_PRODUCTION / 100 138 | if (voting_total > 0) and (block.timestamp > self.period_timestamp[0] + BOOST_WARMUP): 139 | lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100 140 | 141 | lim = min(l, lim) 142 | old_bal: uint256 = self.working_balances[addr] 143 | self.working_balances[addr] = lim 144 | _working_supply: uint256 = self.working_supply + lim - old_bal 145 | self.working_supply = _working_supply 146 | 147 | log UpdateLiquidityLimit(addr, l, L, lim, _working_supply) 148 | 149 | 150 | @internal 151 | def _checkpoint(addr: address): 152 | """ 153 | @notice Checkpoint for a user 154 | @param addr User address 155 | """ 156 | _token: address = self.crv_token 157 | _controller: address = self.controller 158 | _period: int128 = self.period 159 | _period_time: uint256 = self.period_timestamp[_period] 160 | _integrate_inv_supply: uint256 = self.integrate_inv_supply[_period] 161 | rate: uint256 = self.inflation_rate 162 | new_rate: uint256 = rate 163 | prev_future_epoch: uint256 = self.future_epoch_time 164 | 165 | self.future_epoch_time = CRV20(_token).future_epoch_time_write() 166 | new_rate = CRV20(_token).rate() 167 | self.inflation_rate = new_rate 168 | 169 | Controller(_controller).checkpoint_gauge(convert(self.lp_token,bytes32)) 170 | 171 | _working_balance: uint256 = self.working_balances[addr] 172 | _working_supply: uint256 = self.working_supply 173 | 174 | # Update integral of 1/supply 175 | if block.timestamp > _period_time: 176 | prev_week_time: uint256 = _period_time 177 | week_time: uint256 = min((_period_time + WEEK) / WEEK * WEEK, block.timestamp) 178 | 179 | for i in range(500): 180 | dt: uint256 = week_time - prev_week_time 181 | w: uint256 = Controller(_controller).gauge_relative_weight(convert(self.lp_token,bytes32), prev_week_time / WEEK * WEEK) 182 | 183 | if _working_supply > 0: 184 | if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time: 185 | # If we went across one or multiple epochs, apply the rate 186 | # of the first epoch until it ends, and then the rate of 187 | # the last epoch. 188 | # If more than one epoch is crossed - the gauge gets less, 189 | # but that'd meen it wasn't called for more than 1 year 190 | _integrate_inv_supply += rate * w * (prev_future_epoch - prev_week_time) / _working_supply 191 | rate = new_rate 192 | _integrate_inv_supply += rate * w * (week_time - prev_future_epoch) / _working_supply 193 | else: 194 | _integrate_inv_supply += rate * w * dt / _working_supply 195 | # On precisions of the calculation 196 | # rate ~= 10e18 197 | # last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%) 198 | # _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example) 199 | # The largest loss is at dt = 1 200 | # Loss is 1e-9 - acceptable 201 | 202 | if week_time == block.timestamp: 203 | break 204 | prev_week_time = week_time 205 | week_time = min(week_time + WEEK, block.timestamp) 206 | 207 | _period += 1 208 | self.period = _period 209 | self.period_timestamp[_period] = block.timestamp 210 | self.integrate_inv_supply[_period] = _integrate_inv_supply 211 | 212 | # Update user-specific integrals 213 | self.integrate_fraction[addr] += _working_balance * (_integrate_inv_supply - self.integrate_inv_supply_of[addr]) / 10 ** 18 214 | self.integrate_inv_supply_of[addr] = _integrate_inv_supply 215 | self.integrate_checkpoint_of[addr] = block.timestamp 216 | 217 | 218 | @external 219 | def user_checkpoint(addr: address) -> bool: 220 | """ 221 | @notice Record a checkpoint for `addr` 222 | @param addr User address 223 | @return bool success 224 | """ 225 | assert (msg.sender == addr) or (msg.sender == self.minter) # dev: unauthorized 226 | self._checkpoint(addr) 227 | self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply) 228 | return True 229 | 230 | 231 | @external 232 | def claimable_tokens(addr: address) -> uint256: 233 | """ 234 | @notice Get the number of claimable tokens per user 235 | @dev This function should be manually changed to "view" in the ABI 236 | @return uint256 number of claimable tokens per user 237 | """ 238 | self._checkpoint(addr) 239 | return self.integrate_fraction[addr] - Minter(self.minter).minted(addr, self) 240 | 241 | 242 | @external 243 | def claimable_tokens_view(_addr: bytes32) -> uint256: 244 | addr: address = self.convert_to_addr(_addr) 245 | self._checkpoint(addr) 246 | return self.integrate_fraction[addr] - Minter(self.minter).minted(addr, self) 247 | 248 | 249 | 250 | @external 251 | def kick(_addr: bytes32): 252 | """ 253 | @notice Kick `addr` for abusing their boost 254 | @dev Only if either they had another voting event, or their voting escrow lock expired 255 | @param addr Address to kick 256 | """ 257 | addr: address = self.convert_to_addr(_addr) 258 | _voting_escrow: address = self.voting_escrow 259 | 260 | t_last: uint256 = self.integrate_checkpoint_of[addr] 261 | t_ve: uint256 = VotingEscrow(_voting_escrow).user_point_history__ts( 262 | _addr, VotingEscrow(_voting_escrow).user_point_epoch(addr) 263 | ) 264 | _balance: uint256 = self.balanceOf[addr] 265 | 266 | assert VotingEscrow(_voting_escrow).balanceOf(convert(addr,bytes32)) == 0 or t_ve > t_last # dev: kick not allowed 267 | assert self.working_balances[addr] > _balance * TOKENLESS_PRODUCTION / 100 # dev: kick not needed 268 | 269 | self._checkpoint(addr) 270 | self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply) 271 | 272 | 273 | @external 274 | def set_approve_deposit(_addr: bytes32, can_deposit: bool): 275 | """ 276 | @notice Set whether `addr` can deposit tokens for `msg.sender` 277 | @param addr Address to set approval on 278 | @param can_deposit bool - can this account deposit for `msg.sender`? 279 | """ 280 | addr: address = self.convert_to_addr(_addr) 281 | 282 | self.approved_to_deposit[addr][msg.sender] = can_deposit 283 | 284 | 285 | @external 286 | @nonreentrant('lock') 287 | def deposit(_value: uint256, addr: address = msg.sender): 288 | """ 289 | @notice Deposit `_value` LP tokens 290 | @param _value Number of tokens to deposit 291 | @param addr Address to deposit for 292 | """ 293 | if addr != msg.sender: 294 | assert self.approved_to_deposit[msg.sender][addr], "Not approved" 295 | 296 | self._checkpoint(addr) 297 | 298 | if _value != 0: 299 | _balance: uint256 = self.balanceOf[addr] + _value 300 | _supply: uint256 = self.totalSupply + _value 301 | self.balanceOf[addr] = _balance 302 | self.totalSupply = _supply 303 | 304 | self._update_liquidity_limit(addr, _balance, _supply) 305 | log Check(msg.sender, _balance) 306 | 307 | assert ERC20(self.lp_token).transferFrom(msg.sender, self, _value) 308 | 309 | log Deposit(addr, _value) 310 | 311 | 312 | @external 313 | @nonreentrant('lock') 314 | def withdraw(_value: uint256): 315 | """ 316 | @notice Withdraw `_value` LP tokens 317 | @param _value Number of tokens to withdraw 318 | """ 319 | self._checkpoint(msg.sender) 320 | 321 | _balance: uint256 = self.balanceOf[msg.sender] - _value 322 | _supply: uint256 = self.totalSupply - _value 323 | self.balanceOf[msg.sender] = _balance 324 | self.totalSupply = _supply 325 | 326 | self._update_liquidity_limit(msg.sender, _balance, _supply) 327 | 328 | assert ERC20(self.lp_token).transfer(msg.sender, _value) 329 | 330 | log Withdraw(msg.sender, _value) 331 | log Check(msg.sender, _balance) 332 | 333 | 334 | @external 335 | @view 336 | def integrate_checkpoint() -> uint256: 337 | return self.period_timestamp[self.period] -------------------------------------------------------------------------------- /contracts/GaugeController.vy: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 7 * 86400 seconds - all future times are rounded by week 4 | WEEK: constant(uint256) = 7 * 86400 5 | 6 | # Cannot change weight votes more often than once in 10 days 7 | WEIGHT_VOTE_DELAY: constant(uint256) = 10 * 86400 8 | 9 | 10 | struct Point: 11 | bias: uint256 12 | slope: uint256 13 | 14 | struct VotedSlope: 15 | slope: uint256 16 | power: uint256 17 | end: uint256 18 | 19 | 20 | interface VotingEscrow: 21 | def get_last_user_slope(addr: bytes32) -> int128: view 22 | def locked__end(addr: bytes32) -> uint256: view 23 | 24 | 25 | event CommitOwnership: 26 | admin: address 27 | 28 | event ApplyOwnership: 29 | admin: address 30 | 31 | event AddType: 32 | name: String[64] 33 | type_id: int128 34 | 35 | event NewTypeWeight: 36 | type_id: int128 37 | time: uint256 38 | weight: uint256 39 | total_weight: uint256 40 | 41 | event NewGaugeWeight: 42 | gauge_address: address 43 | time: uint256 44 | weight: uint256 45 | total_weight: uint256 46 | 47 | event VoteForGauge: 48 | time: uint256 49 | user: address 50 | gauge_addr: address 51 | weight: uint256 52 | 53 | event NewGauge: 54 | addr: address 55 | gauge_type: int128 56 | weight: uint256 57 | 58 | 59 | MULTIPLIER: constant(uint256) = 10 ** 18 60 | 61 | admin: public(address) # Can and will be a smart contract 62 | future_admin: public(address) # Can and will be a smart contract 63 | 64 | token: public(address) # CRV token 65 | voting_escrow: public(address) # Voting escrow 66 | 67 | # Gauge parameters 68 | # All numbers are "fixed point" on the basis of 1e18 69 | n_gauge_types: public(int128) 70 | n_gauges: public(int128) 71 | gauge_type_names: public(HashMap[int128, String[64]]) 72 | 73 | # Needed for enumeration 74 | gauges: public(address[1000000000]) 75 | 76 | # we increment values by 1 prior to storing them here so we can rely on a value 77 | # of zero as meaning the gauge has not been set 78 | gauge_types_: HashMap[address, int128] 79 | 80 | vote_user_slopes: public(HashMap[address, HashMap[address, VotedSlope]]) # user -> gauge_addr -> VotedSlope 81 | vote_user_power: public(HashMap[address, uint256]) # Total vote power used by user 82 | last_user_vote: public(HashMap[address, HashMap[address, uint256]]) # Last user vote's timestamp for each gauge address 83 | 84 | # Past and scheduled points for gauge weight, sum of weights per type, total weight 85 | # Point is for bias+slope 86 | # changes_* are for changes in slope 87 | # time_* are for the last change timestamp 88 | # timestamps are rounded to whole weeks 89 | 90 | #按照gauge_addr整理的point 91 | points_weight: public(HashMap[address, HashMap[uint256, Point]]) # gauge_addr -> time -> Point 92 | changes_weight: HashMap[address, HashMap[uint256, uint256]] # gauge_addr -> time -> slope 93 | time_weight: public(HashMap[address, uint256]) # gauge_addr -> last scheduled time (next week) 94 | 95 | #按照typeid整理的point 96 | points_sum: public(HashMap[int128, HashMap[uint256, Point]]) # type_id -> time -> Point 97 | changes_sum: HashMap[int128, HashMap[uint256, uint256]] # type_id -> time -> slope 98 | time_sum: public(uint256[1000000000]) # type_id -> last scheduled time (next week) 99 | 100 | #所有池的总Point 101 | points_total: public(HashMap[uint256, uint256]) # time -> total weight 102 | time_total: public(uint256) # last scheduled time 103 | 104 | #以下为人为设置的type Weight 105 | points_type_weight: public(HashMap[int128, HashMap[uint256, uint256]]) # type_id -> time -> type weight 106 | time_type_weight: public(uint256[1000000000]) # type_id -> last scheduled time (next week) 107 | 108 | REDUCER: constant(uint256) = 1461501637330902918203684832716283019655932542975 #0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 109 | initialed: public(bool) 110 | 111 | 112 | @internal 113 | @view 114 | def convert_to_addr(src: bytes32) -> address: 115 | # convert bytes32 to addr,reduce 0x41 116 | return convert(bitwise_and(REDUCER, convert(src, uint256)),address) 117 | 118 | 119 | 120 | @external 121 | def initial(_token: bytes32, _voting_escrow: bytes32): 122 | """ 123 | @notice Contract constructor 124 | @param _token `ERC20CRV` contract address 125 | @param _voting_escrow `VotingEscrow` contract address 126 | """ 127 | assert self.initialed == False 128 | 129 | self.admin = msg.sender 130 | self.token = self.convert_to_addr(_token) 131 | self.voting_escrow = self.convert_to_addr(_voting_escrow) 132 | 133 | assert self.token != ZERO_ADDRESS 134 | assert self.voting_escrow != ZERO_ADDRESS 135 | 136 | self.time_total = block.timestamp / WEEK * WEEK 137 | self.initialed = True 138 | 139 | @external 140 | def commit_transfer_ownership(_addr: bytes32): 141 | """ 142 | @notice Transfer ownership of GaugeController to `addr` 143 | @param addr Address to have ownership transferred to 144 | """ 145 | addr: address = self.convert_to_addr(_addr) 146 | 147 | assert msg.sender == self.admin # dev: admin only 148 | self.future_admin = addr 149 | log CommitOwnership(addr) 150 | 151 | 152 | @external 153 | def apply_transfer_ownership(): 154 | """ 155 | @notice Apply pending ownership transfer 156 | """ 157 | assert msg.sender == self.admin # dev: admin only 158 | _admin: address = self.future_admin 159 | assert _admin != ZERO_ADDRESS # dev: admin not set 160 | self.admin = _admin 161 | log ApplyOwnership(_admin) 162 | 163 | 164 | @external 165 | @view 166 | def gauge_types(_addr: bytes32) -> int128: 167 | """ 168 | @notice Get gauge type for address 169 | @param _addr Gauge address 170 | @return Gauge type id 171 | """ 172 | gauge_type: int128 = self.gauge_types_[self.convert_to_addr(_addr)] 173 | assert gauge_type != 0 174 | 175 | return gauge_type - 1 176 | 177 | 178 | @internal 179 | def _get_type_weight(gauge_type: int128) -> uint256: 180 | """ 181 | @notice Fill historic type weights week-over-week for missed checkins 182 | and return the type weight for the future week 183 | @param gauge_type Gauge type id 184 | @return Type weight 185 | """ 186 | t: uint256 = self.time_type_weight[gauge_type] 187 | if t > 0: 188 | w: uint256 = self.points_type_weight[gauge_type][t] 189 | for i in range(500): 190 | if t > block.timestamp: 191 | break 192 | t += WEEK 193 | self.points_type_weight[gauge_type][t] = w 194 | if t > block.timestamp: 195 | self.time_type_weight[gauge_type] = t 196 | return w 197 | else: 198 | return 0 199 | 200 | 201 | @internal 202 | def _get_sum(gauge_type: int128) -> uint256: 203 | """ 204 | @notice Fill sum of gauge weights for the same type week-over-week for 205 | missed checkins and return the sum for the future week 206 | @param gauge_type Gauge type id 207 | @return Sum of weights 208 | """ 209 | t: uint256 = self.time_sum[gauge_type] 210 | if t > 0: 211 | pt: Point = self.points_sum[gauge_type][t] 212 | for i in range(500): 213 | if t > block.timestamp: 214 | break 215 | t += WEEK 216 | d_bias: uint256 = pt.slope * WEEK 217 | if pt.bias > d_bias: 218 | pt.bias -= d_bias 219 | d_slope: uint256 = self.changes_sum[gauge_type][t] 220 | pt.slope -= d_slope 221 | else: 222 | pt.bias = 0 223 | pt.slope = 0 224 | self.points_sum[gauge_type][t] = pt 225 | if t > block.timestamp: 226 | self.time_sum[gauge_type] = t 227 | return pt.bias 228 | else: 229 | return 0 230 | 231 | 232 | @internal 233 | def _get_total() -> uint256: 234 | """ 235 | @notice Fill historic total weights week-over-week for missed checkins 236 | and return the total for the future week 237 | @return Total weight 238 | """ 239 | t: uint256 = self.time_total 240 | _n_gauge_types: int128 = self.n_gauge_types 241 | if t > block.timestamp: 242 | # If we have already checkpointed - still need to change the value 243 | t -= WEEK 244 | pt: uint256 = self.points_total[t] 245 | 246 | for gauge_type in range(100): 247 | if gauge_type == _n_gauge_types: 248 | break 249 | self._get_sum(gauge_type) 250 | self._get_type_weight(gauge_type) 251 | 252 | for i in range(500): 253 | if t > block.timestamp: 254 | break 255 | t += WEEK 256 | pt = 0 257 | # Scales as n_types * n_unchecked_weeks (hopefully 1 at most) 258 | for gauge_type in range(100): 259 | if gauge_type == _n_gauge_types: 260 | break 261 | type_sum: uint256 = self.points_sum[gauge_type][t].bias 262 | type_weight: uint256 = self.points_type_weight[gauge_type][t] 263 | pt += type_sum * type_weight 264 | self.points_total[t] = pt 265 | 266 | if t > block.timestamp: 267 | self.time_total = t 268 | return pt 269 | 270 | 271 | @internal 272 | def _get_weight(gauge_addr: address) -> uint256: 273 | """ 274 | @notice Fill historic gauge weights week-over-week for missed checkins 275 | and return the total for the future week 276 | @param gauge_addr Address of the gauge 277 | @return Gauge weight 278 | """ 279 | t: uint256 = self.time_weight[gauge_addr] 280 | if t > 0: 281 | pt: Point = self.points_weight[gauge_addr][t] 282 | for i in range(500): 283 | if t > block.timestamp: 284 | break 285 | t += WEEK 286 | d_bias: uint256 = pt.slope * WEEK 287 | if pt.bias > d_bias: 288 | pt.bias -= d_bias 289 | d_slope: uint256 = self.changes_weight[gauge_addr][t] 290 | pt.slope -= d_slope 291 | else: 292 | pt.bias = 0 293 | pt.slope = 0 294 | self.points_weight[gauge_addr][t] = pt 295 | if t > block.timestamp: 296 | self.time_weight[gauge_addr] = t 297 | return pt.bias 298 | else: 299 | return 0 300 | 301 | 302 | @external 303 | def add_gauge(_addr: bytes32, gauge_type: int128, weight: uint256 = 0): 304 | """ 305 | @notice Add gauge `addr` of type `gauge_type` with weight `weight` 306 | @param addr Gauge address 307 | @param gauge_type Gauge type 308 | @param weight Gauge weight 309 | """ 310 | addr: address = self.convert_to_addr(_addr) 311 | 312 | assert msg.sender == self.admin 313 | assert (gauge_type >= 0) and (gauge_type < self.n_gauge_types) 314 | assert self.gauge_types_[addr] == 0 # dev: cannot add the same gauge twice 315 | 316 | n: int128 = self.n_gauges 317 | self.n_gauges = n + 1 318 | self.gauges[n] = addr 319 | 320 | self.gauge_types_[addr] = gauge_type + 1 321 | next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK 322 | 323 | if weight > 0: 324 | _type_weight: uint256 = self._get_type_weight(gauge_type) 325 | _old_sum: uint256 = self._get_sum(gauge_type) 326 | _old_total: uint256 = self._get_total() 327 | 328 | self.points_sum[gauge_type][next_time].bias = weight + _old_sum 329 | self.time_sum[gauge_type] = next_time 330 | self.points_total[next_time] = _old_total + _type_weight * weight 331 | self.time_total = next_time 332 | 333 | self.points_weight[addr][next_time].bias = weight 334 | 335 | if self.time_sum[gauge_type] == 0: 336 | self.time_sum[gauge_type] = next_time 337 | self.time_weight[addr] = next_time 338 | 339 | log NewGauge(addr, gauge_type, weight) 340 | 341 | 342 | @external 343 | def checkpoint(): 344 | """ 345 | @notice Checkpoint to fill data common for all gauges 346 | """ 347 | self._get_total() 348 | 349 | 350 | @external 351 | def checkpoint_gauge(_addr: bytes32): 352 | """ 353 | @notice Checkpoint to fill data for both a specific gauge and common for all gauges 354 | @param addr Gauge address 355 | """ 356 | addr: address = self.convert_to_addr(_addr) 357 | self._get_weight(addr) 358 | self._get_total() 359 | 360 | 361 | @internal 362 | @view 363 | def _gauge_relative_weight(addr: address, time: uint256) -> uint256: 364 | """ 365 | @notice Get Gauge relative weight (not more than 1.0) normalized to 1e18 366 | (e.g. 1.0 == 1e18). Inflation which will be received by it is 367 | inflation_rate * relative_weight / 1e18 368 | @param addr Gauge address 369 | @param time Relative weight at the specified timestamp in the past or present 370 | @return Value of relative weight normalized to 1e18 371 | """ 372 | t: uint256 = time / WEEK * WEEK 373 | _total_weight: uint256 = self.points_total[t] 374 | 375 | if _total_weight > 0: 376 | gauge_type: int128 = self.gauge_types_[addr] - 1 377 | _type_weight: uint256 = self.points_type_weight[gauge_type][t] 378 | _gauge_weight: uint256 = self.points_weight[addr][t].bias 379 | return MULTIPLIER * _type_weight * _gauge_weight / _total_weight 380 | 381 | else: 382 | return 0 383 | 384 | 385 | @external 386 | @view 387 | def gauge_relative_weight(addr: bytes32, time: uint256 = block.timestamp) -> uint256: 388 | """ 389 | @notice Get Gauge relative weight (not more than 1.0) normalized to 1e18 390 | (e.g. 1.0 == 1e18). Inflation which will be received by it is 391 | inflation_rate * relative_weight / 1e18 392 | @param addr Gauge address 393 | @param time Relative weight at the specified timestamp in the past or present 394 | @return Value of relative weight normalized to 1e18 395 | """ 396 | return self._gauge_relative_weight(self.convert_to_addr(addr), time) 397 | 398 | 399 | @external 400 | def gauge_relative_weight_write(_addr: bytes32, time: uint256 = block.timestamp) -> uint256: 401 | """ 402 | @notice Get gauge weight normalized to 1e18 and also fill all the unfilled 403 | values for type and gauge records 404 | @dev Any address can call, however nothing is recorded if the values are filled already 405 | @param addr Gauge address 406 | @param time Relative weight at the specified timestamp in the past or present 407 | @return Value of relative weight normalized to 1e18 408 | """ 409 | addr: address = self.convert_to_addr(_addr) 410 | 411 | self._get_weight(addr) 412 | self._get_total() # Also calculates get_sum 413 | return self._gauge_relative_weight(addr, time) 414 | 415 | 416 | 417 | 418 | @internal 419 | def _change_type_weight(type_id: int128, weight: uint256): 420 | """ 421 | @notice Change type weight 422 | @param type_id Type id 423 | @param weight New type weight 424 | """ 425 | old_weight: uint256 = self._get_type_weight(type_id) 426 | old_sum: uint256 = self._get_sum(type_id) 427 | _total_weight: uint256 = self._get_total() 428 | next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK 429 | 430 | _total_weight = _total_weight + old_sum * weight - old_sum * old_weight 431 | self.points_total[next_time] = _total_weight 432 | self.points_type_weight[type_id][next_time] = weight 433 | self.time_total = next_time 434 | self.time_type_weight[type_id] = next_time 435 | 436 | log NewTypeWeight(type_id, next_time, weight, _total_weight) 437 | 438 | 439 | @external 440 | def add_type(_name: String[64], weight: uint256 = 0): 441 | """ 442 | @notice Add gauge type with name `_name` and weight `weight` 443 | @param _name Name of gauge type 444 | @param weight Weight of gauge type 445 | """ 446 | assert msg.sender == self.admin 447 | type_id: int128 = self.n_gauge_types 448 | self.gauge_type_names[type_id] = _name 449 | self.n_gauge_types = type_id + 1 450 | if weight != 0: 451 | self._change_type_weight(type_id, weight) 452 | log AddType(_name, type_id) 453 | 454 | 455 | @external 456 | def change_type_weight(type_id: int128, weight: uint256): 457 | """ 458 | @notice Change gauge type `type_id` weight to `weight` 459 | @param type_id Gauge type id 460 | @param weight New Gauge weight 461 | """ 462 | assert msg.sender == self.admin 463 | self._change_type_weight(type_id, weight) 464 | 465 | 466 | @internal 467 | def _change_gauge_weight(addr: address, weight: uint256): 468 | # Change gauge weight 469 | # Only needed when testing in reality 470 | gauge_type: int128 = self.gauge_types_[addr] - 1 471 | old_gauge_weight: uint256 = self._get_weight(addr) 472 | type_weight: uint256 = self._get_type_weight(gauge_type) 473 | old_sum: uint256 = self._get_sum(gauge_type) 474 | _total_weight: uint256 = self._get_total() 475 | next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK 476 | 477 | self.points_weight[addr][next_time].bias = weight 478 | self.time_weight[addr] = next_time 479 | 480 | new_sum: uint256 = old_sum + weight - old_gauge_weight 481 | self.points_sum[gauge_type][next_time].bias = new_sum 482 | self.time_sum[gauge_type] = next_time 483 | 484 | _total_weight = _total_weight + new_sum * type_weight - old_sum * type_weight 485 | self.points_total[next_time] = _total_weight 486 | self.time_total = next_time 487 | 488 | log NewGaugeWeight(addr, block.timestamp, weight, _total_weight) 489 | 490 | 491 | @external 492 | def change_gauge_weight(addr: bytes32, weight: uint256): 493 | """ 494 | @notice Change weight of gauge `addr` to `weight` 495 | @param addr `GaugeController` contract address 496 | @param weight New Gauge weight 497 | """ 498 | assert msg.sender == self.admin 499 | self._change_gauge_weight(self.convert_to_addr(addr), weight) 500 | 501 | 502 | @external 503 | def vote_for_gauge_weights(_gauge_addr_bytes32: bytes32, _user_weight: uint256): 504 | """ 505 | @notice Allocate voting power for changing pool weights 506 | @param _gauge_addr Gauge which `msg.sender` votes for 507 | @param _user_weight Weight for a gauge in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0 508 | """ 509 | _gauge_addr: address = self.convert_to_addr(_gauge_addr_bytes32) 510 | 511 | escrow: address = self.voting_escrow 512 | slope: uint256 = convert(VotingEscrow(escrow).get_last_user_slope(convert(msg.sender,bytes32)), uint256) 513 | lock_end: uint256 = VotingEscrow(escrow).locked__end(convert(msg.sender,bytes32)) 514 | _n_gauges: int128 = self.n_gauges 515 | next_time: uint256 = (block.timestamp + WEEK) / WEEK * WEEK 516 | assert lock_end > next_time, "Your token lock expires too soon" 517 | assert (_user_weight >= 0) and (_user_weight <= 10000), "You used all your voting power" 518 | # assert block.timestamp >= self.last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often" 519 | 520 | gauge_type: int128 = self.gauge_types_[_gauge_addr] - 1 521 | assert gauge_type >= 0, "Gauge not added" 522 | # Prepare slopes and biases in memory 523 | old_slope: VotedSlope = self.vote_user_slopes[msg.sender][_gauge_addr] 524 | old_dt: uint256 = 0 525 | if old_slope.end > next_time: 526 | old_dt = old_slope.end - next_time 527 | old_bias: uint256 = old_slope.slope * old_dt 528 | new_slope: VotedSlope = VotedSlope({ 529 | slope: slope * _user_weight / 10000, 530 | end: lock_end, 531 | power: _user_weight 532 | }) 533 | new_dt: uint256 = lock_end - next_time # dev: raises when expired 534 | new_bias: uint256 = new_slope.slope * new_dt 535 | 536 | # Check and update powers (weights) used 537 | power_used: uint256 = self.vote_user_power[msg.sender] 538 | power_used = power_used + new_slope.power - old_slope.power 539 | self.vote_user_power[msg.sender] = power_used 540 | assert (power_used >= 0) and (power_used <= 10000), 'Used too much power' 541 | 542 | ## Remove old and schedule new slope changes 543 | # Remove slope changes for old slopes 544 | # Schedule recording of initial slope for next_time 545 | old_weight_bias: uint256 = self._get_weight(_gauge_addr) 546 | old_weight_slope: uint256 = self.points_weight[_gauge_addr][next_time].slope 547 | old_sum_bias: uint256 = self._get_sum(gauge_type) 548 | old_sum_slope: uint256 = self.points_sum[gauge_type][next_time].slope 549 | 550 | self.points_weight[_gauge_addr][next_time].bias = max(old_weight_bias + new_bias, old_bias) - old_bias 551 | self.points_sum[gauge_type][next_time].bias = max(old_sum_bias + new_bias, old_bias) - old_bias 552 | if old_slope.end > next_time: 553 | self.points_weight[_gauge_addr][next_time].slope = max(old_weight_slope + new_slope.slope, old_slope.slope) - old_slope.slope 554 | self.points_sum[gauge_type][next_time].slope = max(old_sum_slope + new_slope.slope, old_slope.slope) - old_slope.slope 555 | else: 556 | self.points_weight[_gauge_addr][next_time].slope += new_slope.slope 557 | self.points_sum[gauge_type][next_time].slope += new_slope.slope 558 | if old_slope.end > block.timestamp: 559 | # Cancel old slope changes if they still didn't happen 560 | self.changes_weight[_gauge_addr][old_slope.end] -= old_slope.slope 561 | self.changes_sum[gauge_type][old_slope.end] -= old_slope.slope 562 | # Add slope changes for new slopes 563 | self.changes_weight[_gauge_addr][new_slope.end] += new_slope.slope 564 | self.changes_sum[gauge_type][new_slope.end] += new_slope.slope 565 | 566 | self._get_total() 567 | 568 | self.vote_user_slopes[msg.sender][_gauge_addr] = new_slope 569 | 570 | # Record last action time 571 | self.last_user_vote[msg.sender][_gauge_addr] = block.timestamp 572 | 573 | log VoteForGauge(block.timestamp, msg.sender, _gauge_addr, _user_weight) 574 | 575 | 576 | @external 577 | @view 578 | def get_gauge_weight(_addr: bytes32) -> uint256: 579 | """ 580 | @notice Get current gauge weight 581 | @param addr Gauge address 582 | @return Gauge weight 583 | """ 584 | addr: address = self.convert_to_addr(_addr) 585 | return self.points_weight[addr][self.time_weight[addr]].bias 586 | 587 | 588 | @external 589 | @view 590 | def get_type_weight(type_id: int128) -> uint256: 591 | """ 592 | @notice Get current type weight 593 | @param type_id Type id 594 | @return Type weight 595 | """ 596 | return self.points_type_weight[type_id][self.time_type_weight[type_id]] 597 | 598 | 599 | @external 600 | @view 601 | def get_total_weight() -> uint256: 602 | """ 603 | @notice Get current total (type-weighted) weight 604 | @return Total weight 605 | """ 606 | return self.points_total[self.time_total] 607 | 608 | 609 | @external 610 | @view 611 | def get_weights_sum_per_type(type_id: int128) -> uint256: 612 | """ 613 | @notice Get sum of gauge weights per type 614 | @param type_id Type id 615 | @return Sum of gauge weights 616 | """ 617 | return self.points_sum[type_id][self.time_sum[type_id]].bias -------------------------------------------------------------------------------- /contracts/Minter.vy: -------------------------------------------------------------------------------- 1 | interface LiquidityGauge: 2 | # Presumably, other gauges will provide the same interfaces 3 | def integrate_fraction(addr: address) -> uint256: view 4 | def user_checkpoint(addr: address) -> bool: nonpayable 5 | 6 | interface VAULT: 7 | def mint(_to: address, _value: uint256): nonpayable 8 | 9 | interface GaugeController: 10 | def gauge_types(addr: address) -> int128: view 11 | 12 | 13 | event Minted: 14 | recipient: indexed(address) 15 | gauge: address 16 | minted: uint256 17 | 18 | 19 | vault: public(address) 20 | 21 | REDUCER: constant(uint256) = 1461501637330902918203684832716283019655932542975 #0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 22 | 23 | 24 | # user -> gauge -> value 25 | minted: public(HashMap[address, HashMap[address, uint256]]) 26 | 27 | # minter -> user -> can mint? 28 | allowed_to_mint_for: public(HashMap[address, HashMap[address, bool]]) 29 | able_to_mint: public(HashMap[address, bool]) 30 | 31 | admin: public(address) # Can be a smart contract 32 | future_admin: public(address) # Can be a smart contract 33 | 34 | 35 | event CommitOwnership: 36 | admin: address 37 | 38 | event ApplyOwnership: 39 | admin: address 40 | 41 | initialed: public(bool) 42 | 43 | 44 | @internal 45 | @view 46 | def convert_to_addr(src: bytes32) -> address: 47 | # convert bytes32 to addr,reduce 0x41 48 | return convert(bitwise_and(REDUCER, convert(src, uint256)),address) 49 | 50 | 51 | 52 | @external 53 | def initial(_vault: address, _admin: address): 54 | assert self.initialed == False 55 | 56 | self.initialed = True 57 | self.vault = _vault 58 | self.admin = _admin 59 | 60 | 61 | @external 62 | def set_able_to_mint(_addr: bytes32, _ok: bool): 63 | assert msg.sender == self.admin # dev: admin only 64 | addr: address = self.convert_to_addr(_addr) 65 | self.able_to_mint[addr] = _ok 66 | 67 | 68 | @external 69 | def commit_transfer_ownership(_addr: bytes32): 70 | """ 71 | @notice Transfer ownership of VotingEscrow contract to `addr` 72 | @param addr Address to have ownership transferred to 73 | """ 74 | addr: address = self.convert_to_addr(_addr) 75 | 76 | assert msg.sender == self.admin # dev: admin only 77 | self.future_admin = addr 78 | log CommitOwnership(addr) 79 | 80 | 81 | @external 82 | def apply_transfer_ownership(): 83 | """ 84 | @notice Apply ownership transfer 85 | """ 86 | assert msg.sender == self.admin # dev: admin only 87 | _admin: address = self.future_admin 88 | assert _admin != ZERO_ADDRESS # dev: admin not set 89 | self.admin = _admin 90 | log ApplyOwnership(_admin) 91 | 92 | 93 | @internal 94 | def _mint_for(gauge_addr: address, _for: address): 95 | #assert GaugeController(self.controller).gauge_types(gauge_addr) >= 0 # dev: gauge is not added 96 | assert self.able_to_mint[gauge_addr] 97 | 98 | LiquidityGauge(gauge_addr).user_checkpoint(_for) 99 | total_mint: uint256 = LiquidityGauge(gauge_addr).integrate_fraction(_for) 100 | to_mint: uint256 = total_mint - self.minted[_for][gauge_addr] 101 | 102 | if to_mint != 0: 103 | VAULT(self.vault).mint(_for, to_mint) 104 | self.minted[_for][gauge_addr] = total_mint 105 | 106 | log Minted(_for, gauge_addr, total_mint) 107 | 108 | 109 | 110 | @external 111 | @nonreentrant('lock') 112 | def claim(gauge_addr: bytes32): 113 | """ 114 | @notice Mint everything which belongs to `msg.sender` and send to them 115 | @param gauge_addr `LiquidityGauge` address to get mintable amount from 116 | """ 117 | self._mint_for(self.convert_to_addr(gauge_addr), msg.sender) 118 | 119 | 120 | 121 | @external 122 | @nonreentrant('lock') 123 | def mint(gauge_addr: bytes32): 124 | """ 125 | @notice Mint everything which belongs to `msg.sender` and send to them 126 | @param gauge_addr `LiquidityGauge` address to get mintable amount from 127 | """ 128 | self._mint_for(self.convert_to_addr(gauge_addr), msg.sender) 129 | 130 | 131 | @external 132 | @nonreentrant('lock') 133 | def mint_many(gauge_addrs: bytes32[8]): 134 | """ 135 | @notice Mint everything which belongs to `msg.sender` across multiple gauges 136 | @param gauge_addrs List of `LiquidityGauge` addresses 137 | """ 138 | for i in range(8): 139 | addr: address = self.convert_to_addr(gauge_addrs[i]) 140 | 141 | if addr == ZERO_ADDRESS: 142 | break 143 | self._mint_for(addr, msg.sender) 144 | 145 | 146 | @external 147 | @nonreentrant('lock') 148 | def mint_for(_gauge_addr: bytes32, _forwhom: bytes32): 149 | """ 150 | @notice Mint tokens for `_for` 151 | @dev Only possible when `msg.sender` has been approved via `toggle_approve_mint` 152 | @param gauge_addr `LiquidityGauge` address to get mintable amount from 153 | @param _for Address to mint to 154 | """ 155 | gauge_addr: address = self.convert_to_addr(_gauge_addr) 156 | _for: address = self.convert_to_addr(_forwhom) 157 | 158 | if self.allowed_to_mint_for[msg.sender][_for]: 159 | self._mint_for(gauge_addr, _for) 160 | 161 | 162 | @external 163 | def toggle_approve_mint(_minting_user: bytes32): 164 | """ 165 | @notice allow `minting_user` to mint for `msg.sender` 166 | @param minting_user Address to toggle permission for 167 | """ 168 | minting_user: address = self.convert_to_addr(_minting_user) 169 | 170 | self.allowed_to_mint_for[minting_user][msg.sender] = not self.allowed_to_mint_for[minting_user][msg.sender] -------------------------------------------------------------------------------- /contracts/veSUN.vy: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Voting escrow to have time-weighted votes 4 | # Votes have a weight depending on time, so that users are committed 5 | # to the future of (whatever they are voting for). 6 | # The weight in this implementation is linear, and lock cannot be more than maxtime: 7 | # w ^ 8 | # 1 + / 9 | # | / 10 | # | / 11 | # | / 12 | # |/ 13 | # 0 +--------+------> time 14 | # maxtime (4 years?) 15 | 16 | struct Point: 17 | bias: int128 18 | slope: int128 # - dweight / dt 19 | ts: uint256 20 | blk: uint256 # block 21 | # We cannot really do block numbers per se b/c slope is per time, not per block 22 | # and per block could be fairly bad b/c Ethereum changes blocktimes. 23 | # What we can do is to extrapolate ***At functions 24 | 25 | struct LockedBalance: 26 | amount: int128 27 | end: uint256 28 | 29 | 30 | interface ERC20: 31 | def decimals() -> uint256: view 32 | def name() -> String[64]: view 33 | def symbol() -> String[32]: view 34 | def transfer(to: address, amount: uint256) -> bool: nonpayable 35 | def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable 36 | 37 | 38 | # Interface for checking whether address belongs to a whitelisted 39 | # type of a smart wallet. 40 | # When new types are added - the whole contract is changed 41 | # The check() method is modifying to be able to use caching 42 | # for individual wallet addresses 43 | interface SmartWalletChecker: 44 | def check(addr: address) -> bool: nonpayable 45 | 46 | DEPOSIT_FOR_TYPE: constant(int128) = 0 47 | CREATE_LOCK_TYPE: constant(int128) = 1 48 | INCREASE_LOCK_AMOUNT: constant(int128) = 2 49 | INCREASE_UNLOCK_TIME: constant(int128) = 3 50 | 51 | event CommitOwnership: 52 | admin: address 53 | 54 | event ApplyOwnership: 55 | admin: address 56 | 57 | event Deposit: 58 | provider: indexed(address) 59 | value: uint256 60 | locktime: indexed(uint256) 61 | type: int128 62 | ts: uint256 63 | 64 | event Withdraw: 65 | provider: indexed(address) 66 | value: uint256 67 | ts: uint256 68 | 69 | event Supply: 70 | prevSupply: uint256 71 | supply: uint256 72 | 73 | 74 | WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week 75 | MAXTIME: constant(uint256) = 4 * 365 * 86400 # 4 years 76 | MULTIPLIER: constant(uint256) = 10 ** 18 77 | 78 | token: public(address) 79 | supply: public(uint256) 80 | 81 | locked: public(HashMap[address, LockedBalance]) 82 | 83 | epoch: public(uint256) 84 | point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point 85 | user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] 86 | user_point_epoch: public(HashMap[address, uint256]) 87 | slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change 88 | 89 | # Aragon's view methods for compatibility 90 | controller: public(address) 91 | transfersEnabled: public(bool) 92 | 93 | name: public(String[64]) 94 | symbol: public(String[32]) 95 | version: public(String[32]) 96 | decimals: constant(uint256) = 18 97 | 98 | 99 | admin: public(address) # Can and will be a smart contract 100 | future_admin: public(address) 101 | 102 | staker: public(address) 103 | 104 | REDUCER: constant(uint256) = 1461501637330902918203684832716283019655932542975 #0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 105 | initialed: public(bool) 106 | 107 | @internal 108 | @view 109 | def convert_to_addr(src: bytes32) -> address: 110 | # convert bytes32 to addr,reduce 0x41 111 | return convert(bitwise_and(REDUCER, convert(src, uint256)), address) 112 | 113 | 114 | @external 115 | def initial(staker_addr: bytes32 ,token_addr: bytes32, _name: String[64], _symbol: String[32], _version: String[32]): 116 | """ 117 | @notice Contract constructor 118 | @param token_addr `ERC20SUN` token address 119 | @param _name Token name 120 | @param _symbol Token symbol 121 | @param _version Contract version - required for Aragon compatibility 122 | """ 123 | assert self.initialed == False 124 | 125 | 126 | # self.staker = convert(cc , address) 127 | self.admin = msg.sender 128 | self.staker = self.convert_to_addr(staker_addr) 129 | self.token = self.convert_to_addr(token_addr) 130 | self.point_history[0].blk = block.number 131 | self.point_history[0].ts = block.timestamp 132 | self.controller = msg.sender 133 | self.transfersEnabled = True 134 | 135 | self.name = _name 136 | self.symbol = _symbol 137 | self.version = _version 138 | self.initialed = True 139 | 140 | 141 | @external 142 | def commit_transfer_ownership(_addr: bytes32): 143 | """ 144 | @notice Transfer ownership of VotingEscrow contract to `addr` 145 | @param addr Address to have ownership transferred to 146 | """ 147 | addr: address = self.convert_to_addr(_addr) 148 | 149 | assert msg.sender == self.admin # dev: admin only 150 | self.future_admin = addr 151 | log CommitOwnership(addr) 152 | 153 | 154 | @external 155 | def apply_transfer_ownership(): 156 | """ 157 | @notice Apply ownership transfer 158 | """ 159 | assert msg.sender == self.admin # dev: admin only 160 | _admin: address = self.future_admin 161 | assert _admin != ZERO_ADDRESS # dev: admin not set 162 | self.admin = _admin 163 | log ApplyOwnership(_admin) 164 | 165 | 166 | @external 167 | @view 168 | def get_last_user_slope(_addr: bytes32) -> int128: 169 | """ 170 | @notice Get the most recently recorded rate of voting power decrease for `addr` 171 | @param addr Address of the user wallet 172 | @return Value of the slope 173 | """ 174 | addr: address = self.convert_to_addr(_addr) 175 | uepoch: uint256 = self.user_point_epoch[addr] 176 | return self.user_point_history[addr][uepoch].slope 177 | 178 | 179 | @external 180 | @view 181 | def user_point_history__ts(_addr: bytes32, _idx: uint256) -> uint256: 182 | """ 183 | @notice Get the timestamp for checkpoint `_idx` for `_addr` 184 | @param _addr User wallet address 185 | @param _idx User epoch number 186 | @return Epoch time of the checkpoint 187 | """ 188 | return self.user_point_history[self.convert_to_addr(_addr)][_idx].ts 189 | 190 | 191 | @external 192 | @view 193 | def locked__end(_addr: bytes32) -> uint256: 194 | """ 195 | @notice Get timestamp when `_addr`'s lock finishes 196 | @param _addr User wallet 197 | @return Epoch time of the lock end 198 | """ 199 | return self.locked[self.convert_to_addr(_addr)].end 200 | 201 | 202 | @internal 203 | def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance): 204 | """ 205 | @notice Record global and per-user data to checkpoint 206 | @param addr User's wallet address. No user checkpoint if 0x0 207 | @param old_locked Pevious locked amount / end lock time for the user 208 | @param new_locked New locked amount / end lock time for the user 209 | """ 210 | u_old: Point = empty(Point) 211 | u_new: Point = empty(Point) 212 | old_dslope: int128 = 0 213 | new_dslope: int128 = 0 214 | _epoch: uint256 = self.epoch 215 | 216 | if addr != ZERO_ADDRESS: 217 | # Calculate slopes and biases 218 | # Kept at zero when they have to 219 | if old_locked.end > block.timestamp and old_locked.amount > 0: 220 | u_old.slope = old_locked.amount / MAXTIME 221 | u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128) 222 | if new_locked.end > block.timestamp and new_locked.amount > 0: 223 | u_new.slope = new_locked.amount / MAXTIME 224 | u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128) 225 | 226 | # Read values of scheduled changes in the slope 227 | # old_locked.end can be in the past and in the future 228 | # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros 229 | old_dslope = self.slope_changes[old_locked.end] 230 | if new_locked.end != 0: 231 | if new_locked.end == old_locked.end: 232 | new_dslope = old_dslope 233 | else: 234 | new_dslope = self.slope_changes[new_locked.end] 235 | 236 | last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number}) 237 | if _epoch > 0: 238 | last_point = self.point_history[_epoch] 239 | last_checkpoint: uint256 = last_point.ts 240 | # initial_last_point is used for extrapolation to calculate block number 241 | # (approximately, for *At methods) and save them 242 | # as we cannot figure that out exactly from inside the contract 243 | initial_last_point: Point = last_point 244 | block_slope: uint256 = 0 # dblock/dt 245 | if block.timestamp > last_point.ts: 246 | block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts) 247 | # If last point is already recorded in this block, slope=0 248 | # But that's ok b/c we know the block in such case 249 | 250 | # Go over weeks to fill history and calculate what the current point is 251 | t_i: uint256 = (last_checkpoint / WEEK) * WEEK 252 | for i in range(255): 253 | # Hopefully it won't happen that this won't get used in 5 years! 254 | # If it does, users will be able to withdraw but vote weight will be broken 255 | t_i += WEEK 256 | d_slope: int128 = 0 257 | if t_i > block.timestamp: 258 | t_i = block.timestamp 259 | else: 260 | d_slope = self.slope_changes[t_i] 261 | last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128) 262 | last_point.slope += d_slope 263 | if last_point.bias < 0: # This can happen 264 | last_point.bias = 0 265 | if last_point.slope < 0: # This cannot happen - just in case 266 | last_point.slope = 0 267 | last_checkpoint = t_i 268 | last_point.ts = t_i 269 | last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER 270 | _epoch += 1 271 | if t_i == block.timestamp: 272 | last_point.blk = block.number 273 | break 274 | else: 275 | self.point_history[_epoch] = last_point 276 | 277 | self.epoch = _epoch 278 | # Now point_history is filled until t=now 279 | 280 | if addr != ZERO_ADDRESS: 281 | # If last point was in this block, the slope change has been applied already 282 | # But in such case we have 0 slope(s) 283 | last_point.slope += (u_new.slope - u_old.slope) 284 | last_point.bias += (u_new.bias - u_old.bias) 285 | if last_point.slope < 0: 286 | last_point.slope = 0 287 | if last_point.bias < 0: 288 | last_point.bias = 0 289 | 290 | # Record the changed point into history 291 | self.point_history[_epoch] = last_point 292 | 293 | if addr != ZERO_ADDRESS: 294 | # Schedule the slope changes (slope is going down) 295 | # We subtract new_user_slope from [new_locked.end] 296 | # and add old_user_slope to [old_locked.end] 297 | if old_locked.end > block.timestamp: 298 | # old_dslope was - u_old.slope, so we cancel that 299 | old_dslope += u_old.slope 300 | if new_locked.end == old_locked.end: 301 | old_dslope -= u_new.slope # It was a new deposit, not extension 302 | self.slope_changes[old_locked.end] = old_dslope 303 | 304 | if new_locked.end > block.timestamp: 305 | if new_locked.end > old_locked.end: 306 | new_dslope -= u_new.slope # old slope disappeared at this point 307 | self.slope_changes[new_locked.end] = new_dslope 308 | # else: we recorded it already in old_dslope 309 | 310 | # Now handle user history 311 | user_epoch: uint256 = self.user_point_epoch[addr] + 1 312 | 313 | self.user_point_epoch[addr] = user_epoch 314 | u_new.ts = block.timestamp 315 | u_new.blk = block.number 316 | self.user_point_history[addr][user_epoch] = u_new 317 | 318 | 319 | @internal 320 | def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128): 321 | """ 322 | @notice Deposit and lock tokens for a user 323 | @param _addr User's wallet address 324 | @param _value Amount to deposit 325 | @param unlock_time New time when to unlock the tokens, or 0 if unchanged 326 | @param locked_balance Previous locked amount / timestamp 327 | """ 328 | _locked: LockedBalance = locked_balance 329 | supply_before: uint256 = self.supply 330 | 331 | self.supply = supply_before + _value 332 | old_locked: LockedBalance = _locked 333 | # Adding to existing lock, or if a lock is expired - creating a new one 334 | _locked.amount += convert(_value, int128) 335 | if unlock_time != 0: 336 | _locked.end = unlock_time 337 | self.locked[_addr] = _locked 338 | 339 | # Possibilities: 340 | # Both old_locked.end could be current or expired (>/< block.timestamp) 341 | # value == 0 (extend lock) or value > 0 (add to lock or extend lock) 342 | # _locked.end > block.timestamp (always) 343 | self._checkpoint(_addr, old_locked, _locked) 344 | 345 | #if _value != 0: 346 | # assert ERC20(self.token).transferFrom(_addr, self, _value) 347 | 348 | log Deposit(_addr, _value, _locked.end, type, block.timestamp) 349 | log Supply(supply_before, supply_before + _value) 350 | 351 | 352 | @external 353 | def checkpoint(): 354 | """ 355 | @notice Record global data to checkpoint 356 | """ 357 | self._checkpoint(ZERO_ADDRESS, empty(LockedBalance), empty(LockedBalance)) 358 | 359 | 360 | @external 361 | @nonreentrant('lock') 362 | def deposit_for(_addr: address, _value: uint256): 363 | """ 364 | @notice Deposit `_value` tokens for `_addr` and add to the lock 365 | @dev Anyone (even a smart contract) can deposit for someone else, but 366 | cannot extend their locktime and deposit for a brand new user 367 | @param _addr User's wallet address 368 | @param _value Amount to add to user's lock 369 | """ 370 | assert msg.sender == self.staker # dev: staker only 371 | 372 | _locked: LockedBalance = self.locked[_addr] 373 | 374 | assert _value > 0 # dev: need non-zero value 375 | assert _locked.amount > 0, "No existing lock found" 376 | assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" 377 | 378 | self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE) 379 | 380 | 381 | @external 382 | @nonreentrant('lock') 383 | def create_lock(_addr: address, _value: uint256, _unlock_time: uint256): 384 | """ 385 | @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time` 386 | @param _value Amount to deposit 387 | @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks 388 | """ 389 | assert msg.sender == self.staker # dev: staker only 390 | assert _addr == tx.origin 391 | 392 | unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks 393 | _locked: LockedBalance = self.locked[_addr] 394 | 395 | assert _value > 0 # dev: need non-zero value 396 | assert _locked.amount == 0, "Withdraw old tokens first" 397 | assert unlock_time > block.timestamp, "Can only lock until time in the future" 398 | assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max" 399 | 400 | self._deposit_for(_addr, _value, unlock_time, _locked, CREATE_LOCK_TYPE) 401 | 402 | 403 | @external 404 | @nonreentrant('lock') 405 | def increase_amount(_addr: address, _value: uint256): 406 | """ 407 | @notice Deposit `_value` additional tokens for `msg.sender` 408 | without modifying the unlock time 409 | @param _value Amount of tokens to deposit and add to the lock 410 | """ 411 | assert msg.sender == self.staker # dev: staker only 412 | assert _addr == tx.origin 413 | 414 | _locked: LockedBalance = self.locked[_addr] 415 | 416 | assert _value > 0 # dev: need non-zero value 417 | assert _locked.amount > 0, "No existing lock found" 418 | assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" 419 | 420 | self._deposit_for(_addr, _value, 0, _locked, INCREASE_LOCK_AMOUNT) 421 | 422 | 423 | @external 424 | @nonreentrant('lock') 425 | def increase_unlock_time(_addr: address, _unlock_time: uint256): 426 | """ 427 | @notice Extend the unlock time for `msg.sender` to `_unlock_time` 428 | @param _unlock_time New epoch time for unlocking 429 | """ 430 | assert msg.sender == self.staker # dev: staker only 431 | assert _addr == tx.origin 432 | 433 | _locked: LockedBalance = self.locked[_addr] 434 | unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks 435 | 436 | assert _locked.end > block.timestamp, "Lock expired" 437 | assert _locked.amount > 0, "Nothing is locked" 438 | assert unlock_time > _locked.end, "Can only increase lock duration" 439 | assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max" 440 | 441 | self._deposit_for(_addr, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME) 442 | 443 | 444 | @external 445 | @nonreentrant('lock') 446 | def withdraw(_addr: address): 447 | """ 448 | @notice Withdraw all tokens for `msg.sender` 449 | @dev Only possible if the lock has expired 450 | """ 451 | assert msg.sender == self.staker # dev: staker only 452 | 453 | _locked: LockedBalance = self.locked[_addr] 454 | assert block.timestamp >= _locked.end, "The lock didn't expire" 455 | value: uint256 = convert(_locked.amount, uint256) 456 | 457 | old_locked: LockedBalance = _locked 458 | _locked.end = 0 459 | _locked.amount = 0 460 | self.locked[_addr] = _locked 461 | supply_before: uint256 = self.supply 462 | self.supply = supply_before - value 463 | 464 | # old_locked can have either expired <= timestamp or zero end 465 | # _locked has only 0 end 466 | # Both can have >= 0 amount 467 | self._checkpoint(_addr, old_locked, _locked) 468 | 469 | #assert ERC20(self.token).transfer(_addr, value) 470 | 471 | log Withdraw(_addr, value, block.timestamp) 472 | log Supply(supply_before, supply_before - value) 473 | 474 | 475 | # The following ERC20/minime-compatible methods are not real balanceOf and supply! 476 | # They measure the weights for the purpose of voting, so they don't represent 477 | # real coins. 478 | 479 | @internal 480 | @view 481 | def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256: 482 | """ 483 | @notice Binary search to estimate timestamp for block number 484 | @param _block Block to find 485 | @param max_epoch Don't go beyond this epoch 486 | @return Approximate timestamp for block 487 | """ 488 | # Binary search 489 | _min: uint256 = 0 490 | _max: uint256 = max_epoch 491 | for i in range(128): # Will be always enough for 128-bit numbers 492 | if _min >= _max: 493 | break 494 | _mid: uint256 = (_min + _max + 1) / 2 495 | if self.point_history[_mid].blk <= _block: 496 | _min = _mid 497 | else: 498 | _max = _mid - 1 499 | return _min 500 | 501 | 502 | @external 503 | @view 504 | def balanceOf(_addr: bytes32, _t: uint256 = block.timestamp) -> uint256: 505 | """ 506 | @notice Get the current voting power for `msg.sender` 507 | @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility 508 | @param addr User wallet address 509 | @param _t Epoch time to return voting power at 510 | @return User voting power 511 | """ 512 | addr: address = self.convert_to_addr(_addr) 513 | 514 | _epoch: uint256 = self.user_point_epoch[addr] 515 | if _epoch == 0: 516 | return 0 517 | else: 518 | last_point: Point = self.user_point_history[addr][_epoch] 519 | last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128) 520 | if last_point.bias < 0: 521 | last_point.bias = 0 522 | return convert(last_point.bias, uint256) 523 | 524 | 525 | @external 526 | @view 527 | def balanceOfAt(_addr: bytes32, _block: uint256) -> uint256: 528 | """ 529 | @notice Measure voting power of `addr` at block height `_block` 530 | @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime 531 | @param addr User's wallet address 532 | @param _block Block to calculate the voting power at 533 | @return Voting power 534 | """ 535 | # Copying and pasting totalSupply code because Vyper cannot pass by 536 | # reference yet 537 | addr: address = self.convert_to_addr(_addr) 538 | 539 | assert _block <= block.number 540 | 541 | # Binary search 542 | _min: uint256 = 0 543 | _max: uint256 = self.user_point_epoch[addr] 544 | for i in range(128): # Will be always enough for 128-bit numbers 545 | if _min >= _max: 546 | break 547 | _mid: uint256 = (_min + _max + 1) / 2 548 | if self.user_point_history[addr][_mid].blk <= _block: 549 | _min = _mid 550 | else: 551 | _max = _mid - 1 552 | 553 | upoint: Point = self.user_point_history[addr][_min] 554 | 555 | max_epoch: uint256 = self.epoch 556 | _epoch: uint256 = self.find_block_epoch(_block, max_epoch) 557 | point_0: Point = self.point_history[_epoch] 558 | d_block: uint256 = 0 559 | d_t: uint256 = 0 560 | if _epoch < max_epoch: 561 | point_1: Point = self.point_history[_epoch + 1] 562 | d_block = point_1.blk - point_0.blk 563 | d_t = point_1.ts - point_0.ts 564 | else: 565 | d_block = block.number - point_0.blk 566 | d_t = block.timestamp - point_0.ts 567 | block_time: uint256 = point_0.ts 568 | if d_block != 0: 569 | block_time += d_t * (_block - point_0.blk) / d_block 570 | 571 | upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128) 572 | if upoint.bias >= 0: 573 | return convert(upoint.bias, uint256) 574 | else: 575 | return 0 576 | 577 | 578 | @internal 579 | @view 580 | def supply_at(point: Point, t: uint256) -> uint256: 581 | """ 582 | @notice Calculate total voting power at some point in the past 583 | @param point The point (bias/slope) to start search from 584 | @param t Time to calculate the total voting power at 585 | @return Total voting power at that time 586 | """ 587 | last_point: Point = point 588 | t_i: uint256 = (last_point.ts / WEEK) * WEEK 589 | for i in range(255): 590 | t_i += WEEK 591 | d_slope: int128 = 0 592 | if t_i > t: 593 | t_i = t 594 | else: 595 | d_slope = self.slope_changes[t_i] 596 | last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128) 597 | if t_i == t: 598 | break 599 | last_point.slope += d_slope 600 | last_point.ts = t_i 601 | 602 | if last_point.bias < 0: 603 | last_point.bias = 0 604 | return convert(last_point.bias, uint256) 605 | 606 | 607 | @external 608 | @view 609 | def totalSupply(t: uint256 = block.timestamp) -> uint256: 610 | """ 611 | @notice Calculate total voting power 612 | @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility 613 | @return Total voting power 614 | """ 615 | _epoch: uint256 = self.epoch 616 | last_point: Point = self.point_history[_epoch] 617 | return self.supply_at(last_point, t) 618 | 619 | 620 | @external 621 | @view 622 | def totalSupplyAt(_block: uint256) -> uint256: 623 | """ 624 | @notice Calculate total voting power at some point in the past 625 | @param _block Block to calculate the total voting power at 626 | @return Total voting power at `_block` 627 | """ 628 | assert _block <= block.number 629 | _epoch: uint256 = self.epoch 630 | target_epoch: uint256 = self.find_block_epoch(_block, _epoch) 631 | 632 | point: Point = self.point_history[target_epoch] 633 | dt: uint256 = 0 634 | if target_epoch < _epoch: 635 | point_next: Point = self.point_history[target_epoch + 1] 636 | if point.blk != point_next.blk: 637 | dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk) 638 | else: 639 | if point.blk != block.number: 640 | dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk) 641 | # Now dt contains info on how far are we beyond point 642 | 643 | return self.supply_at(point, point.ts + dt) 644 | 645 | 646 | # Dummy methods for compatibility with Aragon 647 | 648 | @external 649 | def changeController(_newController: bytes32): 650 | """ 651 | @dev Dummy method required for Aragon compatibility 652 | """ 653 | assert msg.sender == self.controller 654 | self.controller = self.convert_to_addr(_newController) --------------------------------------------------------------------------------