Compare commits
1870 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd81a96f01 | |||
| 70dea74ac7 | |||
| e4435eed75 | |||
| 7ec712baa5 | |||
| d26025b410 | |||
| a54f6398e5 | |||
| e50c24f583 | |||
| 4d2383e171 | |||
| f03420b5c7 | |||
| ded8c9eedb | |||
| 281550b71c | |||
| 33ff9b086d | |||
| fa3a30a580 | |||
| 4e6deb3c56 | |||
| 819820517d | |||
| a6e2671690 | |||
| 54b3f14346 | |||
| 813cc2b79d | |||
| 4f7047ceec | |||
| a43069e841 | |||
| e0fe936729 | |||
| 652ae921e1 | |||
| 9673c56eba | |||
| 46d5a87c00 | |||
| f214e476e6 | |||
| 17cf519425 | |||
| 43eb8d1265 | |||
| 2d2c5f348d | |||
| df3f1fc4db | |||
| 0ee84342b7 | |||
| 51294b7480 | |||
| 34d926dd7e | |||
| 832c352c7d | |||
| 908f78d07b | |||
| 692d8c85a4 | |||
| 72ed4ca67a | |||
| dcba9f995f | |||
| 8b2ed0f570 | |||
| 9e8b74a0e2 | |||
| a7ee03565b | |||
| 10022da83d | |||
| 538655c3c0 | |||
| 0a2a33be30 | |||
| ab2604ac5b | |||
| 6ff415de6b | |||
| d23f818a59 | |||
| 44d6053b43 | |||
| f959d7e4e6 | |||
| 8f14d52b73 | |||
| 1a079d258b | |||
| 6aac66d065 | |||
| 91df66c4a9 | |||
| 8d633cb7d1 | |||
| 3b117992e1 | |||
| dd1265cb1d | |||
| 788d6a129a | |||
| 86f6eb2c9c | |||
| 661343f74c | |||
| 473d0b6884 | |||
| f2346b6749 | |||
| cf86fd9962 | |||
| 31723ed608 | |||
| 5523c1482b | |||
| ead3bcaccb | |||
| 7e7060e00e | |||
| ca822213f2 | |||
| 3f610ebc25 | |||
| 1a60b061d5 | |||
| 4f4b6ab403 | |||
| 1c8df659b9 | |||
| 75ef7b8b27 | |||
| ea2d5ad2e7 | |||
| e69248e551 | |||
| e739144cb0 | |||
| 6794c9460b | |||
| f8e3c375d1 | |||
| 58a62916de | |||
| 3214f6c256 | |||
| 5f7f660c6e | |||
| b952a82d29 | |||
| a55013e742 | |||
| aaad1f4a4f | |||
| 4fb6833e32 | |||
| de520fe25a | |||
| 7f028d6959 | |||
| eedca5d873 | |||
| e58c6d8ff4 | |||
| 8ba26f388f | |||
| 0ebc6e3529 | |||
| e1a77ab15f | |||
| dad73159df | |||
| 2c69f9f9f5 | |||
| 3f3774c5df | |||
| 8b47e96af1 | |||
| 0fbd039554 | |||
| 2c6ef8c875 | |||
| eb38c059d8 | |||
| 8a43ebac28 | |||
| b4d765b130 | |||
| d5581531de | |||
| 62dbb1b06a | |||
| 6e88df28cb | |||
| a6b8366980 | |||
| 9d06a1b64b | |||
| 7b3e7b7055 | |||
| 53235263a3 | |||
| adb861fb3b | |||
| 227473e88c | |||
| df814dd374 | |||
| 1e245aa54b | |||
| 815bfbf383 | |||
| 7ac4349710 | |||
| 08d085f2c9 | |||
| 96a2297e15 | |||
| d64d7aa5d1 | |||
| d5d9d4bd3d | |||
| 5e76b26225 | |||
| 4ff538af58 | |||
| 4e8aa43a90 | |||
| 6d18435098 | |||
| 2c711d0b05 | |||
| 5a00628ed3 | |||
| dfcc1d16c5 | |||
| 6259e13a0c | |||
| 790cc5b65a | |||
| 94cd8ef12e | |||
| 5b027e517e | |||
| d625cb45ea | |||
| 8177c4620e | |||
| 4d3e423443 | |||
| 95027565c4 | |||
| 1575cc6908 | |||
| 260e082ed1 | |||
| 3b591a48ac | |||
| d13ec90f37 | |||
| 132e5aa97b | |||
| 740ce2d440 | |||
| 91e8d0ba3c | |||
| a70ee4e3bb | |||
| 501a78e134 | |||
| 591c12d22d | |||
| 1684c57922 | |||
| bc6de21fe2 | |||
| 5e28952cbd | |||
| 0320a718e8 | |||
| d6fb3d645f | |||
| 69c8d1010e | |||
| a284569a7c | |||
| a34dd8bb1b | |||
| b1c2c43d6a | |||
| c404862cba | |||
| 2a62fb4a2d | |||
| afcdc58419 | |||
| dc1ffa79e7 | |||
| c8b466b7b5 | |||
| 5a0863ccf0 | |||
| 0c89a8b771 | |||
| f81e5f51c1 | |||
| 74b8ae9372 | |||
| c9bc4f8203 | |||
| 88a64a802e | |||
| 18691d1a6f | |||
| 785f65183c | |||
| a5af0adce4 | |||
| d89e51ac42 | |||
| 717fbc45cb | |||
| ac30219a6a | |||
| 42fab12584 | |||
| 6b17b33430 | |||
| 5dbde1a5c9 | |||
| 86de1e8b6c | |||
| d59306bd06 | |||
| a28aafdc85 | |||
| 88754d0b36 | |||
| 2fc24fc8d4 | |||
| 3a3c87b1a6 | |||
| c1d9da4052 | |||
| a81b0d0b21 | |||
| 276b32a5ec | |||
| 4fc19ddb8d | |||
| 0892f877b8 | |||
| a4596cab05 | |||
| ccc175a9ed | |||
| 2336bd3e8e | |||
| cc27aed391 | |||
| bc50d373e3 | |||
| b0db207e4e | |||
| cafe5eb985 | |||
| 8335676b0e | |||
| 96ff934ad0 | |||
| 7a6a9d1516 | |||
| f799475d93 | |||
| 446cd9f5fb | |||
| b709eae01d | |||
| 4072e853fe | |||
| a3b9fac30e | |||
| 3f16692dad | |||
| 6b0c9f4e77 | |||
| 3d7a93876a | |||
| 98967feeb2 | |||
| 206a239448 | |||
| 93cd2ecb73 | |||
| c30fad9955 | |||
| 88fa71ecd5 | |||
| 24d1bb668a | |||
| 4e8b2fa659 | |||
| 8f8fd84f1e | |||
| 8c9a831596 | |||
| 99589794db | |||
| b2970b96e5 | |||
| 7c5f6bc2c8 | |||
| 49c0e4370d | |||
| e0137d4cbe | |||
| e708b096cf | |||
| 47d4ebb734 | |||
| 634710d795 | |||
| c7315e8779 | |||
| 0b6becd49f | |||
| 88a3f95715 | |||
| d5ca39e9a2 | |||
| 16fa1834e5 | |||
| 7d01facdb8 | |||
| 8bfcb65924 | |||
| 8d105825e9 | |||
| 36951d5838 | |||
| f99bc4e0cf | |||
| 25fe191202 | |||
| 014ce9c6e6 | |||
| a1948250ab | |||
| 1f18fea1de | |||
| 1a41a503e0 | |||
| a71caa28e7 | |||
| 3742c16948 | |||
| 08d5e4a9b0 | |||
| 02722fcc03 | |||
| eb03304b70 | |||
| c463b8a67e | |||
| 4e18cdc186 | |||
| 8d908fe2c2 | |||
| 47003aebec | |||
| 34e5863cd5 | |||
| b7aff6363f | |||
| 9a11de924d | |||
| 7201e6707f | |||
| 561d1061fc | |||
| 50501a751e | |||
| f0de978a9e | |||
| 16408a1dcf | |||
| 0b3acb286a | |||
| c52625018b | |||
| 26dddee1c0 | |||
| 14f98dbb58 | |||
| 9938a85558 | |||
| c22db8104e | |||
| 2a45f99fc4 | |||
| fb9345814b | |||
| 72bbe63ec7 | |||
| ec88e9d6aa | |||
| ed12acfb0a | |||
| 5e8e8bb9f0 | |||
| 561d0eead3 | |||
| e2362ce74f | |||
| c08c6e1b84 | |||
| 16791134c1 | |||
| 76273ecd8e | |||
| d89a62e0a6 | |||
| 089f9b95af | |||
| 8740d8f7f3 | |||
| 09369d265c | |||
| 1bbdf2005d | |||
| c8a0639afe | |||
| 3f6c8245aa | |||
| 408055dfab | |||
| aa4b901788 | |||
| 8f0d4d4a41 | |||
| 046121e080 | |||
| 06e678d6ef | |||
| 8d3d181207 | |||
| 8769b19860 | |||
| a615329423 | |||
| 7a14f6c595 | |||
| a549ec673e | |||
| 76b538c226 | |||
| 7deed7a769 | |||
| 9416743a10 | |||
| d6975f8e07 | |||
| fac768cd2c | |||
| 5e6fd491f3 | |||
| 6a8d977d22 | |||
| 3c552e6e03 | |||
| 5d31ad0eb9 | |||
| 80ae402387 | |||
| c1d6e39aab | |||
| d857ab5702 | |||
| 42fe56031f | |||
| 171fa40bc8 | |||
| 005a35b02a | |||
| 84fafa7832 | |||
| e09ccd1449 | |||
| a60ae0f18b | |||
| 66ec91bc45 | |||
| 1c3410aa75 | |||
| 1f0e7cdf04 | |||
| 160bf1a6eb | |||
| 1eb55e2864 | |||
| 4b8b92c8bf | |||
| affe2456ea | |||
| 71aaca2d9d | |||
| 4b16e88747 | |||
| 93bc3b657b | |||
| 141130db27 | |||
| e45b49eab8 | |||
| 62bab225c1 | |||
| dc14c72e53 | |||
| 6b9bda872d | |||
| 6d8017d07e | |||
| 24fc709533 | |||
| f491e98d37 | |||
| 98c3a79b57 | |||
| 17bc2dabe0 | |||
| c797195f1d | |||
| 27d4293db6 | |||
| 26cf47a2be | |||
| 397c946141 | |||
| 2d4fb93087 | |||
| 54dec0cad5 | |||
| bbb9a17872 | |||
| 6729fec74a | |||
| 4ae655c368 | |||
| 324eda228c | |||
| 2dc086563f | |||
| 386848174b | |||
| d693d89d99 | |||
| ff16696c9c | |||
| ca6f95a7da | |||
| d514cdd26e | |||
| 85a9bed49e | |||
| ec825d8999 | |||
| ce0068c21e | |||
| bc14f8dc89 | |||
| 7b5ef46371 | |||
| c11acfd9d3 | |||
| ffb0ae76e3 | |||
| dc4869eb23 | |||
| 8ea1fc87c9 | |||
| 0d91b620b7 | |||
| 81336aefb5 | |||
| 7ddbfe181e | |||
| 48dd186870 | |||
| fbf235661e | |||
| d1b1e17d23 | |||
| 6e8d885ca7 | |||
| 0ccfd2dbd3 | |||
| 580b7ab816 | |||
| d3ff81b37f | |||
| 1b1dd0ca2c | |||
| e0149921b8 | |||
| 628a6e2866 | |||
| d50e608a4f | |||
| 6ab8d6a67e | |||
| 3210a8653b | |||
| 96163c87e3 | |||
| 3a1c020b7d | |||
| cfc9f840a0 | |||
| 5e7e9acfa4 | |||
| 8b778fa20d | |||
| e7397844f8 | |||
| e02c378745 | |||
| 597654db00 | |||
| 5fbf494511 | |||
| 7a8f269457 | |||
| 4a194b3a1c | |||
| 3dac7bbed7 | |||
| b159b3fee2 | |||
| e734b42fc2 | |||
| 40172c22d9 | |||
| 6f88199db9 | |||
| e8c60f92e3 | |||
| 2894059775 | |||
| 74738721dc | |||
| 450c569d78 | |||
| 4bf9ab41f8 | |||
| 83277ff916 | |||
| cb3f2ab900 | |||
| 109778c17c | |||
| ed704d82b3 | |||
| a55b7d1372 | |||
| 5a0bff465d | |||
| 4135bf9707 | |||
| 429b2242d8 | |||
| c10a097fa8 | |||
| cb7dbf8297 | |||
| 7c75b8aa89 | |||
| 08d109d877 | |||
| 3ac4add548 | |||
| 3aa7ff74b0 | |||
| 994cf9b34b | |||
| f6cd8032ba | |||
| 051cc9e446 | |||
| 54ce38c2e5 | |||
| d506e3b1a5 | |||
| 113443470c | |||
| 35090c1bf1 | |||
| 04204b8fb9 | |||
| 5e00787199 | |||
| 62f2c0531b | |||
| 4fc9fd3efe | |||
| 6d00281593 | |||
| 2b0b0bb186 | |||
| 6766b2de3b | |||
| 4942bd19e2 | |||
| 82a63f6389 | |||
| 488dd562fb | |||
| 61294a62af | |||
| f8157ac854 | |||
| 65b45726b3 | |||
| c019e7c5bb | |||
| de74c8c62c | |||
| b61faf8368 | |||
| c4f76cfa15 | |||
| 6339027a7a | |||
| 46db8a98b0 | |||
| c2077f0034 | |||
| ee4d8380d3 | |||
| 5772512058 | |||
| cd2da98d4d | |||
| e633ee9ed3 | |||
| 3bdf4bfd29 | |||
| 3b9381dfb1 | |||
| 07b1b472c0 | |||
| 4574ce45b6 | |||
| 5ed20f32cf | |||
| 14ca3aeb79 | |||
| 1d2ab4f41c | |||
| be3cfa0f90 | |||
| fcf35e5722 | |||
| a035b235b4 | |||
| 848fef5b6c | |||
| 85627ffa91 | |||
| c12076fb7e | |||
| d4e6236b2b | |||
| 5a75065659 | |||
| cb14ea3c5c | |||
| 7392d83738 | |||
| 28dd490726 | |||
| bc810db320 | |||
| dd01889434 | |||
| 731e199038 | |||
| d4d8d2ed97 | |||
| f81b80fb39 | |||
| 3fb25b604b | |||
| 742e85be7c | |||
| 69c3781043 | |||
| 0762479dd5 | |||
| 049701eacd | |||
| f85eb5b725 | |||
| 9af5af8fff | |||
| d2832ca7a9 | |||
| 2bd75a44bb | |||
| 2a4ca65404 | |||
| 3f49408fb6 | |||
| e7719ded56 | |||
| 668a648bc6 | |||
| 02fcc75bb7 | |||
| ae270b0d89 | |||
| e029096ca4 | |||
| 76eee35971 | |||
| bfff03c0d2 | |||
| 76fca3aec8 | |||
| 74c9130a59 | |||
| 15d5174139 | |||
| 05077f70f1 | |||
| faf5820f03 | |||
| 0eba5112c8 | |||
| b13bdb0249 | |||
| c028c56965 | |||
| 98716177ae | |||
| 52eeebecda | |||
| 6a5bbfc4cb | |||
| 8372b4f923 | |||
| cc1c18098c | |||
| c72281ea45 | |||
| f2a311dc4a | |||
| 8b162e39b5 | |||
| 62eb4568be | |||
| f8bfa560e9 | |||
| 212221d6ff | |||
| c239e1af8d | |||
| f590fcdfe9 | |||
| 9bded169e8 | |||
| dc739dcbe3 | |||
| fd07cc6290 | |||
| f9d9279320 | |||
| abdb20a6bd | |||
| 3e74681e77 | |||
| f6b614a7c5 | |||
| 458697be32 | |||
| af7ec04b78 | |||
| eae2384178 | |||
| 6db2d0ea39 | |||
| 3fe65ef467 | |||
| 525a672862 | |||
| aace774961 | |||
| 6fec54f17f | |||
| 58f635312a | |||
| 3dfc0a44f8 | |||
| be3c5ca504 | |||
| 4cb0926f11 | |||
| 5b6112000c | |||
| afb20119b3 | |||
| 718d0ba2a1 | |||
| 591e30b7b6 | |||
| 24f561a9a1 | |||
| 32b4eee9a3 | |||
| c5a6cae898 | |||
| 63b466901f | |||
| defcf20042 | |||
| b119b564df | |||
| 28986d2f2e | |||
| 85566faeb2 | |||
| bfd05d9646 | |||
| 574f3b89e1 | |||
| ca755ce799 | |||
| fc01c51c13 | |||
| 21c5d623d6 | |||
| 4f8af2dceb | |||
| a81b07ce91 | |||
| f55d2716a8 | |||
| ae54e6de1d | |||
| fb950c6166 | |||
| 6fb1e843cf | |||
| 78deae5a76 | |||
| e298476a90 | |||
| a5e042cedf | |||
| d57593c148 | |||
| 7c47ac7193 | |||
| 8f70c8867e | |||
| b3b83b558e | |||
| 783ac759a9 | |||
| 25eeb80b18 | |||
| 3eb2d43b06 | |||
| ecae978cb5 | |||
| e192fac276 | |||
| 3458c1e854 | |||
| dee772a55f | |||
| 1051db3802 | |||
| a8f35b6c25 | |||
| c366de3626 | |||
| 80d8da0564 | |||
| fd7c4ba8f0 | |||
| 8dd744fc18 | |||
| 828fe365ee | |||
| a65c03bc0d | |||
| 1e815f3308 | |||
| d74605641c | |||
| a9f994f063 | |||
| ef0273fde1 | |||
| da3a9ae18b | |||
| e3391cd65f | |||
| e9e177f53b | |||
| fe38eae6cd | |||
| 915ca181ba | |||
| a0a8bbb57f | |||
| 1ffcb10bd2 | |||
| 10c8fcd3b8 | |||
| cafd900ad6 | |||
| e667ec9533 | |||
| 5ac4d93ae3 | |||
| 0550d13aee | |||
| d95cdaae8e | |||
| 4a9cc8b491 | |||
| 6bd6b76d11 | |||
| 13a6ec753a | |||
| bda61cd5e5 | |||
| b42b05311b | |||
| 553c8fdec7 | |||
| 9a7e4a0112 | |||
| e141d8917a | |||
| 08840b9f8c | |||
| 0c858e2071 | |||
| 4625192940 | |||
| 2a09e50d09 | |||
| 04109f8bee | |||
| 8ef7655e7b | |||
| a05312f1b7 | |||
| abf6024faf | |||
| 6089f11c2f | |||
| 3536587665 | |||
| 7d17acfb2f | |||
| 4961a51cc7 | |||
| 94a0b8f573 | |||
| 0859c70936 | |||
| 6481b05e4f | |||
| c739d1976b | |||
| f6db5c1f30 | |||
| 2f2604d673 | |||
| 0413d58860 | |||
| 86bd4d7389 | |||
| 52d42d6e03 | |||
| 4c8642b70a | |||
| a57e638f57 | |||
| a4cee9ed37 | |||
| 6e5bf05211 | |||
| 65ef662a2b | |||
| 8b8ee57a1a | |||
| a94cf0509f | |||
| cf6e32fc54 | |||
| 3a3e643eb9 | |||
| 462d6fa2b2 | |||
| 70138680cf | |||
| e63b09c9ad | |||
| 3db4393b58 | |||
| 57630d2a64 | |||
| 2b4e023180 | |||
| b5daea6d5f | |||
| 4088540aef | |||
| 09e5888ca8 | |||
| ce51655c81 | |||
| 6dab43c3a9 | |||
| 7579828180 | |||
| 342265d290 | |||
| 5cdf2a8fdf | |||
| fbfe19d6db | |||
| ee14987721 | |||
| 5a398018ee | |||
| a73ad8cc8c | |||
| 46ce0cfa33 | |||
| a51e181e3b | |||
| db58acead1 | |||
| 2b046d79e1 | |||
| a56258945a | |||
| 0a87a4e75b | |||
| 7dcc7b372f | |||
| 9e13852135 | |||
| c9bdb1f0ae | |||
| 7fe5326c2d | |||
| b12af48a7d | |||
| 0db97c9681 | |||
| 88e4b69bfb | |||
| e6f26b46fc | |||
| 7006e4e3e6 | |||
| d244b81972 | |||
| 5b28259dac | |||
| efb5b0a28f | |||
| abeb0fbf5c | |||
| caba6b93ef | |||
| e2fa301bfb | |||
| 29eeb2828e | |||
| 75d422feca | |||
| 6f23efa970 | |||
| 1b27c13096 | |||
| f77fd4667e | |||
| 6af4ed6169 | |||
| 01f2ff1ba1 | |||
| 296223f876 | |||
| 0c4de6721d | |||
| 024cc50df4 | |||
| 6241995ad3 | |||
| d02cc020e8 | |||
| bc5c8e2c9c | |||
| 7237b1672e | |||
| 5e34a84d4a | |||
| 38448c104a | |||
| 8a5b67fb79 | |||
| aefcb3e8aa | |||
| fb36326a58 | |||
| 9b4326d91e | |||
| 6edb6dd1ca | |||
| 0ec7f4c25d | |||
| cec4f2ef57 | |||
| 52188a2127 | |||
| 83644fdb6a | |||
| 56d4063e92 | |||
| 1baa009266 | |||
| 1d2e15fbd7 | |||
| e3cbfcd44b | |||
| 4ac1070cd3 | |||
| bb2b687b46 | |||
| 38c1dd098c | |||
| 494cc6d3a5 | |||
| b174f067df | |||
| 7072b623af | |||
| 4a6d82acb9 | |||
| 9253c614d2 | |||
| 049ff9a3c7 | |||
| 73425c22d8 | |||
| 832ff3b9f9 | |||
| 635e36ff08 | |||
| 6618f9f184 | |||
| 7fcacb549b | |||
| 541e82144f | |||
| 18668870fd | |||
| 694f169832 | |||
| 7279273ebb | |||
| d89f2a4ac6 | |||
| 48375aa908 | |||
| 29957d4a47 | |||
| 4b152bd8ce | |||
| f782943099 | |||
| 3da0cf698f | |||
| e1c0f3eaef | |||
| 8bcf0ad7ab | |||
| 56b3b7a487 | |||
| bbd68b721d | |||
| 2d1e82c901 | |||
| 9a385dcb45 | |||
| 3dadd40034 | |||
| 566a774222 | |||
| 865dc9365c | |||
| 0500c9e53e | |||
| d3c5bdf4de | |||
| 7e41bf2259 | |||
| 6c1a634053 | |||
| f88e555234 | |||
| 87826f807a | |||
| b8fdc620aa | |||
| 466cf05912 | |||
| 8467143f2a | |||
| c346c09b01 | |||
| 3935b907d4 | |||
| 4e2641a915 | |||
| 973b0f883a | |||
| 6d97f09195 | |||
| 1c1ebd765d | |||
| 9eba3ef29a | |||
| e4696f7315 | |||
| 23b3807ea7 | |||
| 15b92e4097 | |||
| 922058dbc8 | |||
| b9e2c7896d | |||
| 9fbc1d71c0 | |||
| 3ae911b442 | |||
| eea376911b | |||
| 1db3037808 | |||
| 556e366ce5 | |||
| 6842518b1b | |||
| 8a775cd8d4 | |||
| 4d0f8c1da8 | |||
| 27e09007cc | |||
| 00128bd260 | |||
| f710bafae8 | |||
| 788d338c9b | |||
| 6f50f6e1ef | |||
| fd111df16d | |||
| 17958adaca | |||
| 0a05f6fa45 | |||
| 78103f3137 | |||
| f7da53cebd | |||
| 75f6dbb86c | |||
| 9d5f77db9d | |||
| b0cf6fbb7a | |||
| e3661abe84 | |||
| 1488d4f678 | |||
| adc50d465c | |||
| f6efd8aced | |||
| ad3c18eb5f | |||
| eabec630b9 | |||
| 406c83649b | |||
| 5f50b9f6e0 | |||
| 1b37c21da5 | |||
| 7144ec8db4 | |||
| f31bb101ad | |||
| 7c754d0665 | |||
| 5f7eda1f98 | |||
| f8f6d56c58 | |||
| 8ad458e955 | |||
| 8ea06b75aa | |||
| c3ae4790c7 | |||
| 5cd86f740d | |||
| aa06c9fbc1 | |||
| 100ca0b8a2 | |||
| 8669481309 | |||
| 1dc4a319ab | |||
| 08ba72afa2 | |||
| 0a905d674b | |||
| 319afebdc0 | |||
| f1e92dd184 | |||
| 9048e565d7 | |||
| 95dec684ad | |||
| 0d40f705a7 | |||
| df8a0fe8dc | |||
| 50a9b4d1e7 | |||
| 2e30b9f382 | |||
| 23869e5b2a | |||
| 3ba2a1354f | |||
| 903df56837 | |||
| 4a53ee5a13 | |||
| cfd6e7eab9 | |||
| a98814b930 | |||
| 10c763da6f | |||
| a020a55f9d | |||
| 6552adafb2 | |||
| 5756e31999 | |||
| 8ae9f0914b | |||
| 3c4a18f0df | |||
| 165e6f1948 | |||
| 750a17653d | |||
| 07b48484ff | |||
| a664fb1e45 | |||
| 2e6ef74593 | |||
| 440b65bf77 | |||
| df7996b671 | |||
| 9a698e6b7b | |||
| 915109b2c4 | |||
| 2fa86736dd | |||
| b9125001f0 | |||
| f17624194b | |||
| 7360a2532d | |||
| 72eb905c58 | |||
| ad90dbdaff | |||
| e0a2b195e4 | |||
| 1c676ac0ec | |||
| 538b6d21b1 | |||
| 3157aff844 | |||
| f4898a2e3a | |||
| 555be63c90 | |||
| 057efbfe95 | |||
| 585db1f667 | |||
| 7f6a1666fa | |||
| 0c621246d3 | |||
| 5bd798c6e1 | |||
| 5ecd2b850c | |||
| 243adff49c | |||
| 378531b709 | |||
| 9584c23594 | |||
| 64f20ec624 | |||
| 783e928ae8 | |||
| a673994de5 | |||
| 9b0e27c98c | |||
| a3712a3288 | |||
| 74e03de760 | |||
| 09e302384a | |||
| 3dc374ddcf | |||
| 90a72f9acd | |||
| 7c1625b0e0 | |||
| cd443f0d5b | |||
| d5194725b7 | |||
| 82e3ec3afe | |||
| 682def9f89 | |||
| 96aece07f3 | |||
| 508a879ef0 | |||
| 5171e46c47 | |||
| 1ee939e7c4 | |||
| 59f33c1cc1 | |||
| fb95a8b34b | |||
| d104ced953 | |||
| 1fd54698ff | |||
| 2658e01baa | |||
| 9612b891c9 | |||
| 27fc833550 | |||
| 0b3e0d74b0 | |||
| d75b4cf613 | |||
| 5aab6ff81b | |||
| f076ff5dbd | |||
| b8ab763581 | |||
| 78a9821129 | |||
| 5dbbda5435 | |||
| b1e0aa487d | |||
| 6505e28c40 | |||
| 7adf0d88a0 | |||
| fc004c0ad2 | |||
| f07c39be8a | |||
| 29ffe6f426 | |||
| ec63c604ab | |||
| 69ca2ad676 | |||
| 3d587f3043 | |||
| ae1e384a9a | |||
| 954f6110d6 | |||
| 4390b8df12 | |||
| cb88e15b33 | |||
| 1705067e65 | |||
| 618a735816 | |||
| 136de91049 | |||
| 103ff8e23d | |||
| e2bf45f262 | |||
| cdb929aada | |||
| 93d990c653 | |||
| 62af16b9f2 | |||
| 3dfd02b4b4 | |||
| 7a7bd8f6d7 | |||
| 16312fcfb9 | |||
| a81a88cca0 | |||
| 5acedf5027 | |||
| 566c601d41 | |||
| ba1d773176 | |||
| 583146f9d6 | |||
| c789941d76 | |||
| 232c355b3a | |||
| 773c7265e8 | |||
| 742c436d28 | |||
| 15214a53ce | |||
| 36b855972b | |||
| d5f4862405 | |||
| 735c03fcf3 | |||
| 4a1985f870 | |||
| 4519c237c5 | |||
| ac8279a0a5 | |||
| 7cab19a2e4 | |||
| 4ee9fd77fd | |||
| 556cdf5f1a | |||
| c760fe9a2e | |||
| 24a0bc23cf | |||
| c3178ff942 | |||
| f8cc8b844a | |||
| e341bdc1b7 | |||
| 09734e8028 | |||
| 7d20d0e2d2 | |||
| 0059b17dfc | |||
| e733b24dfe | |||
| f7c8252611 | |||
| e66e3ca523 | |||
| 24660c3e48 | |||
| ee6348a7a0 | |||
| a44303c373 | |||
| f49e9ee41b | |||
| 1ba03b164a | |||
| 9c1e703990 | |||
| f2112be4d8 | |||
| c82895fb65 | |||
| b840341a77 | |||
| ed7623273a | |||
| 9425a7e0ab | |||
| 93438eca0b | |||
| e28615628b | |||
| fa64a1ced0 | |||
| 0f553e7f9e | |||
| f4e57fd529 | |||
| a0e2bca79a | |||
| 63a0343dda | |||
| 5db7413827 | |||
| d163aeb80d | |||
| 60205944bb | |||
| 03832c130c | |||
| 9f93e25b88 | |||
| afaebc3869 | |||
| fdf57a9788 | |||
| 5609cd4c74 | |||
| a0323d446c | |||
| 0db76e98f9 | |||
| fa33fbce75 | |||
| bcc7192af7 | |||
| 0d27b7a7ff | |||
| 6d202894b6 | |||
| ff300b10e1 | |||
| 18c7efe800 | |||
| 0931062a3f | |||
| 3b4c951838 | |||
| c2c08af16d | |||
| 9e49be41b6 | |||
| dd86c97e18 | |||
| 5a8e6bda51 | |||
| c34211417c | |||
| 974c534210 | |||
| 5a58fd2de0 | |||
| faec973e09 | |||
| 65b7e515a2 | |||
| 318c4b44ea | |||
| b6e5187973 | |||
| 1e8cb4b4a4 | |||
| 9daa0d4d2f | |||
| ceb7324657 | |||
| da00d28cb7 | |||
| c23d5e0143 | |||
| d8658dbc47 | |||
| a6a4c042f5 | |||
| 6be0b410dc | |||
| 051d340171 | |||
| 0a4233f0f6 | |||
| ecc66719bd | |||
| 03d51c5677 | |||
| 268c6ab36c | |||
| 0786f31f22 | |||
| 883116e4af | |||
| 5748034cd3 | |||
| 061e7f0da7 | |||
| a138dc3970 | |||
| fa2b204535 | |||
| 73c7af102b | |||
| 18a8ae5a8a | |||
| fa65bf89be | |||
| 60c8b0e02f | |||
| acac4a7fca | |||
| 6be5ffe54f | |||
| ba843f0ad9 | |||
| 4297be6ac1 | |||
| 7d02933da3 | |||
| d2003a6fff | |||
| ef0483b4be | |||
| 9e51b2e5df | |||
| 951e946e71 | |||
| 1fccf6fa53 | |||
| 7f8fc584de | |||
| 1d1d2ba661 | |||
| 520bf7ac61 | |||
| 85f50d3e06 | |||
| e12cb97e16 | |||
| a3a7e6fa34 | |||
| 3881175d72 | |||
| ef32c04795 | |||
| 42da4d6867 | |||
| f7bb84ce4c | |||
| 5bd1c0d063 | |||
| c67d81bae1 | |||
| a5d1ac7ed0 | |||
| b8187aa529 | |||
| 877f440331 | |||
| 58892471a3 | |||
| 90ed1af972 | |||
| 21344b91ed | |||
| 3f9673c177 | |||
| fce2513827 | |||
| 3debb8820e | |||
| d3a4e31fe2 | |||
| 25ac33486c | |||
| 5803e11663 | |||
| b33372cc92 | |||
| bcb2b5d1cd | |||
| 3f2a2b5f62 | |||
| 4972bf4f23 | |||
| 3d2e5c0310 | |||
| f547b6ff27 | |||
| 5272799bdd | |||
| 1a230e3aa5 | |||
| 679b7d147c | |||
| 8effd1e02a | |||
| cb19930b83 | |||
| c6b07b33c5 | |||
| 47e37975c9 | |||
| 5472eedbad | |||
| af95fe7a93 | |||
| 705b0b0001 | |||
| 35c931126b | |||
| 6aada9d356 | |||
| da9161fc91 | |||
| 1e78adc28c | |||
| fcdf8f4618 | |||
| 8533499507 | |||
| fb4659c5d7 | |||
| a1408a3411 | |||
| f06738828b | |||
| b6b1c0130b | |||
| ea305cf32e | |||
| d41102d6c3 | |||
| 0c27ecc536 | |||
| 0f55c18595 | |||
| e5fbbda9ba | |||
| 7650210ad6 | |||
| ba125961d6 | |||
| 2bb06b1af4 | |||
| 8108f2f9b9 | |||
| 08dfaab829 | |||
| d1c121b599 | |||
| e98b187e64 | |||
| 87326f7313 | |||
| 7f3eee750a | |||
| ace35fdeda | |||
| bf4c9d700d | |||
| c7d99531a7 | |||
| ca7ee098ca | |||
| ec8efd52b4 | |||
| 9a83ced46e | |||
| 0d7bf01ac1 | |||
| 1229c28ce0 | |||
| f7ca35c9c6 | |||
| c08d1439dd | |||
| 09d437c1f9 | |||
| 492d4ac013 | |||
| 870ea2a88a | |||
| 29f4a38c0d | |||
| 1ada3b633e | |||
| 0ce14a078a | |||
| 45b81c969f | |||
| cfb7f80003 | |||
| 628dea7a08 | |||
| 22de765f3c | |||
| ae4e9845de | |||
| 95dc7525ac | |||
| f1cce7e223 | |||
| f2fb8cf23b | |||
| 9f900f6dba | |||
| 91f16a7e47 | |||
| 6bc2ae3731 | |||
| 885b76f66f | |||
| ded2b0a55c | |||
| 0f08b1d099 | |||
| aaa5ef81cf | |||
| cb791a1b21 | |||
| 0b489f4d79 | |||
| c187517395 | |||
| 89e2b7424b | |||
| 1f06fd7e66 | |||
| e5da0986bb | |||
| 0fc9578fe6 | |||
| b1ea59e814 | |||
| 7d48d79b53 | |||
| 6d5a73815d | |||
| d1f336d8fd | |||
| 48072e439d | |||
| 9700e6b4bd | |||
| 722879b32d | |||
| 943a663a31 | |||
| 2acb1abb1f | |||
| 4eb8d2bb10 | |||
| 04fc35707d | |||
| 09014f90f9 | |||
| 0c667ca32a | |||
| 7c4a2a0a29 | |||
| c801c8d2bb | |||
| 98e862e162 | |||
| b9d72d2991 | |||
| c6612871fb | |||
| b991fca341 | |||
| ef3bbbcf40 | |||
| 32f0fe5d08 | |||
| 6d987f1418 | |||
| de99bccbfd | |||
| 0cc882ef9d | |||
| 3f4420527b | |||
| 21d06999f6 | |||
| f3803d3a5d | |||
| c6da044dc5 | |||
| 02a6b05bde | |||
| 242400e9ea | |||
| 8c1daaab57 | |||
| 7f62c8e145 | |||
| e2876b947d | |||
| ed3a513f86 | |||
| b72088a90a | |||
| 8b58fc7191 | |||
| d9fa59f30a | |||
| 13e2132b69 | |||
| 298e1b5fdc | |||
| 5294f3b9b7 | |||
| b191e67552 | |||
| fac773ac99 | |||
| 82769e54c3 | |||
| 022f2bc177 | |||
| ccf79ee483 | |||
| ecf0725dd1 | |||
| d3ef93a9a5 | |||
| 47a7a575e8 | |||
| ff8fe4be7e | |||
| d7a6e7be2e | |||
| a81e948fc9 | |||
| ccfd370b89 | |||
| 91516b2318 | |||
| 52f7b6e03b | |||
| 64ab594ad6 | |||
| 394987f365 | |||
| df4b317fb3 | |||
| 142f6c3f05 | |||
| 414adb94f0 | |||
| 7ee98cf525 | |||
| d418da6b91 | |||
| 2a0f2a8ba8 | |||
| 5513dfd3cf | |||
| c18ffd7c81 | |||
| 424ab2a024 | |||
| e2b97d6712 | |||
| 217a616fde | |||
| 56de324afe | |||
| fa36244bec | |||
| 4fe31f8d4d | |||
| cfa9ad9943 | |||
| fd981ad315 | |||
| 5f9a86a8f2 | |||
| 956d6632d9 | |||
| 0c7b2ec09e | |||
| 4cbcaf35e4 | |||
| 73baf0b635 | |||
| 78dc37739f | |||
| ac0a4327a9 | |||
| c20e620a06 | |||
| 4bc45e35c2 | |||
| b362e886ec | |||
| ca38899ede | |||
| 61973af29f | |||
| 087d314daf | |||
| 6e3de4d48a | |||
| dc3f92210b | |||
| 1ff8a97d11 | |||
| dd226e1987 | |||
| d0f67f99a7 | |||
| e156b617b9 | |||
| 837828967a | |||
| 3843c9beb7 | |||
| c75b016919 | |||
| ad290e7181 | |||
| 364da52a49 | |||
| e50e496f5b | |||
| c4be4d16e8 | |||
| 6f40ed3fbf | |||
| 57551c8516 | |||
| b7452960be | |||
| 32f55009af | |||
| a4320108d5 | |||
| 05085aa605 | |||
| 528c23d679 | |||
| 715ec806f0 | |||
| 5bb8d0245b | |||
| c986dfb256 | |||
| 6d5ef3ef5f | |||
| 2663c5ee0f | |||
| 1075898b06 | |||
| c7705712ba | |||
| 13e113df81 | |||
| cca71ab709 | |||
| 8b10cb38d8 | |||
| 3e139baa50 | |||
| b6513f6ad7 | |||
| 4ca719bf8b | |||
| 3216fd9729 | |||
| b734bf3fa5 | |||
| 3b10205c8e | |||
| d2ab599bd3 | |||
| 281bcb747e | |||
| e03c05774b | |||
| 05d1618d01 | |||
| 885378e421 | |||
| 0292d793f3 | |||
| f4566aaade | |||
| d8a5ffeee9 | |||
| 9edee8a7a8 | |||
| bbb245fc43 | |||
| cd6f52997e | |||
| c4e5daff86 | |||
| 96a7113a16 | |||
| 21ceabc77c | |||
| 7c19c56844 | |||
| 63a3665a17 | |||
| 92a60b2587 | |||
| a0f6bd3926 | |||
| 402c6ab4cc | |||
| 6668a4d05c | |||
| bd0778461d | |||
| 0408e51ae6 | |||
| 5b5d91c9d4 | |||
| 1bc923b6a2 | |||
| d704b5257b | |||
| 06ae096586 | |||
| 0f9adc1749 | |||
| feb7ead005 | |||
| 7415ebe8bc | |||
| 24c37c1f9e | |||
| c8bef057f8 | |||
| 97894a140d | |||
| 4476a2d531 | |||
| 7554ea84a3 | |||
| 3118bb33ac | |||
| 7c3606a19b | |||
| d6616fdb0d | |||
| c8aab57d8c | |||
| 3ae631c81d | |||
| 0afba1fb9d | |||
| a65bcfdbe8 | |||
| 60a73c6365 | |||
| 5112b6b1b5 | |||
| 865aae0106 | |||
| 5ccecab631 | |||
| a3c6e5f088 | |||
| a8d6f13c94 | |||
| e7b22483af | |||
| 90058040f0 | |||
| 63c89c772b | |||
| 9975a1a0c2 | |||
| eb34ae67ff | |||
| f1db30955d | |||
| c173392e15 | |||
| c9630526e2 | |||
| 8176a901a9 | |||
| baaadde3c5 | |||
| 68ad1ed18f | |||
| d14007d035 | |||
| a949d4e2bf | |||
| ed2c289c2c | |||
| 7acd60d152 | |||
| 0449062126 | |||
| 65e4ffde58 | |||
| e2813df879 | |||
| ca19cea05e | |||
| 3296ca7219 | |||
| cb458f3250 | |||
| 21d2b90d74 | |||
| ab19138f1f | |||
| 970122a9d5 | |||
| 62065cf043 | |||
| 03b61e074e | |||
| 1350f49939 | |||
| 1c22c79a32 | |||
| 08cea10aa7 | |||
| 82147dc2ee | |||
| e4d2830f1d | |||
| 19417c7cd5 | |||
| 5dfe7215a9 | |||
| 04a1b21874 | |||
| f905a14f5f | |||
| 4a531fe588 | |||
| 25dec297bf | |||
| 8e58cda8c3 | |||
| e7bded655b | |||
| 5f357d85de | |||
| 58a3368215 | |||
| 941d305044 | |||
| 1b938e39a8 | |||
| 7988c044e0 | |||
| 0ac83ce289 | |||
| 8975289c6b | |||
| 6102ddf675 | |||
| 5a67a69f80 | |||
| f2512a4f7b | |||
| 06be0ab39a | |||
| d775e315cc | |||
| e4a8a5533e | |||
| 88dc1ca6fc | |||
| 1e0f07751e | |||
| 73abd54cbe | |||
| 7d7cbcad14 | |||
| e493fb3e95 | |||
| 8c40a5b275 | |||
| d6698c3bfb | |||
| b1e811430a | |||
| 06983a2075 | |||
| d037217769 | |||
| 410e4f8ab9 | |||
| 54d2aec232 | |||
| d006a750ee | |||
| 5376e5d349 | |||
| e5805c4979 | |||
| 73cbd6a38b | |||
| 7014d94108 | |||
| 62651e8147 | |||
| f9650b4cb8 | |||
| 249b5e9a4b | |||
| 67a71519d3 | |||
| ccdeca65a1 | |||
| fbddf5ba5b | |||
| cf0e71b4ed | |||
| 73a5903789 | |||
| 5d47aa85bc | |||
| 3eaf50005f | |||
| aec642c5ab | |||
| d0d5c2835e | |||
| 2593457503 | |||
| b56e00eb61 | |||
| 0c644cc470 | |||
| 5f92adc2e7 | |||
| ca6a5dc8a2 | |||
| cc0f89e331 | |||
| 28b95ef5be | |||
| 4237eea7d8 | |||
| 95e54173e9 | |||
| 8e980480aa | |||
| 49042f2b1a | |||
| 714f13df7e | |||
| 33047ffddc | |||
| f90700f3f1 | |||
| ab43553f0f | |||
| 9cac50e428 | |||
| bc84ea450c | |||
| 3608fb4ae9 | |||
| aa73460a8d | |||
| a20408a9e0 | |||
| 81b1e48107 | |||
| 207f315231 | |||
| c3f2ed8168 | |||
| f4c5ce3009 | |||
| 5b2de262fd | |||
| 21d19dccb5 | |||
| 19a6590306 | |||
| 8e3563968f | |||
| d839c24e4b | |||
| cdf655f770 | |||
| f5c95cc5cb | |||
| bddbbe99a7 | |||
| c4bb7b7bb9 | |||
| 1eb80ddb22 | |||
| feaf051fef | |||
| 4c997ea09e | |||
| 1c220e175d | |||
| ab4d43ce14 | |||
| 875f179fab | |||
| e84e191322 | |||
| a1b3de68c1 | |||
| b0dbe0fc59 | |||
| 6efbee609e | |||
| 21d014d481 | |||
| 7c31b98e6c | |||
| bfbbf4dfe7 | |||
| 86bfdfce96 | |||
| 9df4f7971e | |||
| f5fe378cc6 | |||
| 5183c6a307 | |||
| 80125a1be5 | |||
| 4f4d305784 | |||
| 8026a8ae2e | |||
| f9f0eec359 | |||
| 53edc70ab6 | |||
| f615fd1a46 | |||
| dd2094b86e | |||
| c247a21cb5 | |||
| a3603b9098 | |||
| 7d1c5eb71b | |||
| dba4d5023d | |||
| 5f1d8e19da | |||
| 89a90fda4c | |||
| a71047952d | |||
| 23dc15898f | |||
| bd51db5bf0 | |||
| 25b82b0db8 | |||
| a0e74cd756 | |||
| 99dca07f5f | |||
| 00947dee3d | |||
| 2d30415373 | |||
| 9113335cb3 | |||
| 76a1d7a3cd | |||
| d6e10ab786 | |||
| eaee98f227 | |||
| 4757f1c1c9 | |||
| c58d5cdfed | |||
| 5c8fa0f0cf | |||
| fcd77a4a37 | |||
| 5702a84429 | |||
| 91992eed0a | |||
| f6f918c64c | |||
| 8d166f3e35 | |||
| aae95db4e0 | |||
| 9e19d90cd9 | |||
| 53f1b11668 | |||
| 3e1519ac1e | |||
| 2c11ed59fa | |||
| bfca6bd379 | |||
| 85a081ff36 | |||
| 566519fd6b | |||
| 8077ae890e | |||
| 13b85c6848 | |||
| eda29e06b2 | |||
| 5d6a040fc3 | |||
| e557a54ce7 | |||
| b62c677420 | |||
| 8836fac06f | |||
| 77ae18f12f | |||
| 285ccecd66 | |||
| 59933b77ce | |||
| 2ec317ac03 | |||
| 3274d9db00 | |||
| c866522833 | |||
| 4edd0ee799 | |||
| 45befe6e95 | |||
| 40647ad04a | |||
| 38502031ae | |||
| 445d8987f3 | |||
| fa1cbc435f | |||
| 111cde942f | |||
| b8ccf8e0f1 | |||
| fba77596ce | |||
| f9a94788fb | |||
| 18473d0100 | |||
| 54182bf31c | |||
| 5ab6b99607 | |||
| b33b906b6e | |||
| 19a41980a1 | |||
| 6f8e672f3f | |||
| 52dc14c368 | |||
| 6148abb1b1 | |||
| a2ffe21aa2 | |||
| c876254457 | |||
| 681db1d330 | |||
| d9accb63fc | |||
| 72ccec4e6c | |||
| 54ee1c7ff5 | |||
| e855ca450a | |||
| 1efa9f6281 | |||
| d864228624 | |||
| 5274b3d84c | |||
| 9c733a7bf8 | |||
| 52c30edab8 | |||
| ba0fd11f52 | |||
| 635a381460 | |||
| 4e527a20a5 | |||
| 92b2e2800a | |||
| 5d183d0efe | |||
| 188a5fd700 | |||
| 2836b066c9 | |||
| d8a25d17aa | |||
| 5ec7204948 | |||
| 1e42749847 | |||
| c77181c976 | |||
| f9a306bbdf | |||
| c0042cd9d0 | |||
| 49eae91441 | |||
| 6a01da193e | |||
| 3e33066dde | |||
| 5bcaf7a2d8 | |||
| 0fa5270f7e | |||
| 226c6bd8f1 | |||
| 89d07a2492 | |||
| f1c7e63923 | |||
| 79c30f96aa | |||
| 9fdc91758d | |||
| 18d7e24a2c | |||
| 29e20ff9bf | |||
| b2b31b2551 | |||
| 2b3aa0d220 | |||
| f2c3af4d0f | |||
| ee5fdaf639 | |||
| 5c19876fbf | |||
| 3ae589ebc5 | |||
| 588b810256 | |||
| 6ae0915c3d | |||
| cab26fd046 | |||
| d1158ea68c | |||
| f095b62856 | |||
| 0ec1d761aa | |||
| e9cd1b5b44 | |||
| c771b2c8d5 | |||
| 3812bd8e6f | |||
| eeeb3a5be1 | |||
| e59ded9134 | |||
| 1c1fa5d968 | |||
| 0da886c9c4 | |||
| 3306cd1ece | |||
| 08ce03f6c3 | |||
| 2a29f697bf | |||
| 18b57ca043 | |||
| 0b7f1777ef | |||
| 0d1aec13ba | |||
| bd1f2ededd | |||
| 0ba85035cc | |||
| 04ddadc48f | |||
| be4b80a508 | |||
| 5d34dbecea | |||
| 377431bc4c | |||
| 571fa7f4e7 | |||
| b8c8308d77 | |||
| e869b37d0b | |||
| 623c3c05c8 | |||
| 9428bc9317 | |||
| 509d0c997d | |||
| 90831c8970 | |||
| 2187fae154 | |||
| 1b350c5837 | |||
| 5d2af083cf | |||
| eae306f259 | |||
| c5c0e22c89 | |||
| 7552af95e4 | |||
| 84f85a410f | |||
| f13d140ba7 | |||
| d95ca12415 | |||
| 136710cdd4 | |||
| b084f42029 | |||
| cdccf5ed21 | |||
| 0282c3608d | |||
| c9f328ef66 | |||
| 489d2e85f1 | |||
| 852e11c2db | |||
| 79346123ea | |||
| 1e584e4957 | |||
| d9d1082247 | |||
| f358a03043 | |||
| 8545c99fb1 | |||
| 08f8bf71d7 | |||
| 31c366af67 | |||
| b95d7e7f8a | |||
| 96e4950742 | |||
| c23c63f9e5 | |||
| a2c5659faa | |||
| 984468b583 | |||
| fbbff53528 | |||
| a83f680a63 | |||
| 132e442c1f | |||
| a71c3bce32 | |||
| d957b880fa | |||
| 602d9d9884 | |||
| fd3f2421b7 | |||
| c589256203 | |||
| d95f5e2e50 | |||
| e41daba7b4 | |||
| bd13be9174 | |||
| 2f9ae975c8 | |||
| 96fbe823ff | |||
| ca922c8fed | |||
| ee7ff81728 | |||
| b7b8fa947e | |||
| 036dd9bd6e | |||
| a289314426 | |||
| 9ab93f8ae4 | |||
| f7ff3e7e67 | |||
| 2ccc12b328 | |||
| ad18098d65 | |||
| dd6ec21951 | |||
| cc19ccfdb3 | |||
| fa004a7f5d | |||
| 64d6fefc11 | |||
| 74ef942bd1 | |||
| b6b2aea212 | |||
| 3112c1b95e | |||
| 81d04101ff | |||
| 1292b6abab | |||
| 44590fe0b6 | |||
| 6c2b7c260d | |||
| 835c23d6f7 | |||
| c794c96617 | |||
| c92b75305e | |||
| e60c7904d4 | |||
| aa4c9feabd | |||
| 86f214b7a3 | |||
| 376820d55c | |||
| b84c145c12 | |||
| b85dfb9824 | |||
| 6782f91425 | |||
| e287ea6fe5 | |||
| 9646cc951f | |||
| 2d467268e5 | |||
| 31bc231c2c | |||
| 4b2229783d | |||
| af55a843e3 | |||
| ae919629cd | |||
| 67bca857ee | |||
| 2769d0d2c7 | |||
| a3af8266db | |||
| 9d868ee846 | |||
| 2f8e844514 | |||
| 3682c6575c | |||
| 4a0a67b04c | |||
| 97a9ca5681 | |||
| 19c7414052 | |||
| 5e46cf6de4 | |||
| ed5652b0aa | |||
| 412cdfd0be | |||
| 59f03f00c5 | |||
| ea59f86cdf | |||
| 0df0e1b250 | |||
| 5a2b349992 | |||
| 963f1eb35b | |||
| bbe0eefd9f | |||
| 19c705d928 | |||
| d45f0c89db | |||
| 1ebeef7ffd | |||
| b353f7e18c | |||
| 92e68e3aaa | |||
| 07fc8c731a | |||
| 81e36d2e74 | |||
| 35d8e860ad | |||
| 8b44efd035 | |||
| 3cb4a00fce | |||
| c008f1f0c6 | |||
| dc5ca8edc4 | |||
| 7ccd224567 | |||
| cf7375d6b8 | |||
| 308a4c9907 | |||
| 269e096c5a | |||
| edfce5a3dd | |||
| ab14debb08 | |||
| 40d2eb3007 | |||
| 9ed1b44865 | |||
| 7aaf15be59 | |||
| 2a37b09cf6 | |||
| 29fa0a4ce5 | |||
| 06f7266a6e | |||
| 4c706480e5 | |||
| 4dd8575bb6 | |||
| 9c69df5962 | |||
| 1e5295d304 | |||
| 61c4318c33 | |||
| 79dd498b16 | |||
| 547a5f242a | |||
| 352ec94579 | |||
| 3772233cf5 | |||
| 7f54542172 | |||
| 3ff2e24518 | |||
| 0c617b755e | |||
| 8cc3349d1f | |||
| 41aa39afbc | |||
| 0dded493a0 | |||
| a1f168be96 | |||
| 5b93137c26 | |||
| f5ba7e7f0d | |||
| 5a64eb2e14 | |||
| 2648206394 | |||
| 77d569d8fa | |||
| 634883ce8e | |||
| de90f4458a | |||
| a39bea230e | |||
| 8d0937c54c | |||
| d6c47c4b7c | |||
| 3111291b4a | |||
| 14ec984cc4 | |||
| b14c9f4b68 | |||
| fc17a1d9f5 | |||
| c44e40c293 | |||
| 2fc0aaabb3 | |||
| ae41195863 | |||
| 99a07493f2 | |||
| 328d03a7f4 | |||
| 530c90d438 | |||
| 3654150d0c | |||
| 9cf68279c1 | |||
| 039e5b9602 | |||
| cab777b820 | |||
| 7ed22bbded | |||
| 889af764e2 | |||
| 40d15146c4 | |||
| 2c34cfbe0e | |||
| 8dd92fd6ca | |||
| 1fd993bc01 | |||
| a09cf147ea | |||
| f80c65ff67 | |||
| 07603d5875 | |||
| 3a5da75876 | |||
| e1f40e7e23 | |||
| 8617c4b4c8 | |||
| 96432bf28e | |||
| 1a9cb0786d | |||
| fd808b3ff8 | |||
| 6a658f2ac8 | |||
| d9a8cd2bef | |||
| b7d60f2f9a | |||
| 7fb90a1566 | |||
| e5b94c1481 | |||
| 79145fa53a | |||
| 3e82db2f6f | |||
| 089f14aa06 | |||
| ae02103e47 | |||
| ba83239dd8 | |||
| 14a59b2903 | |||
| 4f5715bbd7 | |||
| 312a2584e8 | |||
| c23b7ffbad | |||
| 6aa88e2b7d | |||
| 76abd1a41e | |||
| 35d70397b6 | |||
| a5b14f262e | |||
| 350aae09d2 | |||
| 63ce358f30 | |||
| 40f08a8c0b | |||
| 8c20d53789 | |||
| 576b020384 | |||
| 13eff70112 | |||
| daa301fef0 | |||
| 4b8fcbcee1 | |||
| 7361ffd26b | |||
| be5ed7364b | |||
| 73cbad2f78 | |||
| 02f9727dd8 | |||
| 503674167d | |||
| 54ceedbb1f | |||
| d17ae0b806 | |||
| fad30c3d2c | |||
| 8056433c44 | |||
| 12533bcb2b | |||
| 2da19eebd3 | |||
| a99b9da3d4 | |||
| 7142e25e78 | |||
| dd2e947976 | |||
| 357fded2b5 | |||
| 70e616db7c | |||
| 70fd243e3b | |||
| 5caecf548c | |||
| b7d56d5471 | |||
| 41c540fb1e | |||
| e7d6e86395 | |||
| c0bda4db88 | |||
| 88e8c7c94b | |||
| 9faebb5365 | |||
| 714ab21a6e | |||
| 8dae261d20 | |||
| 6fd0010325 | |||
| 7ef2730506 | |||
| 6407d7818d | |||
| c2de8ffd14 | |||
| f0e12d5301 | |||
| 21ed6bee25 | |||
| b9793abec6 | |||
| 5471fc808a | |||
| 281338409b | |||
| a833c4b4ee | |||
| bb6a24e6aa | |||
| 83bc045a56 | |||
| 6363a776f7 | |||
| f72c79d54a | |||
| 32bc7a6fe5 | |||
| 1186520617 | |||
| b5892a2b5e | |||
| 4f356e35a9 | |||
| 79eb94d3cd | |||
| 18552a8d5f | |||
| 80458ebc72 | |||
| 6d415c508b | |||
| a936cbf5fb | |||
| 1ac1ac984e | |||
| 8204f6a71f | |||
| bbe804a14b | |||
| 28cf567e41 | |||
| 5bb865218f | |||
| 94692acb14 | |||
| 384dff9abe | |||
| 5bf3907862 | |||
| c8220aeea8 | |||
| 9155bfe27f | |||
| 1c5f16ae26 | |||
| d9fdffe88e | |||
| 315f426f5c | |||
| 67dac7ae99 | |||
| a07499796a | |||
| fcab9c89f2 | |||
| cbe414864f | |||
| 0231fac514 | |||
| ff0c048ad9 | |||
| 96d6103e0b | |||
| df3c113711 | |||
| f3df2dab3d | |||
| cfef58361f | |||
| d8ab8a00f2 | |||
| 2733cb4a41 | |||
| 03765e08b9 | |||
| 9be0f100c4 | |||
| a3e312d133 | |||
| 3341dd8899 | |||
| 3bd30f7382 | |||
| 844a96d0b9 | |||
| 03dfb2965c | |||
| 9f822906b8 | |||
| eb92fd1f48 | |||
| 076cb7decd | |||
| da67e435b1 | |||
| b0da0fded0 | |||
| 58a42443df | |||
| af42c39e6c | |||
| 3acc2b4216 | |||
| f7e53d90fa | |||
| 41f84f8b40 | |||
| b17de011dc | |||
| 308e5e9cc0 | |||
| 744f567f40 | |||
| b41b125c15 | |||
| fd4de77651 | |||
| ace14d3ad7 | |||
| 5c228805ad | |||
| c87433e0c2 | |||
| f3888bb0b0 | |||
| 43ebb4fc43 | |||
| f4d6f40442 | |||
| 269981ee6a | |||
| 34bc709bc2 | |||
| 6ae041bca6 | |||
| fa1dfbd113 | |||
| a6889776f4 | |||
| 1a4e5346ed | |||
| 536262f2ec | |||
| 75c0a59bb4 | |||
| 947de54cee | |||
| ae109f69cc | |||
| 5fdeaa81d5 | |||
| db2e1d2c74 | |||
| 396680b856 | |||
| 21198bf1c1 | |||
| 9b37104655 | |||
| 3e55408cb5 | |||
| 4b21fcf5af | |||
| d062352de7 | |||
| b4f87bb8fc | |||
| 225b492109 | |||
| 7825caa1fd | |||
| b79196f911 | |||
| d8cbd5554f | |||
| b644c72eb0 | |||
| 0b8296e525 | |||
| 651f6d8b57 | |||
| 13b19612d6 | |||
| c12a1c6898 |
12
.changeset/config.json
Normal file
12
.changeset/config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||
"changelog": [
|
||||
"@changesets/changelog-github",
|
||||
{
|
||||
"repo": "OpenZeppelin/openzeppelin-contracts"
|
||||
}
|
||||
],
|
||||
"commit": false,
|
||||
"access": "public",
|
||||
"baseBranch": "master"
|
||||
}
|
||||
12
.codecov.yml
Normal file
12
.codecov.yml
Normal file
@ -0,0 +1,12 @@
|
||||
comment: off
|
||||
github_checks:
|
||||
annotations: false
|
||||
coverage:
|
||||
status:
|
||||
patch:
|
||||
default:
|
||||
target: 95%
|
||||
only_pulls: true
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
@ -6,7 +6,16 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
trim_trailing_whitespace = false
|
||||
max_line_length = 120
|
||||
|
||||
[*.sol]
|
||||
indent_size = 4
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
|
||||
[*.{adoc,md}]
|
||||
max_line_length = 0
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
# configure your infura api key (not technically required)
|
||||
INFURA_API_KEY=
|
||||
|
||||
# change the mnemonic that your hd wallet is seeded with
|
||||
MNEMONIC=
|
||||
59
.eslintrc
59
.eslintrc
@ -1,55 +1,20 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends" : [
|
||||
"standard",
|
||||
"plugin:promise/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"promise"
|
||||
"eslint:recommended",
|
||||
"prettier",
|
||||
],
|
||||
"env": {
|
||||
"browser" : true,
|
||||
"node" : true,
|
||||
"mocha" : true,
|
||||
"jest" : true
|
||||
"es2022": true,
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
},
|
||||
"globals" : {
|
||||
"artifacts": false,
|
||||
"contract": false,
|
||||
"assert": false,
|
||||
"web3": false
|
||||
},
|
||||
"rules": {
|
||||
|
||||
// Strict mode
|
||||
"strict": ["error", "global"],
|
||||
|
||||
// Code style
|
||||
"camelcase": ["error", {"properties": "always"}],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"comma-spacing": ["error", {"before": false, "after": true}],
|
||||
"dot-notation": ["error", {"allowKeywords": true, "allowPattern": ""}],
|
||||
"eol-last": ["error", "always"],
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"generator-star-spacing": ["error", "before"],
|
||||
"indent": ["error", 2],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"max-len": ["error", 120, 2],
|
||||
"no-debugger": "off",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
"no-redeclare": ["error", {"builtinGlobals": true}],
|
||||
"no-trailing-spaces": ["error", { "skipBlankLines": false }],
|
||||
"no-undef": "error",
|
||||
"no-use-before-define": "off",
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"prefer-const": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-function-paren": ["error", "always"],
|
||||
|
||||
"promise/always-return": "off",
|
||||
"promise/avoid-new": "off",
|
||||
"artifacts": "readonly",
|
||||
"contract": "readonly",
|
||||
"web3": "readonly",
|
||||
"extendEnvironment": "readonly",
|
||||
"expect": "readonly",
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
*.sol linguist-language=Solidity
|
||||
34
.github/ISSUE_TEMPLATE.md
vendored
34
.github/ISSUE_TEMPLATE.md
vendored
@ -1,34 +0,0 @@
|
||||
## 🎉 Description
|
||||
|
||||
<!-- Briefly describe the issue you are experiencing (or the feature you want to see added to OpenZeppelin). Tell us what you were trying to do and what happened instead. **Remember, this is _not_ a place to ask for help debugging code; for that, we welcome you in the [OpenZeppelin Slack Channel](https://slack.openzeppelin.org/).** -->
|
||||
|
||||
- [ ] 🐛 This is a bug report.
|
||||
- [ ] 📈 This is a feature request.
|
||||
|
||||
<!-- Please check one of the above by placing an x in the box. -->
|
||||
|
||||
## 💻 Environment
|
||||
|
||||
Next, we need to know what your environment looks like.
|
||||
|
||||
- Which version of OpenZeppelin are you using?
|
||||
- What network are you deploying to? Ganache? Ropsten?
|
||||
- How are you deploying your OpenZeppelin-backed contracts? truffle? Remix? Let us know!
|
||||
|
||||
## 📝 Details
|
||||
|
||||
Describe the problem you have been experiencing in more detail. Include as much information as you think is relevant. Keep in mind that transactions can fail for many reasons; context is key here.
|
||||
|
||||
## 🔢 Code To Reproduce Issue [ Good To Have ]
|
||||
|
||||
Please remember that with sample code it's easier to reproduce the bug and it's much faster to fix it.
|
||||
|
||||
```
|
||||
insert short code snippets here
|
||||
```
|
||||
|
||||
<!-- If your code is larger, consider linking us to a repo illustrating your issue. -->
|
||||
|
||||
## 👍 Other Information
|
||||
|
||||
<!-- List any other information that is relevant to your issue. Error logs, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->
|
||||
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug in OpenZeppelin Contracts
|
||||
|
||||
---
|
||||
|
||||
<!-- Briefly describe the issue you're experiencing. Tell us what you were trying to do and what happened instead. -->
|
||||
|
||||
<!-- Remember, this is not a place to ask for help debugging code. For that, we welcome you in the OpenZeppelin Community Forum: https://forum.openzeppelin.com/. -->
|
||||
|
||||
**💻 Environment**
|
||||
|
||||
<!-- Tell us what version of OpenZeppelin Contracts you're using, and how you're using it: Truffle, Remix, etc. -->
|
||||
|
||||
**📝 Details**
|
||||
|
||||
<!-- Describe the problem you have been experiencing in more detail. Include as much information as you think is relevant. Keep in mind that transactions can fail for many reasons; context is key here. -->
|
||||
|
||||
**🔢 Code to reproduce bug**
|
||||
|
||||
<!-- We will be able to better help if you provide a minimal example that triggers the bug. -->
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Questions & Support Requests
|
||||
url: https://forum.openzeppelin.com/c/support/contracts/18
|
||||
about: Ask in the OpenZeppelin Forum
|
||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for OpenZeppelin Contracts
|
||||
|
||||
---
|
||||
|
||||
**🧐 Motivation**
|
||||
<!-- Is your feature request related to a specific problem? Is it just a crazy idea? Tell us about it! -->
|
||||
|
||||
**📝 Details**
|
||||
<!-- Please describe your feature request in detail. -->
|
||||
|
||||
<!-- Make sure that you have reviewed the OpenZeppelin Contracts Contributor Guidelines. -->
|
||||
<!-- https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CONTRIBUTING.md -->
|
||||
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,17 +1,20 @@
|
||||
<!-- 0. 🎉 Thank you for submitting a PR! -->
|
||||
<!-- Thank you for your interest in contributing to OpenZeppelin! -->
|
||||
|
||||
<!-- 1. **Does this close any open issues?** If so, list them here. If not, remove the `Fixes #` line. -->
|
||||
<!-- Consider opening an issue for discussion prior to submitting a PR. -->
|
||||
<!-- New features will be merged faster if they were first discussed and designed with the team. -->
|
||||
|
||||
Fixes #
|
||||
Fixes #???? <!-- Fill in with issue number -->
|
||||
|
||||
# 🚀 Description
|
||||
<!-- Describe the changes introduced in this pull request. -->
|
||||
<!-- Include any context necessary for understanding the PR's purpose. -->
|
||||
|
||||
<!-- 2. Describe the changes introduced in this pull request -->
|
||||
<!-- Include any context necessary for understanding the PR's purpose. -->
|
||||
|
||||
<!-- 3. Before submitting, please review the following checklist: -->
|
||||
#### PR Checklist
|
||||
|
||||
- [ ] 📘 I've reviewed the [OpenZeppelin Contributor Guidelines](../blob/master/CONTRIBUTING.md)
|
||||
- [ ] ✅ I've added tests where applicable to test my new functionality.
|
||||
- [ ] 📖 I've made sure that my contracts are well-documented.
|
||||
- [ ] 🎨 I've run the JS/Solidity linters and fixed any issues (`npm run lint:fix`).
|
||||
<!-- Before merging the pull request all of the following must be complete. -->
|
||||
<!-- Feel free to submit a PR or Draft PR even if some items are pending. -->
|
||||
<!-- Some of the items may not apply. -->
|
||||
|
||||
- [ ] Tests
|
||||
- [ ] Documentation
|
||||
- [ ] Changeset entry (run `npx changeset add`)
|
||||
|
||||
49
.github/actions/gas-compare/action.yml
vendored
Normal file
49
.github/actions/gas-compare/action.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Compare gas costs
|
||||
inputs:
|
||||
token:
|
||||
description: github token
|
||||
required: true
|
||||
report:
|
||||
description: report to read from
|
||||
required: false
|
||||
default: gasReporterOutput.json
|
||||
out_report:
|
||||
description: report to read
|
||||
required: false
|
||||
default: ${{ github.ref_name }}.gasreport.json
|
||||
ref_report:
|
||||
description: report to read from
|
||||
required: false
|
||||
default: ${{ github.base_ref }}.gasreport.json
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download reference report
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
RUN_ID=`gh run list --repo ${{ github.repository }} --branch ${{ github.base_ref }} --workflow ${{ github.workflow }} --limit 100 --json 'conclusion,databaseId,event' --jq 'map(select(.conclusion=="success" and .event!="pull_request"))[0].databaseId'`
|
||||
gh run download ${RUN_ID} --repo ${{ github.repository }} -n gasreport
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
id: reference
|
||||
- name: Compare reports
|
||||
if: steps.reference.outcome == 'success' && github.event_name == 'pull_request'
|
||||
run: |
|
||||
node scripts/checks/compareGasReports.js ${{ inputs.report }} ${{ inputs.ref_report }} >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
STYLE: markdown
|
||||
shell: bash
|
||||
- name: Rename report for upload
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
mv ${{ inputs.report }} ${{ inputs.out_report }}
|
||||
shell: bash
|
||||
- name: Save report
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gasreport
|
||||
path: ${{ inputs.out_report }}
|
||||
19
.github/actions/setup/action.yml
vendored
Normal file
19
.github/actions/setup/action.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Setup
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14.x
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: npm-v3-${{ hashFiles('**/package-lock.json') }}
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
shell: bash
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
SKIP_COMPILE: true
|
||||
55
.github/actions/storage-layout/action.yml
vendored
Normal file
55
.github/actions/storage-layout/action.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Compare storage layouts
|
||||
inputs:
|
||||
token:
|
||||
description: github token
|
||||
required: true
|
||||
buildinfo:
|
||||
description: compilation artifacts
|
||||
required: false
|
||||
default: artifacts/build-info/*.json
|
||||
layout:
|
||||
description: extracted storage layout
|
||||
required: false
|
||||
default: HEAD.layout.json
|
||||
out_layout:
|
||||
description: storage layout to upload
|
||||
required: false
|
||||
default: ${{ github.ref_name }}.layout.json
|
||||
ref_layout:
|
||||
description: storage layout for the reference branch
|
||||
required: false
|
||||
default: ${{ github.base_ref }}.layout.json
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Extract layout
|
||||
run: |
|
||||
node scripts/checks/extract-layout.js ${{ inputs.buildinfo }} > ${{ inputs.layout }}
|
||||
shell: bash
|
||||
- name: Download reference
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
RUN_ID=`gh run list --repo ${{ github.repository }} --branch ${{ github.base_ref }} --workflow ${{ github.workflow }} --limit 100 --json 'conclusion,databaseId,event' --jq 'map(select(.conclusion=="success" and .event!="pull_request"))[0].databaseId'`
|
||||
gh run download ${RUN_ID} --repo ${{ github.repository }} -n layout
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
id: reference
|
||||
- name: Compare layouts
|
||||
if: steps.reference.outcome == 'success' && github.event_name == 'pull_request'
|
||||
run: |
|
||||
node scripts/checks/compare-layout.js --head ${{ inputs.layout }} --ref ${{ inputs.ref_layout }}
|
||||
shell: bash
|
||||
- name: Rename artifacts for upload
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
mv ${{ inputs.layout }} ${{ inputs.out_layout }}
|
||||
shell: bash
|
||||
- name: Save artifacts
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: layout
|
||||
path: ${{ inputs.out_layout }}
|
||||
18
.github/workflows/actionlint.yml
vendored
Normal file
18
.github/workflows/actionlint.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: lint workflows
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/**/*.ya?ml'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Add problem matchers
|
||||
run: |
|
||||
# https://github.com/rhysd/actionlint/blob/3a2f2c7/docs/usage.md#problem-matchers
|
||||
curl -LO https://raw.githubusercontent.com/rhysd/actionlint/main/.github/actionlint-matcher.json
|
||||
echo "::add-matcher::actionlint-matcher.json"
|
||||
- uses: docker://rhysd/actionlint:latest
|
||||
28
.github/workflows/changeset.yml
vendored
Normal file
28
.github/workflows/changeset.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: changeset
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
concurrency:
|
||||
group: changeset-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'ignore-changeset') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Include history so Changesets finds merge-base
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Check changeset
|
||||
run: npx changeset status --since=origin/${{ github.base_ref }}
|
||||
111
.github/workflows/checks.yml
vendored
Normal file
111
.github/workflows/checks.yml
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
name: checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release-v*
|
||||
pull_request: {}
|
||||
workflow_dispatch: {}
|
||||
|
||||
concurrency:
|
||||
group: checks-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=5120
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: npm run lint
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
GAS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Run tests and generate gas report
|
||||
run: npm run test
|
||||
- name: Check linearisation of the inheritance graph
|
||||
run: npm run test:inheritance
|
||||
- name: Check proceduraly generated contracts are up-to-date
|
||||
run: npm run test:generation
|
||||
- name: Compare gas costs
|
||||
uses: ./.github/actions/gas-compare
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
|
||||
tests-upgradeable:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Include history so patch conflicts are resolved automatically
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Transpile to upgradeable
|
||||
run: bash scripts/upgradeable/transpile.sh
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
- name: Check linearisation of the inheritance graph
|
||||
run: npm run test:inheritance
|
||||
- name: Check storage layout
|
||||
uses: ./.github/actions/storage-layout
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
|
||||
tests-foundry:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
- name: Run tests
|
||||
run: forge test -vv
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: npm run coverage
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
slither:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: rm foundry.toml
|
||||
- uses: crytic/slither-action@v0.3.0
|
||||
with:
|
||||
node-version: 18.15
|
||||
|
||||
codespell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run CodeSpell
|
||||
uses: codespell-project/actions-codespell@v1.0
|
||||
with:
|
||||
check_filenames: true
|
||||
skip: package-lock.json,*.pdf
|
||||
19
.github/workflows/docs.yml
vendored
Normal file
19
.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Build Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [release-v*]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: bash scripts/git-user-config.sh
|
||||
- run: node scripts/update-docs-branch.js
|
||||
- run: git push --all origin
|
||||
68
.github/workflows/formal-verification.yml
vendored
Normal file
68
.github/workflows/formal-verification.yml
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
name: formal verification
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- labeled
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
PIP_VERSION: '3.10'
|
||||
JAVA_VERSION: '11'
|
||||
SOLC_VERSION: '0.8.19'
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
apply-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Apply patches
|
||||
run: make -C certora apply
|
||||
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'formal-verification')
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: identify specs that need to be run
|
||||
id: arguments
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} = 'pull_request' ]];
|
||||
then
|
||||
RESULT=$(git diff ${{ github.event.pull_request.head.sha }}..${{ github.event.pull_request.base.sha }} --name-only certora/specs/*.spec | while IFS= read -r file; do [[ -f $file ]] && basename "${file%.spec}"; done | tr "\n" " ")
|
||||
else
|
||||
RESULT='--all'
|
||||
fi
|
||||
echo "result=$RESULT" >> "$GITHUB_OUTPUT"
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PIP_VERSION }}
|
||||
cache: 'pip'
|
||||
- name: Install python packages
|
||||
run: pip install -r requirements.txt
|
||||
- name: Install java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: Install solc
|
||||
run: |
|
||||
wget https://github.com/ethereum/solidity/releases/download/v${{ env.SOLC_VERSION }}/solc-static-linux
|
||||
sudo mv solc-static-linux /usr/local/bin/solc
|
||||
chmod +x /usr/local/bin/solc
|
||||
- name: Verify specification
|
||||
run: |
|
||||
make -C certora apply
|
||||
node certora/run.js ${{ steps.arguments.outputs.result }} >> "$GITHUB_STEP_SUMMARY"
|
||||
env:
|
||||
CERTORAKEY: ${{ secrets.CERTORAKEY }}
|
||||
218
.github/workflows/release-cycle.yml
vendored
Normal file
218
.github/workflows/release-cycle.yml
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
# D: Manual Dispatch
|
||||
# M: Merge release PR
|
||||
# C: Commit
|
||||
# ┌───────────┐ ┌─────────────┐ ┌────────────────┐
|
||||
# │Development├──D──►RC-Unreleased│ ┌──►Final-Unreleased│
|
||||
# └───────────┘ └─┬─────────▲─┘ │ └─┬────────────▲─┘
|
||||
# │ │ │ │ │
|
||||
# M C D M C
|
||||
# │ │ │ │ │
|
||||
# ┌▼─────────┴┐ │ ┌▼────────────┴┐
|
||||
# │RC-Released├───┘ │Final-Released│
|
||||
# └───────────┘ └──────────────┘
|
||||
name: Release Cycle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release-v*
|
||||
workflow_dispatch: {}
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
state:
|
||||
name: Check state
|
||||
permissions:
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- id: state
|
||||
name: Get state
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
TRIGGERING_ACTOR: ${{ github.triggering_actor }}
|
||||
with:
|
||||
result-encoding: string
|
||||
script: await require('./scripts/release/workflow/state.js')({ github, context, core })
|
||||
outputs:
|
||||
# Job Flags
|
||||
start: ${{ steps.state.outputs.start }}
|
||||
changesets: ${{ steps.state.outputs.changesets }}
|
||||
promote: ${{ steps.state.outputs.promote }}
|
||||
publish: ${{ steps.state.outputs.publish }}
|
||||
merge: ${{ steps.state.outputs.merge }}
|
||||
|
||||
# Global variables
|
||||
is_prerelease: ${{ steps.state.outputs.is_prerelease }}
|
||||
|
||||
start:
|
||||
needs: state
|
||||
name: Start new release candidate
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
if: needs.state.outputs.start == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: bash scripts/git-user-config.sh
|
||||
- id: start
|
||||
name: Create branch with release candidate
|
||||
run: bash scripts/release/workflow/start.sh
|
||||
- name: Re-run workflow
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
REF: ${{ steps.start.outputs.branch }}
|
||||
with:
|
||||
script: await require('./scripts/release/workflow/rerun.js')({ github, context })
|
||||
|
||||
promote:
|
||||
needs: state
|
||||
name: Promote to final release
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
if: needs.state.outputs.promote == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: bash scripts/git-user-config.sh
|
||||
- name: Exit prerelease state
|
||||
if: needs.state.outputs.is_prerelease == 'true'
|
||||
run: bash scripts/release/workflow/exit-prerelease.sh
|
||||
- name: Re-run workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: await require('./scripts/release/workflow/rerun.js')({ github, context })
|
||||
|
||||
changesets:
|
||||
needs: state
|
||||
name: Update PR to release
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: needs.state.outputs.changesets == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # To get all tags
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Set release title
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
result-encoding: string
|
||||
script: await require('./scripts/release/workflow/set-changesets-pr-title.js')({ core })
|
||||
- name: Create PR
|
||||
uses: changesets/action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PRERELEASE: ${{ needs.state.outputs.is_prerelease }}
|
||||
with:
|
||||
version: npm run version
|
||||
title: ${{ env.TITLE }}
|
||||
commit: ${{ env.TITLE }}
|
||||
body: | # Wait for support on this https://github.com/changesets/action/pull/250
|
||||
This is an automated PR for releasing ${{ github.repository }}
|
||||
Check [CHANGELOG.md](${{ github.repository }}/CHANGELOG.md)
|
||||
|
||||
publish:
|
||||
needs: state
|
||||
name: Publish to npm
|
||||
environment: npm
|
||||
permissions:
|
||||
contents: write
|
||||
if: needs.state.outputs.publish == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- id: pack
|
||||
name: Pack
|
||||
run: bash scripts/release/workflow/pack.sh
|
||||
env:
|
||||
PRERELEASE: ${{ needs.state.outputs.is_prerelease }}
|
||||
- name: Upload tarball artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ github.ref_name }}
|
||||
path: ${{ steps.pack.outputs.tarball }}
|
||||
- name: Tag
|
||||
run: npx changeset tag
|
||||
- name: Publish
|
||||
run: bash scripts/release/workflow/publish.sh
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
TARBALL: ${{ steps.pack.outputs.tarball }}
|
||||
TAG: ${{ steps.pack.outputs.tag }}
|
||||
- name: Push tags
|
||||
run: git push --tags
|
||||
- name: Create Github Release
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
PRERELEASE: ${{ needs.state.outputs.is_prerelease }}
|
||||
with:
|
||||
script: await require('./scripts/release/workflow/github-release.js')({ github, context })
|
||||
outputs:
|
||||
tarball_name: ${{ steps.pack.outputs.tarball_name }}
|
||||
|
||||
integrity_check:
|
||||
needs: publish
|
||||
name: Tarball Integrity Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download tarball artifact
|
||||
id: artifact
|
||||
# Replace with actions/upload-artifact@v3 when
|
||||
# https://github.com/actions/download-artifact/pull/194 gets released
|
||||
uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b
|
||||
with:
|
||||
name: ${{ github.ref_name }}
|
||||
- name: Check integrity
|
||||
run: bash scripts/release/workflow/integrity-check.sh
|
||||
env:
|
||||
TARBALL: ${{ steps.artifact.outputs.download-path }}/${{ needs.publish.outputs.tarball_name }}
|
||||
|
||||
merge:
|
||||
needs: state
|
||||
name: Create PR back to master
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: needs.state.outputs.merge == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MERGE_BRANCH: merge/${{ github.ref_name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # All branches
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: bash scripts/git-user-config.sh
|
||||
- name: Create branch to merge
|
||||
run: |
|
||||
git checkout -B "$MERGE_BRANCH" "$GITHUB_REF_NAME"
|
||||
git push -f origin "$MERGE_BRANCH"
|
||||
- name: Create PR back to master
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
head: process.env.MERGE_BRANCH,
|
||||
base: 'master',
|
||||
title: '${{ format('Merge {0} branch', github.ref_name) }}'
|
||||
});
|
||||
30
.github/workflows/upgradeable.yml
vendored
Normal file
30
.github/workflows/upgradeable.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: transpile upgradeable
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release-v*
|
||||
|
||||
jobs:
|
||||
transpile:
|
||||
environment: push-upgradeable
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: OpenZeppelin/openzeppelin-contracts-upgradeable
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_TOKEN_UPGRADEABLE }}
|
||||
- name: Fetch current non-upgradeable branch
|
||||
run: |
|
||||
git fetch "https://github.com/${{ github.repository }}.git" "$REF"
|
||||
git checkout FETCH_HEAD
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- run: bash scripts/git-user-config.sh
|
||||
- name: Transpile to upgradeable
|
||||
run: bash scripts/upgradeable/transpile-onto.sh ${{ github.ref_name }} origin/${{ github.ref_name }}
|
||||
- run: git push origin ${{ github.ref_name }}
|
||||
36
.gitignore
vendored
36
.gitignore
vendored
@ -32,8 +32,40 @@ npm-debug.log
|
||||
# truffle build directory
|
||||
build/
|
||||
|
||||
# lol macs
|
||||
.DS_Store/
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# truffle
|
||||
.node-xmlhttprequest-*
|
||||
|
||||
# IntelliJ IDE
|
||||
.idea
|
||||
|
||||
# docs artifacts
|
||||
docs/modules/api
|
||||
|
||||
# only used to package @openzeppelin/contracts
|
||||
contracts/build/
|
||||
contracts/README.md
|
||||
|
||||
# temporary artifact from solidity-coverage
|
||||
allFiredEvents
|
||||
.coverage_artifacts
|
||||
.coverage_cache
|
||||
.coverage_contracts
|
||||
|
||||
# hardat-exposed
|
||||
contracts-exposed
|
||||
|
||||
# Hardhat
|
||||
/cache
|
||||
/artifacts
|
||||
|
||||
# Foundry
|
||||
/out
|
||||
|
||||
# Certora
|
||||
.certora*
|
||||
.last_confs
|
||||
certora_*
|
||||
.zip-output-url.txt
|
||||
|
||||
7
.gitmodules
vendored
Normal file
7
.gitmodules
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
[submodule "lib/forge-std"]
|
||||
branch = v1
|
||||
path = lib/forge-std
|
||||
url = https://github.com/foundry-rs/forge-std
|
||||
[submodule "lib/erc4626-tests"]
|
||||
path = lib/erc4626-tests
|
||||
url = https://github.com/a16z/erc4626-tests.git
|
||||
4
.mocharc.js
Normal file
4
.mocharc.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
require: 'hardhat/register',
|
||||
timeout: 4000,
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
v8.9.1
|
||||
14
.prettierrc
Normal file
14
.prettierrc
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "avoid",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.sol",
|
||||
"options": {
|
||||
"singleQuote": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
20
.solcover.js
20
.solcover.js
@ -1,9 +1,13 @@
|
||||
module.exports = {
|
||||
norpc: true,
|
||||
testCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle test --network coverage',
|
||||
compileCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle compile --network coverage',
|
||||
skipFiles: [
|
||||
'lifecycle/Migrations.sol',
|
||||
'mocks'
|
||||
]
|
||||
}
|
||||
norpc: true,
|
||||
testCommand: 'npm test',
|
||||
compileCommand: 'npm run compile',
|
||||
skipFiles: ['mocks'],
|
||||
providerOptions: {
|
||||
default_balance_ether: '10000000000000000000000000',
|
||||
},
|
||||
mocha: {
|
||||
fgrep: '[skip-on-coverage]',
|
||||
invert: true,
|
||||
},
|
||||
};
|
||||
|
||||
14
.solhint.json
Normal file
14
.solhint.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-unused-vars": "error",
|
||||
"const-name-snakecase": "error",
|
||||
"contract-name-camelcase": "error",
|
||||
"event-name-camelcase": "error",
|
||||
"func-name-mixedcase": "error",
|
||||
"func-param-name-mixedcase": "error",
|
||||
"modifier-name-mixedcase": "error",
|
||||
"private-vars-leading-underscore": "error",
|
||||
"var-name-mixedcase": "error",
|
||||
"imports-on-top": "error"
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
node_modules
|
||||
@ -1,21 +0,0 @@
|
||||
{
|
||||
"extends": "solium:all",
|
||||
"plugins": ["security"],
|
||||
"rules": {
|
||||
"blank-lines": "off",
|
||||
"error-reason": "off",
|
||||
"indentation": ["error", 2],
|
||||
"lbrace": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"max-len": ["error", 79],
|
||||
"no-constant": ["error"],
|
||||
"no-empty-blocks": "off",
|
||||
"quotes": ["error", "double"],
|
||||
"uppercase": "off",
|
||||
"visibility-first": "error",
|
||||
|
||||
"security/enforce-explicit-visibility": ["error"],
|
||||
"security/no-block-members": ["warning"],
|
||||
"security/no-inline-assembly": ["warning"]
|
||||
}
|
||||
}
|
||||
56
.travis.yml
56
.travis.yml
@ -1,56 +0,0 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
group: beta
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
jobs:
|
||||
# XXX fast_finish doesn't work with stages yet. See
|
||||
# https://github.com/travis-ci/travis-ci/issues/8425
|
||||
# --elopio - 20180531
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: SOLIDITY_COVERAGE=true
|
||||
- env: SOLC_NIGHTLY=true
|
||||
include:
|
||||
# Run the unit test suite three times in parallel.
|
||||
# The first one gets results faster and is the only one required to pass.
|
||||
# The second one generates the coverage report.
|
||||
# The third one is to keep us informed about possible issues with the
|
||||
# upcoming solidity release.
|
||||
- stage: tests
|
||||
name: "unit tests"
|
||||
script: npm run test
|
||||
- stage: tests
|
||||
name: "unit tests with coverage"
|
||||
script: npm run test
|
||||
env: SOLIDITY_COVERAGE=true
|
||||
- stage: tests
|
||||
name: "unit tests with solc nightly"
|
||||
script: npm run test
|
||||
env: SOLC_NIGHTLY=true
|
||||
# solidity and javascript style tests.
|
||||
- stage: tests
|
||||
name: "static tests"
|
||||
script: npm run lint
|
||||
- stage: update docs
|
||||
if: tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- curl
|
||||
script:
|
||||
- ./scripts/ci/trigger_docs_update "${TRAVIS_TAG}"
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
rooms:
|
||||
- secure: uEhwUkuwJp5pBNh+VTEytPHz3FDKsnPrKO+8MTAKv5hKi4PCRoVhLv6pklr82HUpL6pvSvJbUPA0HVebOXA+MMSxdny/BHZTh2mtw5Y78l2Ad0svDTWuV2Lus2pmhYigRhT0Wo00/SRX9+pxm0kg4EIFJSTS+uR9G76x0l9NljpEGXrqxlDxjxoHBgk8Ciru2LHaLzX/utE3jlABts4Sb1F3wc2BwFkjd6BDCRTGAPhVJwwFk41ZfnmLVbgSNUyk46Cb38oG5oXHb0FI3d3jV/k1OUhRyFfmA2fLXRk0wavibW8TG1gGJJWZ7xTCKzw/Cvup6mpehSAeQef8eekMdjpWEhF9hYRq1BvOs0384UU8NQ0O+BtdXU+X3Nyr84TMJN/iIfgN7gYX7AsvXH3jC0JfNUcIkWlJvyXdE6l2GV1hMmhL09GFEBbSpuSXRIWlOXTcYBlp5NbvE8xO8PUW+T6N5RG2XXjv1g8wCpr6Wwk1+LmRkX5trv8MFBZ2pM8p4H5da5++Ov8egLonNGK2jbx6aBLBX3tPf+g70LZEkiQ4eBfZw8VIgXIvKreisicppNuCD27gNmSEPNt0NkwiEBcTCJ9GSVAO0CU2g4ggvHDX2A+RW5XPET9bGkBXKLfFyV7Qe+MSQjXkCnW3bIRh7Wo1V31XiUiYOLuZPIiH3EQ=
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_pull_requests: false
|
||||
730
CHANGELOG.md
Normal file
730
CHANGELOG.md
Normal file
@ -0,0 +1,730 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## 4.9.3 (2023-07-28)
|
||||
|
||||
- `ERC2771Context`: Return the forwarder address whenever the `msg.data` of a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes), as specified by ERC-2771. ([#4481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4481))
|
||||
- `ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case. ([#4484](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4484))
|
||||
|
||||
## 4.9.2 (2023-06-16)
|
||||
|
||||
- `MerkleProof`: Fix a bug in `processMultiProof` and `processMultiProofCalldata` that allows proving arbitrary leaves if the tree contains a node with value 0 at depth 1.
|
||||
|
||||
## 4.9.1 (2023-06-07)
|
||||
|
||||
- `Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description.
|
||||
|
||||
## 4.9.0 (2023-05-23)
|
||||
|
||||
- `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714))
|
||||
- `ERC721Wrapper`: add a new extension of the `ERC721` token which wraps an underlying token. Deposit and withdraw guarantee that the ownership of each token is backed by a corresponding underlying token with the same identifier. ([#3863](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3863))
|
||||
- `EnumerableMap`: add a `keys()` function that returns an array containing all the keys. ([#3920](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3920))
|
||||
- `Governor`: add a public `cancel(uint256)` function. ([#3983](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3983))
|
||||
- `Governor`: Enable timestamp operation for blockchains without a stable block time. This is achieved by connecting a Governor's internal clock to match a voting token's EIP-6372 interface. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934))
|
||||
- `Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774))
|
||||
- `IERC5313`: Add an interface for EIP-5313 that is now final. ([#4013](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4013))
|
||||
- `IERC4906`: Add an interface for ERC-4906 that is now Final. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012))
|
||||
- `StorageSlot`: Add support for `string` and `bytes`. ([#4008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4008))
|
||||
- `Votes`, `ERC20Votes`, `ERC721Votes`: support timestamp checkpointing using EIP-6372. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934))
|
||||
- `ERC4626`: Add mitigation to the inflation attack through virtual shares and assets. ([#3979](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3979))
|
||||
- `Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773))
|
||||
- `ERC20Wrapper`: Make the `underlying` variable private and add a public accessor. ([#4029](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4029))
|
||||
- `EIP712`: add EIP-5267 support for better domain discovery. ([#3969](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3969))
|
||||
- `AccessControlDefaultAdminRules`: Add an extension of `AccessControl` with additional security rules for the `DEFAULT_ADMIN_ROLE`. ([#4009](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4009))
|
||||
- `SignatureChecker`: Add `isValidERC1271SignatureNow` for checking a signature directly against a smart contract using ERC-1271. ([#3932](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3932))
|
||||
- `SafeERC20`: Add a `forceApprove` function to improve compatibility with tokens behaving like USDT. ([#4067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4067))
|
||||
- `ERC1967Upgrade`: removed contract-wide `oz-upgrades-unsafe-allow delegatecall` annotation, replaced by granular annotation in `UUPSUpgradeable`. ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971))
|
||||
- `ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitly forbidden. ([#4100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4100))
|
||||
- `ECDSA`: optimize bytes32 computation by using assembly instead of `abi.encodePacked`. ([#3853](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3853))
|
||||
- `ERC721URIStorage`: Emit ERC-4906 `MetadataUpdate` in `_setTokenURI`. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012))
|
||||
- `ShortStrings`: Added a library for handling short strings in a gas efficient way, with fallback to storage for longer strings. ([#4023](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4023))
|
||||
- `SignatureChecker`: Allow return data length greater than 32 from EIP-1271 signers. ([#4038](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4038))
|
||||
- `UUPSUpgradeable`: added granular `oz-upgrades-unsafe-allow-reachable` annotation to improve upgrade safety checks on latest version of the Upgrades Plugins (starting with `@openzeppelin/upgrades-core@1.21.0`). ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971))
|
||||
- `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787))
|
||||
- `Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. ([#3960](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3960))
|
||||
- `UUPSUpgradeable.sol`: Change visibility to the functions `upgradeTo ` and `upgradeToAndCall ` from `external` to `public`. ([#3959](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3959))
|
||||
- `TimelockController`: Add the `CallSalt` event to emit on operation schedule. ([#4001](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4001))
|
||||
- Reformatted codebase with latest version of Prettier Solidity. ([#3898](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3898))
|
||||
- `Math`: optimize `log256` rounding check. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745))
|
||||
- `ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748))
|
||||
- `Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. ([#3961](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3961))
|
||||
- `ERC20Pausable`, `ERC721Pausable`, `ERC1155Pausable`: Add note regarding missing public pausing functionality ([#4007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4007))
|
||||
- `ECDSA`: Add a function `toDataWithIntendedValidatorHash` that encodes data with version 0x00 following EIP-191. ([#4063](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4063))
|
||||
- `MerkleProof`: optimize by using unchecked arithmetic. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `EIP712`: Addition of ERC5267 support requires support for user defined value types, which was released in Solidity version 0.8.8. This requires a pragma change from `^0.8.0` to `^0.8.8`.
|
||||
- `EIP712`: Optimization of the cache for the upgradeable version affects the way `name` and `version` are set. This is no longer done through an initializer, and is instead part of the implementation's constructor. As a consequence, all proxies using the same implementation will necessarily share the same `name` and `version`. Additionally, an implementation upgrade risks changing the EIP712 domain unless the same `name` and `version` are used when deploying the new implementation contract.
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `ERC20Permit`: Added the file `IERC20Permit.sol` and `ERC20Permit.sol` and deprecated `draft-IERC20Permit.sol` and `draft-ERC20Permit.sol` since [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) is no longer a Draft. Developers are encouraged to update their imports. ([#3793](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3793))
|
||||
- `Timers`: The `Timers` library is now deprecated and will be removed in the next major release. ([#4062](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4062))
|
||||
- `ERC777`: The `ERC777` token standard is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066))
|
||||
- `ERC1820Implementer`: The `ERC1820` pseudo-introspection mechanism is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066))
|
||||
|
||||
## 4.8.3 (2023-04-13)
|
||||
|
||||
- `GovernorCompatibilityBravo`: Fix encoding of proposal data when signatures are missing.
|
||||
- `TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata or payable mutability. ([#4154](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4154))
|
||||
|
||||
## 4.8.2 (2023-03-02)
|
||||
|
||||
- `ERC721Consecutive`: Fixed a bug when `_mintConsecutive` is used for batches of size 1 that could lead to balance overflow. Refer to the breaking changes section in the changelog for a note on the behavior of `ERC721._beforeTokenTransfer`.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ERC721`: The internal function `_beforeTokenTransfer` no longer updates balances, which it previously did when `batchSize` was greater than 1. This change has no consequence unless a custom ERC721 extension is explicitly invoking `_beforeTokenTransfer`. Balance updates in extensions must now be done explicitly using `__unsafe_increaseBalance`, with a name that indicates that there is an invariant that has to be manually verified.
|
||||
|
||||
## 4.8.1 (2023-01-12)
|
||||
|
||||
- `ERC4626`: Use staticcall instead of call when fetching underlying ERC-20 decimals. ([#3943](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3943))
|
||||
|
||||
## 4.8.0 (2022-11-08)
|
||||
|
||||
- `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722))
|
||||
- `Initializable`: add internal functions `_getInitializedVersion` and `_isInitializing` ([#3598](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3598))
|
||||
- `ERC165Checker`: add `supportsERC165InterfaceUnchecked` for consulting individual interfaces without the full ERC165 protocol. ([#3339](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3339))
|
||||
- `Address`: optimize `functionCall` by calling `functionCallWithValue` directly. ([#3468](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3468))
|
||||
- `Address`: optimize `functionCall` functions by checking contract size only if there is no returned data. ([#3469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3469))
|
||||
- `Governor`: make the `relay` function payable, and add support for EOA payments. ([#3730](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3730))
|
||||
- `GovernorCompatibilityBravo`: remove unused `using` statements. ([#3506](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3506))
|
||||
- `ERC20`: optimize `_transfer`, `_mint` and `_burn` by using `unchecked` arithmetic when possible. ([#3513](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3513))
|
||||
- `ERC20Votes`, `ERC721Votes`: optimize `getPastVotes` for looking up recent checkpoints. ([#3673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3673))
|
||||
- `ERC20FlashMint`: add an internal `_flashFee` function for overriding. ([#3551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3551))
|
||||
- `ERC4626`: use the same `decimals()` as the underlying asset by default (if available). ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639))
|
||||
- `ERC4626`: add internal `_initialConvertToShares` and `_initialConvertToAssets` functions to customize empty vaults behavior. ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639))
|
||||
- `ERC721`: optimize transfers by making approval clearing implicit instead of emitting an event. ([#3481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3481))
|
||||
- `ERC721`: optimize burn by making approval clearing implicit instead of emitting an event. ([#3538](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3538))
|
||||
- `ERC721`: Fix balance accounting when a custom `_beforeTokenTransfer` hook results in a transfer of the token under consideration. ([#3611](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3611))
|
||||
- `ERC721`: use unchecked arithmetic for balance updates. ([#3524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3524))
|
||||
- `ERC721Consecutive`: Implementation of EIP-2309 that allows batch minting of ERC721 tokens during construction. ([#3311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3311))
|
||||
- `ReentrancyGuard`: Reduce code size impact of the modifier by using internal functions. ([#3515](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3515))
|
||||
- `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565))
|
||||
- `ECDSA`: Remove redundant check on the `v` value. ([#3591](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3591))
|
||||
- `VestingWallet`: add `releasable` getters. ([#3580](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3580))
|
||||
- `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605))
|
||||
- `VestingWallet`: make constructor payable. ([#3665](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3665))
|
||||
- `Create2`: optimize address computation by using assembly instead of `abi.encodePacked`. ([#3600](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3600))
|
||||
- `Clones`: optimized the assembly to use only the scratch space during deployments, and optimized `predictDeterministicAddress` to use fewer operations. ([#3640](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3640))
|
||||
- `Checkpoints`: Use procedural generation to support multiple key/value lengths. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589))
|
||||
- `Checkpoints`: Add new lookup mechanisms. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589))
|
||||
- `Arrays`: Add `unsafeAccess` functions that allow reading and writing to an element in a storage array bypassing Solidity's "out-of-bounds" check. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589))
|
||||
- `Strings`: optimize `toString`. ([#3573](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3573))
|
||||
- `Ownable2Step`: extension of `Ownable` that makes the ownership transfers a two step process. ([#3620](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620))
|
||||
- `Math` and `SignedMath`: optimize function `max` by using `>` instead of `>=`. ([#3679](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3679))
|
||||
- `Math`: Add `log2`, `log10` and `log256`. ([#3670](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3670))
|
||||
- Arbitrum: Update the vendored arbitrum contracts to match the nitro upgrade. ([#3692](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3692))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ERC721`: In order to add support for batch minting via `ERC721Consecutive` it was necessary to make a minor breaking change in the internal interface of `ERC721`. Namely, the hooks `_beforeTokenTransfer` and `_afterTokenTransfer` have one additional argument that may need to be added to overrides:
|
||||
|
||||
```diff
|
||||
function _beforeTokenTransfer(
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
+ uint256 batchSize
|
||||
) internal virtual override
|
||||
```
|
||||
|
||||
- `ERC4626`: Conversion from shares to assets (and vice-versa) in an empty vault used to consider the possible mismatch between the underlying asset's and the vault's decimals. This initial conversion rate is now set to 1-to-1 irrespective of decimals, which are meant for usability purposes only. The vault now uses the assets decimals by default, so off-chain the numbers should appear the same. Developers overriding the vault decimals to a value that does not match the underlying asset may want to override the `_initialConvertToShares` and `_initialConvertToAssets` to replicate the previous behavior.
|
||||
|
||||
- `TimelockController`: During deployment, the TimelockController used to grant the `TIMELOCK_ADMIN_ROLE` to the deployer and to the timelock itself. The deployer was then expected to renounce this role once configuration of the timelock is over. Failing to renounce that role allows the deployer to change the timelock permissions (but not to bypass the delay for any time-locked actions). The role is no longer given to the deployer by default. A new parameter `admin` can be set to a non-zero address to grant the admin role during construction (to the deployer or any other address). Just like previously, this admin role should be renounced after configuration. If this param is given `address(0)`, the role is not allocated and doesn't need to be revoked. In any case, the timelock itself continues to have this role.
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `EIP712`: Added the file `EIP712.sol` and deprecated `draft-EIP712.sol` since the EIP is no longer a Draft. Developers are encouraged to update their imports. ([#3621](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3621))
|
||||
|
||||
```diff
|
||||
-import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
|
||||
+import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
||||
```
|
||||
|
||||
- `ERC721Votes`: Added the file `ERC721Votes.sol` and deprecated `draft-ERC721Votes.sol` since it no longer depends on a Draft EIP (EIP-712). Developers are encouraged to update their imports. ([#3699](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3699))
|
||||
|
||||
```diff
|
||||
-import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol";
|
||||
+import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol";
|
||||
```
|
||||
|
||||
### ERC-721 Compatibility Note
|
||||
|
||||
ERC-721 integrators that interpret contract state from events should make sure that they implement the clearing of approval that is implicit in every transfer according to the EIP. Previous versions of OpenZeppelin Contracts emitted an explicit `Approval` event even though it was not required by the specification, and this is no longer the case.
|
||||
|
||||
With the new `ERC721Consecutive` extension, the internal workings of `ERC721` are slightly changed. Custom extensions to ERC721 should be reviewed to ensure they remain correct. The internal functions that should be considered are `_ownerOf` (new), `_beforeTokenTransfer`, and `_afterTokenTransfer`.
|
||||
|
||||
### ERC-4626 Upgrade Note
|
||||
|
||||
Existing `ERC4626` contracts that are upgraded to 4.8 must initialize a new variable that holds the vault token decimals. The recommended way to do this is to use a [reinitializer]:
|
||||
|
||||
[reinitializer]: https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable-reinitializer-uint8-
|
||||
|
||||
```solidity
|
||||
function migrateToV48() public reinitializer(2) {
|
||||
__ERC4626_init(IERC20Upgradeable(asset()));
|
||||
}
|
||||
```
|
||||
|
||||
## 4.7.3 (2022-08-10)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ECDSA`: `recover(bytes32,bytes)` and `tryRecover(bytes32,bytes)` no longer accept compact signatures to prevent malleability. Compact signature support remains available using `recover(bytes32,bytes32,bytes32)` and `tryRecover(bytes32,bytes32,bytes32)`.
|
||||
|
||||
## 4.7.2 (2022-07-25)
|
||||
|
||||
- `LibArbitrumL2`, `CrossChainEnabledArbitrumL2`: Fixed detection of cross-chain calls for EOAs. Previously, calls from EOAs would be classified as cross-chain calls. ([#3578](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3578))
|
||||
- `GovernorVotesQuorumFraction`: Fixed quorum updates so they do not affect past proposals that failed due to lack of quorum. ([#3561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3561))
|
||||
- `ERC165Checker`: Added protection against large returndata. ([#3587](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3587))
|
||||
|
||||
## 4.7.1 (2022-07-18)
|
||||
|
||||
- `SignatureChecker`: Fix an issue that causes `isValidSignatureNow` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552))
|
||||
- `ERC165Checker`: Fix an issue that causes `supportsInterface` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552))
|
||||
|
||||
## 4.7.0 (2022-06-29)
|
||||
|
||||
- `TimelockController`: Migrate `_call` to `_execute` and allow inheritance and overriding similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317))
|
||||
- `CrossChainEnabledPolygonChild`: replace the `require` statement with the custom error `NotCrossChainCall`. ([#3380](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3380))
|
||||
- `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327))
|
||||
- `ERC4626`: add an extension of `ERC20` that implements the ERC4626 Tokenized Vault Standard. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171))
|
||||
- `SafeERC20`: add `safePermit` as mitigation against phantom permit functions. ([#3280](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3280))
|
||||
- `Math`: add a `mulDiv` function that can round the result either up or down. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171))
|
||||
- `Math`: Add a `sqrt` function to compute square roots of integers, rounding either up or down. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242))
|
||||
- `Strings`: add a new overloaded function `toHexString` that converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. ([#3403](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3403))
|
||||
- `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338))
|
||||
- `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416))
|
||||
- `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245))
|
||||
- `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276))
|
||||
- `MerkleProof`: add calldata versions of the functions to avoid copying input arrays to memory and save gas. ([#3200](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3200))
|
||||
- `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254), ([#3438](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3438)))
|
||||
- `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434))
|
||||
- `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350))
|
||||
- `Initializable`: refactored implementation of modifiers for easier understanding. ([#3450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3450))
|
||||
- `Proxies`: remove runtime check of ERC1967 storage slots. ([#3455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3455))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `Initializable`: functions decorated with the modifier `reinitializer(1)` may no longer invoke each other.
|
||||
|
||||
## 4.6.0 (2022-04-26)
|
||||
|
||||
- `crosschain`: Add a new set of contracts for cross-chain applications. `CrossChainEnabled` is a base contract with instantiations for several chains and bridges, and `AccessControlCrossChain` is an extension of access control that allows cross-chain operation. ([#3183](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3183))
|
||||
- `AccessControl`: add a virtual `_checkRole(bytes32)` function that can be overridden to alter the `onlyRole` modifier behavior. ([#3137](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3137))
|
||||
- `EnumerableMap`: add new `AddressToUintMap` map type. ([#3150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3150))
|
||||
- `EnumerableMap`: add new `Bytes32ToBytes32Map` map type. ([#3192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3192))
|
||||
- `ERC20FlashMint`: support infinite allowance when paying back a flash loan. ([#3226](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3226))
|
||||
- `ERC20Wrapper`: the `decimals()` function now tries to fetch the value from the underlying token instance. If that calls revert, then the default value is used. ([#3259](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3259))
|
||||
- `draft-ERC20Permit`: replace `immutable` with `constant` for `_PERMIT_TYPEHASH` since the `keccak256` of string literals is treated specially and the hash is evaluated at compile time. ([#3196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3196))
|
||||
- `ERC1155`: Add a `_afterTokenTransfer` hook for improved extensibility. ([#3166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3166))
|
||||
- `ERC1155URIStorage`: add a new extension that implements a `_setURI` behavior similar to ERC721's `_setTokenURI`. ([#3210](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3210))
|
||||
- `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153))
|
||||
- `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147))
|
||||
- `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
|
||||
- `Governor`: rewording of revert reason for consistency. ([#3275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3275))
|
||||
- `Governor`: fix an inconsistency in data locations that could lead to invalid bytecode being produced. ([#3295](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3295))
|
||||
- `Governor`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by governors. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230))
|
||||
- `TimelockController`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by timelocks. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230))
|
||||
- `TimelockController`: Add a separate canceller role for the ability to cancel. ([#3165](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3165))
|
||||
- `Initializable`: add a reinitializer modifier that enables the initialization of new modules, added to already initialized contracts through upgradeability. ([#3232](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3232))
|
||||
- `Initializable`: add an Initialized event that tracks initialized version numbers. ([#3294](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3294))
|
||||
- `ERC2981`: make `royaltyInfo` public to allow super call in overrides. ([#3305](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3305))
|
||||
|
||||
### Upgradeability notice
|
||||
|
||||
- `TimelockController`: **(Action needed)** The upgrade from <4.6 to >=4.6 introduces a new `CANCELLER_ROLE` that requires set up to be assignable. After the upgrade, only addresses with this role will have the ability to cancel. Proposers will no longer be able to cancel. Assigning cancellers can be done by an admin (including the timelock itself) once the role admin is set up. To do this, we recommend upgrading to the `TimelockControllerWith46MigrationUpgradeable` contract and then calling the `migrateTo46` function.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `Governor`: Adds internal virtual `_getVotes` method that must be implemented; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing voting module extension, rename `getVotes` to `_getVotes` and add a `bytes memory` argument. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
|
||||
- `Governor`: Adds `params` parameter to internal virtual `_countVote` method; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing counting module extension, add a `bytes memory` argument to `_countVote`. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
|
||||
- `Governor`: Does not emit `VoteCast` event when params data is non-empty; instead emits `VoteCastWithParams` event. To fix this on an integration that consumes the `VoteCast` event, also fetch/monitor `VoteCastWithParams` events. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
|
||||
- `Votes`: The internal virtual function `_getVotingUnits` was made `view` (which was accidentally missing). Any overrides should now be updated so they are `view` as well.
|
||||
|
||||
## 4.5.0 (2022-02-09)
|
||||
|
||||
- `ERC2981`: add implementation of the royalty standard, and the respective extensions for `ERC721` and `ERC1155`. ([#3012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3012))
|
||||
- `GovernorTimelockControl`: improve the `state()` function to have it reflect cases where a proposal has been canceled directly on the timelock. ([#2977](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2977))
|
||||
- Preset contracts are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com). ([#2986](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2986))
|
||||
- `Governor`: add a relay function to help recover assets sent to a governor that is not its own executor (e.g. when using a timelock). ([#2926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2926))
|
||||
- `GovernorPreventLateQuorum`: add new module to ensure a minimum voting duration is available after the quorum is reached. ([#2973](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2973))
|
||||
- `ERC721`: improved revert reason when transferring from wrong owner. ([#2975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2975))
|
||||
- `Votes`: Added a base contract for vote tracking with delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944))
|
||||
- `ERC721Votes`: Added an extension of ERC721 enabled with vote tracking and delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944))
|
||||
- `ERC2771Context`: use immutable storage to store the forwarder address, no longer an issue since Solidity >=0.8.8 allows reading immutable variables in the constructor. ([#2917](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2917))
|
||||
- `Base64`: add a library to parse bytes into base64 strings using `encode(bytes memory)` function, and provide examples to show how to use to build URL-safe `tokenURIs`. ([#2884](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884))
|
||||
- `ERC20`: reduce allowance before triggering transfer. ([#3056](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3056))
|
||||
- `ERC20`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085))
|
||||
- `ERC20`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170))
|
||||
- `ERC20Burnable`: do not update allowance on `burnFrom` when allowance is `type(uint256).max`. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170))
|
||||
- `ERC777`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085))
|
||||
- `ERC777`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170))
|
||||
- `SignedMath`: a new signed version of the Math library with `max`, `min`, and `average`. ([#2686](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2686))
|
||||
- `SignedMath`: add an `abs(int256)` method that returns the unsigned absolute value of a signed value. ([#2984](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2984))
|
||||
- `ERC1967Upgrade`: Refactor the secure upgrade to use `ERC1822` instead of the previous rollback mechanism. This reduces code complexity and attack surface with similar security guarantees. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021))
|
||||
- `UUPSUpgradeable`: Add `ERC1822` compliance to support the updated secure upgrade mechanism. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021))
|
||||
- Some more functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ERC1967Upgrade`: The function `_upgradeToAndCallSecure` was renamed to `_upgradeToAndCallUUPS`, along with the change in security mechanism described above.
|
||||
- `Address`: The Solidity pragma is increased from `^0.8.0` to `^0.8.1`. This is required by the `account.code.length` syntax that replaces inline assembly. This may require users to bump their compiler version from `0.8.0` to `0.8.1` or later. Note that other parts of the code already include stricter requirements.
|
||||
|
||||
## 4.4.2 (2022-01-11)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- `GovernorCompatibilityBravo`: Fix error in the encoding of calldata for proposals submitted through the compatibility interface with explicit signatures. ([#3100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3100))
|
||||
|
||||
## 4.4.1 (2021-12-14)
|
||||
|
||||
- `Initializable`: change the existing `initializer` modifier and add a new `onlyInitializing` modifier to prevent reentrancy risk. ([#3006](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3006))
|
||||
|
||||
### Breaking change
|
||||
|
||||
It is no longer possible to call an `initializer`-protected function from within another `initializer` function outside the context of a constructor. Projects using OpenZeppelin upgradeable proxies should continue to work as is, since in the common case the initializer is invoked in the constructor directly. If this is not the case for you, the suggested change is to use the new `onlyInitializing` modifier in the following way:
|
||||
|
||||
```diff
|
||||
contract A {
|
||||
- function initialize() public initializer { ... }
|
||||
+ function initialize() internal onlyInitializing { ... }
|
||||
}
|
||||
contract B is A {
|
||||
function initialize() public initializer {
|
||||
A.initialize();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4.4.0 (2021-11-25)
|
||||
|
||||
- `Ownable`: add an internal `_transferOwnership(address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568))
|
||||
- `AccessControl`: add internal `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568))
|
||||
- `AccessControl`: mark `_setupRole(bytes32,address)` as deprecated in favor of `_grantRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568))
|
||||
- `AccessControlEnumerable`: hook into `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2946](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2946))
|
||||
- `EIP712`: cache `address(this)` to immutable storage to avoid potential issues if a vanilla contract is used in a delegatecall context. ([#2852](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2852))
|
||||
- Add internal `_setApprovalForAll` to `ERC721` and `ERC1155`. ([#2834](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2834))
|
||||
- `Governor`: shift vote start and end by one block to better match Compound's GovernorBravo and prevent voting at the Governor level if the voting snapshot is not ready. ([#2892](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2892))
|
||||
- `GovernorCompatibilityBravo`: consider quorum an inclusive rather than exclusive minimum to match Compound's GovernorBravo. ([#2974](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2974))
|
||||
- `GovernorSettings`: a new governor module that manages voting settings updatable through governance actions. ([#2904](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2904))
|
||||
- `PaymentSplitter`: now supports ERC20 assets in addition to Ether. ([#2858](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2858))
|
||||
- `ECDSA`: add a variant of `toEthSignedMessageHash` for arbitrary length message hashing. ([#2865](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2865))
|
||||
- `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841))
|
||||
- `VestingWallet`: new contract that handles the vesting of Ether and ERC20 tokens following a customizable vesting schedule. ([#2748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2748))
|
||||
- `Governor`: enable receiving Ether when a Timelock contract is not used. ([#2849](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849))
|
||||
- `GovernorTimelockCompound`: fix ability to use Ether stored in the Timelock contract. ([#2849](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849))
|
||||
|
||||
## 4.3.3 (2021-11-08)
|
||||
|
||||
- `ERC1155Supply`: Handle `totalSupply` changes by hooking into `_beforeTokenTransfer` to ensure consistency of balances and supply during `IERC1155Receiver.onERC1155Received` calls.
|
||||
|
||||
## 4.3.2 (2021-09-14)
|
||||
|
||||
- `UUPSUpgradeable`: Add modifiers to prevent `upgradeTo` and `upgradeToAndCall` being executed on any contract that is not the active ERC1967 proxy. This prevents these functions being called on implementation contracts or minimal ERC1167 clones, in particular.
|
||||
|
||||
## 4.3.1 (2021-08-26)
|
||||
|
||||
- `TimelockController`: Add additional isOperationReady check.
|
||||
|
||||
## 4.3.0 (2021-08-17)
|
||||
|
||||
- `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754))
|
||||
- `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768))
|
||||
- `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672))
|
||||
- Add an `interfaces` folder containing solidity interfaces to final ERCs. ([#2517](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2517))
|
||||
- `ECDSA`: add `tryRecover` functions that will not throw if the signature is invalid, and will return an error flag instead. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661))
|
||||
- `SignatureChecker`: Reduce gas usage of the `isValidSignatureNow` function for the "signature by EOA" case. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661))
|
||||
|
||||
## 4.2.0 (2021-06-30)
|
||||
|
||||
- `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632))
|
||||
- `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706))
|
||||
- `ERC20Wrapper`: add a new extension of the `ERC20` token which wraps an underlying token. Deposit and withdraw guarantee that the total supply is backed by a corresponding amount of underlying token. ([#2633](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2633))
|
||||
- Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`.
|
||||
- Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`.
|
||||
- `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678))
|
||||
- Tokens: Wrap definitely safe subtractions in `unchecked` blocks.
|
||||
- `Math`: Add a `ceilDiv` method for performing ceiling division.
|
||||
- `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593))
|
||||
- `BitMaps`: add a new `BitMaps` library that provides a storage efficient datastructure for `uint256` to `bool` mapping with contiguous keys. ([#2710](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2710))
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `ERC20FlashMint` is no longer a Draft ERC. ([#2673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2673)))
|
||||
|
||||
**How to update:** Change your import paths by removing the `draft-` prefix from `@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20FlashMint.sol`.
|
||||
|
||||
> See [Releases and Stability: Drafts](https://docs.openzeppelin.com/contracts/4.x/releases-stability#drafts).
|
||||
|
||||
## 4.1.0 (2021-04-29)
|
||||
|
||||
- `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561))
|
||||
- `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552))
|
||||
- `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565))
|
||||
- `ERC20FlashMint`: add an implementation of the ERC3156 extension for flash-minting ERC20 tokens. ([#2543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2543))
|
||||
- `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532))
|
||||
- `Multicall`: add abstract contract with `multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608))
|
||||
- `ECDSA`: add support for ERC2098 short-signatures. ([#2582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2582))
|
||||
- `AccessControl`: add an `onlyRole` modifier to restrict specific function to callers bearing a specific role. ([#2609](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2609))
|
||||
- `StorageSlot`: add a library for reading and writing primitive types to specific storage slots. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542))
|
||||
- UUPS Proxies: add `UUPSUpgradeable` to implement the UUPS proxy pattern together with `EIP1967Proxy`. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
This release includes two small breaking changes in `TimelockController`.
|
||||
|
||||
1. The `onlyRole` modifier in this contract was designed to let anyone through if the role was granted to `address(0)`,
|
||||
allowing the possibility to to make a role "open", which can be used for `EXECUTOR_ROLE`. This modifier is now
|
||||
replaced by `AccessControl.onlyRole`, which does not have this ability. The previous behavior was moved to the
|
||||
modifier `TimelockController.onlyRoleOrOpenRole`.
|
||||
2. It was possible to make `PROPOSER_ROLE` an open role (as described in the previous item) if it was granted to
|
||||
`address(0)`. This would affect the `schedule`, `scheduleBatch`, and `cancel` operations in `TimelockController`.
|
||||
This ability was removed as it does not make sense to open up the `PROPOSER_ROLE` in the same way that it does for
|
||||
`EXECUTOR_ROLE`.
|
||||
|
||||
## 4.0.0 (2021-03-23)
|
||||
|
||||
- Now targeting the 0.8.x line of Solidity compilers. For 0.6.x (resp 0.7.x) support, use version 3.4.0 (resp 3.4.0-solc-0.7) of OpenZeppelin.
|
||||
- `Context`: making `_msgData` return `bytes calldata` instead of `bytes memory` ([#2492](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2492))
|
||||
- `ERC20`: removed the `_setDecimals` function and the storage slot associated to decimals. ([#2502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2502))
|
||||
- `Strings`: addition of a `toHexString` function. ([#2504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2504))
|
||||
- `EnumerableMap`: change implementation to optimize for `key → value` lookups instead of enumeration. ([#2518](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2518))
|
||||
- `GSN`: deprecate GSNv1 support in favor of upcoming support for GSNv2. ([#2521](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2521))
|
||||
- `ERC165`: remove uses of storage in the base ERC165 implementation. ERC165 based contracts now use storage-less virtual functions. Old behavior remains available in the `ERC165Storage` extension. ([#2505](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2505))
|
||||
- `Initializable`: make initializer check stricter during construction. ([#2531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2531))
|
||||
- `ERC721`: remove enumerability of tokens from the base implementation. This feature is now provided separately through the `ERC721Enumerable` extension. ([#2511](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2511))
|
||||
- `AccessControl`: removed enumerability by default for a more lightweight contract. It is now opt-in through `AccessControlEnumerable`. ([#2512](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2512))
|
||||
- Meta Transactions: add `ERC2771Context` and a `MinimalForwarder` for meta-transactions. ([#2508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2508))
|
||||
- Overall reorganization of the contract folder to improve clarity and discoverability. ([#2503](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2503))
|
||||
- `ERC20Capped`: optimize gas usage by enforcing the check directly in `_mint`. ([#2524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2524))
|
||||
- Rename `UpgradeableProxy` to `ERC1967Proxy`. ([#2547](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2547))
|
||||
- `ERC777`: optimize the gas costs of the constructor. ([#2551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2551))
|
||||
- `ERC721URIStorage`: add a new extension that implements the `_setTokenURI` behavior as it was available in 3.4.0. ([#2555](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2555))
|
||||
- `AccessControl`: added ERC165 interface detection. ([#2562](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2562))
|
||||
- `ERC1155`: make `uri` public so overloading function can call it using super. ([#2576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2576))
|
||||
|
||||
### Bug fixes for beta releases
|
||||
|
||||
- `AccessControlEnumerable`: Fixed `renounceRole` not updating enumerable set of addresses for a role. ([#2572](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2572))
|
||||
|
||||
### How to upgrade from 3.x
|
||||
|
||||
Since this version has moved a few contracts to different directories, users upgrading from a previous version will need to adjust their import statements. To make this easier, the package includes a script that will migrate import statements automatically. After upgrading to the latest version of the package, run:
|
||||
|
||||
```
|
||||
npx openzeppelin-contracts-migrate-imports
|
||||
```
|
||||
|
||||
Make sure you're using git or another version control system to be able to recover from any potential error in our script.
|
||||
|
||||
### How to upgrade from 4.0-beta.x
|
||||
|
||||
Some further changes have been done between the different beta iterations. Transitions made during this period are configured in the `migrate-imports` script. Consequently, you can upgrade from any previous 4.0-beta.x version using the same script as described in the _How to upgrade from 3.x_ section.
|
||||
|
||||
## 3.4.2 (2021-07-24)
|
||||
|
||||
- `TimelockController`: Add additional isOperationReady check.
|
||||
|
||||
## 3.4.1 (2021-03-03)
|
||||
|
||||
- `ERC721`: made `_approve` an internal function (was private).
|
||||
|
||||
## 3.4.0 (2021-02-02)
|
||||
|
||||
- `BeaconProxy`: added new kind of proxy that allows simultaneous atomic upgrades. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411))
|
||||
- `EIP712`: added helpers to verify EIP712 typed data signatures on chain. ([#2418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2418))
|
||||
- `ERC20Permit`: added an implementation of the ERC20 permit extension for gasless token approvals. ([#2237](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237))
|
||||
- Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399))
|
||||
- `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
|
||||
- `Clones`: added a library for deploying EIP 1167 minimal proxies. ([#2449](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2449))
|
||||
- `Context`: moved from `contracts/GSN` to `contracts/utils`. ([#2453](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2453))
|
||||
- `PaymentSplitter`: replace usage of `.transfer()` with `Address.sendValue` for improved compatibility with smart wallets. ([#2455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2455))
|
||||
- `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454))
|
||||
- `SafeMath`: fix a memory allocation issue by adding new `SafeMath.tryOp(uint,uint)→(bool,uint)` functions. `SafeMath.op(uint,uint,string)→uint` are now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462))
|
||||
- `EnumerableMap`: fix a memory allocation issue by adding new `EnumerableMap.tryGet(uint)→(bool,address)` functions. `EnumerableMap.get(uint)→string` is now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462))
|
||||
- `ERC165Checker`: added batch `getSupportedInterfaces`. ([#2469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2469))
|
||||
- `RefundEscrow`: `beneficiaryWithdraw` will forward all available gas to the beneficiary. ([#2480](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2480))
|
||||
- Many view and pure functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior.
|
||||
|
||||
### Security Fixes
|
||||
|
||||
- `ERC777`: fix potential reentrancy issues for custom extensions to `ERC777`. ([#2483](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483))
|
||||
|
||||
If you're using our implementation of ERC777 from version 3.3.0 or earlier, and you define a custom `_beforeTokenTransfer` function that writes to a storage variable, you may be vulnerable to a reentrancy attack. If you're affected and would like assistance please write to security@openzeppelin.com. [Read more in the pull request.](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483)
|
||||
|
||||
## 3.3.0 (2020-11-26)
|
||||
|
||||
- Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings.
|
||||
- `Address`: added `functionStaticCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
|
||||
- `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354))
|
||||
- `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395))
|
||||
|
||||
## 3.2.2-solc-0.7 (2020-10-28)
|
||||
|
||||
- Resolve warnings introduced by Solidity 0.7.4. ([#2396](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2396))
|
||||
|
||||
## 3.2.1-solc-0.7 (2020-09-15)
|
||||
|
||||
- `ERC777`: Remove a warning about function state visibility in Solidity 0.7. ([#2327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2327))
|
||||
|
||||
## 3.2.0 (2020-09-10)
|
||||
|
||||
### New features
|
||||
|
||||
- Proxies: added the proxy contracts from OpenZeppelin SDK. ([#2335](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2335))
|
||||
|
||||
#### Proxy changes with respect to OpenZeppelin SDK
|
||||
|
||||
Aside from upgrading them from Solidity 0.5 to 0.6, we've changed a few minor things from the proxy contracts as they were found in OpenZeppelin SDK.
|
||||
|
||||
- `UpgradeabilityProxy` was renamed to `UpgradeableProxy`.
|
||||
- `AdminUpgradeabilityProxy` was renamed to `TransparentUpgradeableProxy`.
|
||||
- `Proxy._willFallback` was renamed to `Proxy._beforeFallback`.
|
||||
- `UpgradeabilityProxy._setImplementation` and `AdminUpgradeabilityProxy._setAdmin` were made private.
|
||||
|
||||
### Improvements
|
||||
|
||||
- `Address.isContract`: switched from `extcodehash` to `extcodesize` for less gas usage. ([#2311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2311))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ERC20Snapshot`: switched to using `_beforeTokenTransfer` hook instead of overriding ERC20 operations. ([#2312](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2312))
|
||||
|
||||
This small change in the way we implemented `ERC20Snapshot` may affect users who are combining this contract with
|
||||
other ERC20 flavors, since it no longer overrides `_transfer`, `_mint`, and `_burn`. This can result in having to remove Solidity `override(...)` specifiers in derived contracts for these functions, and to instead have to add it for `_beforeTokenTransfer`. See [Using Hooks](https://docs.openzeppelin.com/contracts/3.x/extending-contracts#using-hooks) in the documentation.
|
||||
|
||||
## 3.1.0 (2020-06-23)
|
||||
|
||||
### New features
|
||||
|
||||
- `SafeCast`: added functions to downcast signed integers (e.g. `toInt32`), improving usability of `SignedSafeMath`. ([#2243](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2243))
|
||||
- `functionCall`: new helpers that replicate Solidity's function call semantics, reducing the need to rely on `call`. ([#2264](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2264))
|
||||
- `ERC1155`: added support for a base implementation, non-standard extensions and a preset contract. ([#2014](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2014), [#2230](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2230))
|
||||
|
||||
### Improvements
|
||||
|
||||
- `ReentrancyGuard`: reduced overhead of using the `nonReentrant` modifier. ([#2171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2171))
|
||||
- `AccessControl`: added a `RoleAdminChanged` event to `_setAdminRole`. ([#2214](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2214))
|
||||
- Made all `public` functions in the token preset contracts `virtual`. ([#2257](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2257))
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `SafeERC20`: deprecated `safeApprove`. ([#2268](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2268))
|
||||
|
||||
## 3.0.2 (2020-06-08)
|
||||
|
||||
### Improvements
|
||||
|
||||
- Added SPX license identifier to all contracts. ([#2235](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2235))
|
||||
|
||||
## 3.0.1 (2020-04-27)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- `ERC777`: fixed the `_approve` internal function not validating some of their arguments for non-zero addresses. ([#2213](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2213))
|
||||
|
||||
## 3.0.0 (2020-04-20)
|
||||
|
||||
### New features
|
||||
|
||||
- `AccessControl`: new contract for managing permissions in a system, replacement for `Ownable` and `Roles`. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112))
|
||||
- `SafeCast`: new functions to convert to and from signed and unsigned values: `toUint256` and `toInt256`. ([#2123](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2123))
|
||||
- `EnumerableMap`: a new data structure for key-value pairs (like `mapping`) that can be iterated over. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ERC721`: `burn(owner, tokenId)` was removed, use `burn(tokenId)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))
|
||||
- `ERC721`: `_checkOnERC721Received` was removed. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))
|
||||
- `ERC721`: `_transferFrom` and `_safeTransferFrom` were renamed to `_transfer` and `_safeTransfer`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162))
|
||||
- `Ownable`: removed `_transferOwnership`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162))
|
||||
- `PullPayment`, `Escrow`: `withdrawWithGas` was removed. The old `withdraw` function now forwards all gas. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))
|
||||
- `Roles` was removed, use `AccessControl` as a replacement. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112))
|
||||
- `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114))
|
||||
- `Create2`: added an `amount` argument to `deploy` for contracts with `payable` constructors. ([#2117](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2117))
|
||||
- `Pausable`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
|
||||
- `Strings`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
|
||||
- `Counters`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
|
||||
- `SignedSafeMath`: moved to the `math` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
|
||||
- `ERC20Snapshot`: moved to the `token/ERC20` directory. `snapshot` was changed into an `internal` function. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
|
||||
- `Ownable`: moved to the `access` directory. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120))
|
||||
- `Ownable`: removed `isOwner`. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120))
|
||||
- `Secondary`: removed from the library, use `Ownable` instead. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120))
|
||||
- `Escrow`, `ConditionalEscrow`, `RefundEscrow`: these now use `Ownable` instead of `Secondary`, their external API changed accordingly. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120))
|
||||
- `ERC20`: removed `_burnFrom`. ([#2119](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2119))
|
||||
- `Address`: removed `toPayable`, use `payable(address)` instead. ([#2133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2133))
|
||||
- `ERC777`: `_send`, `_mint` and `_burn` now use the caller as the operator. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134))
|
||||
- `ERC777`: removed `_callsTokensToSend` and `_callTokensReceived`. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134))
|
||||
- `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151))
|
||||
- `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150))
|
||||
- `ERC721Metadata`, `ERC721Enumerable`: these contracts were removed, and their functionality merged into `ERC721`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160))
|
||||
- `ERC721`: added a constructor for `name` and `symbol`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160))
|
||||
- `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161))
|
||||
- `ERC20`: added a constructor for `name` and `symbol`. `decimals` now defaults to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161))
|
||||
- `Strings`: renamed `fromUint256` to `toString` ([#2188](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2188))
|
||||
|
||||
## 2.5.1 (2020-04-24)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- `ERC777`: fixed the `_send` and `_approve` internal functions not validating some of their arguments for non-zero addresses. ([#2212](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2212))
|
||||
|
||||
## 2.5.0 (2020-02-04)
|
||||
|
||||
### New features
|
||||
|
||||
- `SafeCast.toUintXX`: new library for integer downcasting, which allows for safe operation on smaller types (e.g. `uint32`) when combined with `SafeMath`. ([#1926](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1926))
|
||||
- `ERC721Metadata`: added `baseURI`, which can be used for dramatic gas savings when all token URIs share a prefix (e.g. `http://api.myapp.com/tokens/<id>`). ([#1970](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1970))
|
||||
- `EnumerableSet`: new library for storing enumerable sets of values. Only `AddressSet` is supported in this release. ([#2061](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/2061))
|
||||
- `Create2`: simple library to make usage of the `CREATE2` opcode easier. ([#1744](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1744))
|
||||
|
||||
### Improvements
|
||||
|
||||
- `ERC777`: `_burn` is now internal, providing more flexibility and making it easier to create tokens that deflate. ([#1908](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1908))
|
||||
- `ReentrancyGuard`: greatly improved gas efficiency by using the net gas metering mechanism introduced in the Istanbul hardfork. ([#1992](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1992), [#1996](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1996))
|
||||
- `ERC777`: improve extensibility by making `_send` and related functions `internal`. ([#2027](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2027))
|
||||
- `ERC721`: improved revert reason when transferring tokens to a non-recipient contract. ([#2018](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2018))
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `ERC165Checker` now requires a minimum Solidity compiler version of 0.5.10. ([#1829](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1829))
|
||||
|
||||
## 2.4.0 (2019-10-29)
|
||||
|
||||
### New features
|
||||
|
||||
- `Address.toPayable`: added a helper to convert between address types without having to resort to low-level casting. ([#1773](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1773))
|
||||
- Facilities to make metatransaction-enabled contracts through the Gas Station Network. ([#1844](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1844))
|
||||
- `Address.sendValue`: added a replacement to Solidity's `transfer`, removing the fixed gas stipend. ([#1962](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1962))
|
||||
- Added replacement for functions that don't forward all gas (which have been deprecated): ([#1976](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1976))
|
||||
- `PullPayment.withdrawPaymentsWithGas(address payable payee)`
|
||||
- `Escrow.withdrawWithGas(address payable payee)`
|
||||
- `SafeMath`: added support for custom error messages to `sub`, `div` and `mod` functions. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828))
|
||||
|
||||
### Improvements
|
||||
|
||||
- `Address.isContract`: switched from `extcodesize` to `extcodehash` for less gas usage. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802))
|
||||
- `ERC20` and `ERC777` updated to throw custom errors on subtraction overflows. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828))
|
||||
|
||||
### Deprecations
|
||||
|
||||
- Deprecated functions that don't forward all gas: ([#1976](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1976))
|
||||
- `PullPayment.withdrawPayments(address payable payee)`
|
||||
- `Escrow.withdraw(address payable payee)`
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- `Address` now requires a minimum Solidity compiler version of 0.5.5. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802))
|
||||
- `SignatureBouncer` has been removed from drafts, both to avoid confusions with the GSN and `GSNRecipientSignature` (previously called `GSNBouncerSignature`) and because the API was not very clear. ([#1879](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1879))
|
||||
|
||||
### How to upgrade from 2.4.0-beta
|
||||
|
||||
The final 2.4.0 release includes a refactor of the GSN contracts that will be a breaking change for 2.4.0-beta users.
|
||||
|
||||
- The default empty implementations of `_preRelayedCall` and `_postRelayedCall` were removed and must now be explicitly implemented always in custom recipients. If your custom recipient didn't include an implementation, you can provide an empty one.
|
||||
- `GSNRecipient`, `GSNBouncerBase`, and `GSNContext` were all merged into `GSNRecipient`.
|
||||
- `GSNBouncerSignature` and `GSNBouncerERC20Fee` were renamed to `GSNRecipientSignature` and `GSNRecipientERC20Fee`.
|
||||
- It is no longer necessary to inherit from `GSNRecipient` when using `GSNRecipientSignature` and `GSNRecipientERC20Fee`.
|
||||
|
||||
For example, a contract using `GSNBouncerSignature` would have to be changed in the following way.
|
||||
|
||||
```diff
|
||||
-contract MyDapp is GSNRecipient, GSNBouncerSignature {
|
||||
+contract MyDapp is GSNRecipientSignature {
|
||||
```
|
||||
|
||||
Refer to the table below to adjust your inheritance list.
|
||||
|
||||
| 2.4.0-beta | 2.4.0 |
|
||||
| ----------------------------------- | ----------------------- |
|
||||
| `GSNRecipient, GSNBouncerSignature` | `GSNRecipientSignature` |
|
||||
| `GSNRecipient, GSNBouncerERC20Fee` | `GSNRecipientERC20Fee` |
|
||||
| `GSNBouncerBase` | `GSNRecipient` |
|
||||
|
||||
## 2.3.0 (2019-05-27)
|
||||
|
||||
### New features
|
||||
|
||||
- `ERC1820`: added support for interacting with the [ERC1820](https://eips.ethereum.org/EIPS/eip-1820) registry contract (`IERC1820Registry`), as well as base contracts that can be registered as implementers there. ([#1677](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1677))
|
||||
- `ERC777`: support for the [ERC777 token](https://eips.ethereum.org/EIPS/eip-777), which has multiple improvements over `ERC20` (but is backwards compatible with it) such as built-in burning, a more straightforward permission system, and optional sender and receiver hooks on transfer (mandatory for contracts!). ([#1684](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1684))
|
||||
- All contracts now have revert reason strings, which give insight into error conditions, and help debug failing transactions. ([#1704](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1704))
|
||||
|
||||
### Improvements
|
||||
|
||||
- Reverted the Solidity version bump done in v2.2.0, setting the minimum compiler version to v0.5.0, to prevent unexpected build breakage. Users are encouraged however to stay on top of new compiler releases, which usually include bugfixes. ([#1729](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1729))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- `PostDeliveryCrowdsale`: some validations where skipped when paired with other crowdsale flavors, such as `AllowanceCrowdsale`, or `MintableCrowdsale` and `ERC20Capped`, which could cause buyers to not be able to claim their purchased tokens. ([#1721](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1721))
|
||||
- `ERC20._transfer`: the `from` argument was allowed to be the zero address, so it was possible to internally trigger a transfer of 0 tokens from the zero address. This address is not a valid destinatary of transfers, nor can it give or receive allowance, so this behavior was inconsistent. It now reverts. ([#1752](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1752))
|
||||
|
||||
## 2.2.0 (2019-03-14)
|
||||
|
||||
### New features
|
||||
|
||||
- `ERC20Snapshot`: create snapshots on demand of the token balances and total supply, to later retrieve and e.g. calculate dividends at a past time. ([#1617](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1617))
|
||||
- `SafeERC20`: `ERC20` contracts with no return value (i.e. that revert on failure) are now supported. ([#1655](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1655))
|
||||
- `ERC20`: added internal `_approve(address owner, address spender, uint256 value)`, allowing derived contracts to set the allowance of arbitrary accounts. ([#1609](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1609))
|
||||
- `ERC20Metadata`: added internal `_setTokenURI(string memory tokenURI)`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618))
|
||||
- `TimedCrowdsale`: added internal `_extendTime(uint256 newClosingTime)` as well as `TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime)` event allowing to extend the crowdsale, as long as it hasn't already closed.
|
||||
|
||||
### Improvements
|
||||
|
||||
- Upgraded the minimum compiler version to v0.5.2: this removes many Solidity warnings that were false positives. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606))
|
||||
- `ECDSA`: `recover` no longer accepts malleable signatures (those using upper-range values for `s`, or 0/1 for `v`). ([#1622](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1622))
|
||||
- `ERC721`'s transfers are now more gas efficient due to removal of unnecessary `SafeMath` calls. ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610))
|
||||
- Fixed variable shadowing issues. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- (minor) `SafeERC20`: `safeApprove` wasn't properly checking for a zero allowance when attempting to set a non-zero allowance. ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647))
|
||||
|
||||
### Breaking changes in drafts
|
||||
|
||||
- `TokenMetadata` has been renamed to `ERC20Metadata`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618))
|
||||
- The library `Counter` has been renamed to `Counters` and its API has been improved. See an example in `ERC721`, lines [17](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L17) and [204](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L204). ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610))
|
||||
|
||||
## 2.1.3 (2019-02-26)
|
||||
|
||||
- Backported `SafeERC20.safeApprove` bugfix. ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647))
|
||||
|
||||
## 2.1.2 (2019-01-17)
|
||||
|
||||
- Removed most of the test suite from the npm package, except `PublicRole.behavior.js`, which may be useful to users testing their own `Roles`.
|
||||
|
||||
## 2.1.1 (2019-01-04)
|
||||
|
||||
- Version bump to avoid conflict in the npm registry.
|
||||
|
||||
## 2.1.0 (2019-01-04)
|
||||
|
||||
### New features
|
||||
|
||||
- Now targeting the 0.5.x line of Solidity compilers. For 0.4.24 support, use version 2.0 of OpenZeppelin.
|
||||
- `WhitelistCrowdsale`: a crowdsale where only whitelisted accounts (`WhitelistedRole`) can purchase tokens. Adding or removing accounts from the whitelist is done by whitelist admins (`WhitelistAdminRole`). Similar to the pre-2.0 `WhitelistedCrowdsale`. ([#1525](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1525), [#1589](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1589))
|
||||
- `RefundablePostDeliveryCrowdsale`: replacement for `RefundableCrowdsale` (deprecated, see below) where tokens are only granted once the crowdsale ends (if it meets its goal). ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543))
|
||||
- `PausableCrowdsale`: allows for pausers (`PauserRole`) to pause token purchases. Other crowdsale operations (e.g. withdrawals and refunds, if applicable) are not affected. ([#832](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/832))
|
||||
- `ERC20`: `transferFrom` and `_burnFrom ` now emit `Approval` events, to represent the token's state comprehensively through events. ([#1524](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1524))
|
||||
- `ERC721`: added `_burn(uint256 tokenId)`, replacing the similar deprecated function (see below). ([#1550](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1550))
|
||||
- `ERC721`: added `_tokensOfOwner(address owner)`, allowing to internally retrieve the array of an account's owned tokens. ([#1522](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1522))
|
||||
- Crowdsales: all constructors are now `public`, meaning it is not necessary to extend these contracts in order to deploy them. The exception is `FinalizableCrowdsale`, since it is meaningless unless extended. ([#1564](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1564))
|
||||
- `SignedSafeMath`: added overflow-safe operations for signed integers (`int256`). ([#1559](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1559), [#1588](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1588))
|
||||
|
||||
### Improvements
|
||||
|
||||
- The compiler version required by `Array` was behind the rest of the library so it was updated to `v0.4.24`. ([#1553](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1553))
|
||||
- Now conforming to a 4-space indentation code style. ([1508](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1508))
|
||||
- `ERC20`: more gas efficient due to removed redundant `require`s. ([#1409](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1409))
|
||||
- `ERC721`: fixed a bug that prevented internal data structures from being properly cleaned, missing potential gas refunds. ([#1539](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1539) and [#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549))
|
||||
- `ERC721`: general gas savings on `transferFrom`, `_mint` and `_burn`, due to redundant `require`s and `SSTORE`s. ([#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
### Breaking changes
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `ERC721._burn(address owner, uint256 tokenId)`: due to the `owner` parameter being unnecessary. ([#1550](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1550))
|
||||
- `RefundableCrowdsale`: due to trading abuse potential on crowdsales that miss their goal. ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543))
|
||||
@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at maintainers@openzeppelin.org. All
|
||||
reported by contacting the project team at contact@openzeppelin.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
# Code Style
|
||||
|
||||
We value clean code and consistency, and those are prerequisites for us to
|
||||
include new code in the repository. Before proposing a change, please read this
|
||||
document and take some time to familiarize yourself with the style of the
|
||||
existing codebase.
|
||||
|
||||
## Solidity code
|
||||
|
||||
In order to be consistent with all the other Solidity projects, we follow the
|
||||
[official recommendations documented in the Solidity style guide](http://solidity.readthedocs.io/en/latest/style-guide.html).
|
||||
|
||||
Any exception or additions specific to our project are documented below.
|
||||
|
||||
### Naming
|
||||
|
||||
* Try to avoid acronyms and abbreviations.
|
||||
|
||||
* All state variables should be private.
|
||||
|
||||
* Private state variables should have an underscore prefix.
|
||||
|
||||
```
|
||||
contract TestContract {
|
||||
uint256 private _privateVar;
|
||||
uint256 internal _internalVar;
|
||||
}
|
||||
```
|
||||
|
||||
* Parameters must not be prefixed with an underscore.
|
||||
|
||||
```
|
||||
function test(uint256 testParameter1, uint256 testParameter2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* Internal and private functions should have an underscore prefix.
|
||||
|
||||
```
|
||||
function _testInternal() internal {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
function _testPrivate() private {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* Events should be emitted immediately after the state change that they
|
||||
represent, and consequently they should be named in past tense.
|
||||
|
||||
```
|
||||
function _burn(address _who, uint256 _value) internal {
|
||||
super._burn(_who, _value);
|
||||
emit TokensBurned(_who, _value);
|
||||
}
|
||||
```
|
||||
|
||||
Some standards (e.g. ERC20) use present tense, and in those cases the
|
||||
standard specification prevails.
|
||||
@ -1,58 +1,36 @@
|
||||
Contributing to OpenZeppelin
|
||||
=======
|
||||
# Contributing Guidelines
|
||||
|
||||
We really appreciate and value contributions to OpenZeppelin. Please take 5' to review the items listed below to make sure that your contributions are merged as soon as possible.
|
||||
There are many ways to contribute to OpenZeppelin Contracts.
|
||||
|
||||
## Contribution guidelines
|
||||
## Troubleshooting
|
||||
|
||||
Smart contracts manage value and are highly vulnerable to errors and attacks. We have very strict guidelines, please make sure to review them: ["Contribution guidelines wiki entry"](https://github.com/OpenZeppelin/openzeppelin-solidity/wiki/Contribution-guidelines).
|
||||
You can help other users in the community to solve their smart contract issues in the [OpenZeppelin Forum].
|
||||
|
||||
## Creating Pull Requests (PRs)
|
||||
[OpenZeppelin Forum]: https://forum.openzeppelin.com/
|
||||
|
||||
As a contributor, you are expected to fork this repository, work on your own fork and then submit pull requests. The pull requests will be reviewed and eventually merged into the main repo. See ["Fork-a-Repo"](https://help.github.com/articles/fork-a-repo/) for how this works.
|
||||
## Opening an issue
|
||||
|
||||
*IMPORTANT*
|
||||
* Please see ["Git flow wiki entry"](https://github.com/OpenZeppelin/openzeppelin-solidity/wiki/Git-flow) for understanding how to use branches in this repository.
|
||||
You can [open an issue] to suggest a feature or report a minor bug. For serious bugs please do not open an issue, instead refer to our [security policy] for appropriate steps.
|
||||
|
||||
## A typical workflow
|
||||
If you believe your issue may be due to user error and not a problem in the library, consider instead posting a question on the [OpenZeppelin Forum].
|
||||
|
||||
1) Make sure your fork is up to date with the main repository:
|
||||
Before opening an issue, be sure to search through the existing open and closed issues, and consider posting a comment in one of those instead.
|
||||
|
||||
```
|
||||
cd openzeppelin-solidity
|
||||
git remote add upstream https://github.com/OpenZeppelin/openzeppelin-solidity.git
|
||||
git fetch upstream
|
||||
git pull --rebase upstream master
|
||||
```
|
||||
NOTE: The directory `openzeppelin-solidity` represents your fork's local copy.
|
||||
When requesting a new feature, include as many details as you can, especially around the use cases that motivate it. Features are prioritized according to the impact they may have on the ecosystem, so we appreciate information showing that the impact could be high.
|
||||
|
||||
2) Branch out from `master` into `fix/some-bug-#123`:
|
||||
(Postfixing #123 will associate your PR with the issue #123 and make everyone's life easier =D)
|
||||
```
|
||||
git checkout -b fix/some-bug-#123
|
||||
```
|
||||
[security policy]: https://github.com/OpenZeppelin/openzeppelin-contracts/security
|
||||
[open an issue]: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose
|
||||
|
||||
3) Make your changes, add your files, commit and push to your fork.
|
||||
## Submitting a pull request
|
||||
|
||||
```
|
||||
git add SomeFile.js
|
||||
git commit "Fix some bug #123"
|
||||
git push origin fix/some-bug-#123
|
||||
```
|
||||
If you would like to contribute code or documentation you may do so by forking the repository and submitting a pull request.
|
||||
|
||||
4) Go to [github.com/OpenZeppelin/openzeppelin-solidity](https://github.com/OpenZeppelin/zeppelin-solidity) in your web browser and issue a new pull request.
|
||||
Any non-trivial code contribution must be first discussed with the maintainers in an issue (see [Opening an issue](#opening-an-issue)). Only very minor changes are accepted without prior discussion.
|
||||
|
||||
*IMPORTANT* Read the PR template very carefully and make sure to follow all the instructions. These instructions
|
||||
refer to some very important conditions that your PR must meet in order to be accepted, such as making sure that all tests pass, JS linting tests pass, solidity linting tests pass, etc.
|
||||
Make sure to read and follow the [engineering guidelines](./GUIDELINES.md). Run linter and tests to make sure your pull request is good before submitting it.
|
||||
|
||||
5) Maintainers will review your code and possibly ask for changes before your code is pulled in to the main repository. We'll check that all tests pass, review the coding style, and check for general code correctness. If everything is OK, we'll merge your pull request and your code will be part of OpenZeppelin.
|
||||
Changelog entries should be added to each pull request by using [Changesets](https://github.com/changesets/changesets/).
|
||||
|
||||
*IMPORTANT* Please pay attention to the maintainer's feedback, since its a necessary step to keep up with the standards OpenZeppelin attains to.
|
||||
When opening the pull request you will be presented with a template and a series of instructions. Read through it carefully and follow all the steps. Expect a review and feedback from the maintainers afterwards.
|
||||
|
||||
## All set!
|
||||
|
||||
If you have any questions feel free to post them to github.com/OpenZeppelin/openzeppelin-solidity/issues.
|
||||
|
||||
Finally, if you're looking to collaborate and want to find easy tasks to start, look at the issues we marked as ["Good first issue"](https://github.com/OpenZeppelin/openzeppelin-solidity/labels/good%20first%20issue).
|
||||
|
||||
Thanks for your time and code!
|
||||
If you're looking for a good place to start, look for issues labelled ["good first issue"](https://github.com/OpenZeppelin/openzeppelin-contracts/labels/good%20first%20issue)!
|
||||
|
||||
117
GUIDELINES.md
Normal file
117
GUIDELINES.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Engineering Guidelines
|
||||
|
||||
## Testing
|
||||
|
||||
Code must be thoroughly tested with quality unit tests.
|
||||
|
||||
We defer to the [Moloch Testing Guide](https://github.com/MolochVentures/moloch/tree/master/test#readme) for specific recommendations, though not all of it is relevant here. Note the introduction:
|
||||
|
||||
> Tests should be written, not only to verify correctness of the target code, but to be comprehensively reviewed by other programmers. Therefore, for mission critical Solidity code, the quality of the tests are just as important (if not more so) than the code itself, and should be written with the highest standards of clarity and elegance.
|
||||
|
||||
Every addition or change to the code must come with relevant and comprehensive tests.
|
||||
|
||||
Refactors should avoid simultaneous changes to tests.
|
||||
|
||||
Flaky tests are not acceptable.
|
||||
|
||||
The test suite should run automatically for every change in the repository, and in pull requests tests must pass before merging.
|
||||
|
||||
The test suite coverage must be kept as close to 100% as possible, enforced in pull requests.
|
||||
|
||||
In some cases unit tests may be insufficient and complementary techniques should be used:
|
||||
|
||||
1. Property-based tests (aka. fuzzing) for math-heavy code.
|
||||
2. Formal verification for state machines.
|
||||
|
||||
## Code style
|
||||
|
||||
Solidity code should be written in a consistent format enforced by a linter, following the official [Solidity Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html). See below for further [Solidity Conventions](#solidity-conventions).
|
||||
|
||||
The code should be simple and straightforward, prioritizing readability and understandability. Consistency and predictability should be maintained across the codebase. In particular, this applies to naming, which should be systematic, clear, and concise.
|
||||
|
||||
Sometimes these guidelines may be broken if doing so brings significant efficiency gains, but explanatory comments should be added.
|
||||
|
||||
Modularity should be pursued, but not at the cost of the above priorities.
|
||||
|
||||
## Documentation
|
||||
|
||||
For contributors, project guidelines and processes must be documented publicly.
|
||||
|
||||
For users, features must be abundantly documented. Documentation should include answers to common questions, solutions to common problems, and recommendations for critical decisions that the user may face.
|
||||
|
||||
All changes to the core codebase (excluding tests, auxiliary scripts, etc.) must be documented in a changelog, except for purely cosmetic or documentation changes.
|
||||
|
||||
## Peer review
|
||||
|
||||
All changes must be submitted through pull requests and go through peer code review.
|
||||
|
||||
The review must be approached by the reviewer in a similar way as if it was an audit of the code in question (but importantly it is not a substitute for and should not be considered an audit).
|
||||
|
||||
Reviewers should enforce code and project guidelines.
|
||||
|
||||
External contributions must be reviewed separately by multiple maintainers.
|
||||
|
||||
## Automation
|
||||
|
||||
Automation should be used as much as possible to reduce the possibility of human error and forgetfulness.
|
||||
|
||||
Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions worklows](https://github.com/nikitastupin/pwnhub).
|
||||
|
||||
Some other examples of automation are:
|
||||
|
||||
- Looking for common security vulnerabilities or errors in our code (eg. reentrancy analysis).
|
||||
- Keeping dependencies up to date and monitoring for vulnerable dependencies.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Pull requests are squash-merged to keep the `master` branch history clean. The title of the pull request becomes the commit message, so it should be written in a consistent format:
|
||||
|
||||
1) Begin with a capital letter.
|
||||
2) Do not end with a period.
|
||||
3) Write in the imperative: "Add feature X" and not "Adds feature X" or "Added feature X".
|
||||
|
||||
This repository does not follow conventional commits, so do not prefix the title with "fix:" or "feat:".
|
||||
|
||||
Work in progress pull requests should be submitted as Drafts and should not be prefixed with "WIP:".
|
||||
|
||||
Branch names don't matter, and commit messages within a pull request mostly don't matter either, although they can help the review process.
|
||||
|
||||
# Solidity Conventions
|
||||
|
||||
In addition to the official Solidity Style Guide we have a number of other conventions that must be followed.
|
||||
|
||||
* All state variables should be private.
|
||||
|
||||
Changes to state should be accompanied by events, and in some cases it is not correct to arbitrarily set state. Encapsulating variables as private and only allowing modification via setters enables us to ensure that events and other rules are followed reliably and prevents this kind of user error.
|
||||
|
||||
* Internal or private state variables or functions should have an underscore prefix.
|
||||
|
||||
```solidity
|
||||
contract TestContract {
|
||||
uint256 private _privateVar;
|
||||
uint256 internal _internalVar;
|
||||
function _testInternal() internal { ... }
|
||||
function _testPrivate() private { ... }
|
||||
}
|
||||
```
|
||||
|
||||
* Events should be emitted immediately after the state change that they
|
||||
represent, and should be named in the past tense.
|
||||
|
||||
```solidity
|
||||
function _burn(address who, uint256 value) internal {
|
||||
super._burn(who, value);
|
||||
emit TokensBurned(who, value);
|
||||
}
|
||||
```
|
||||
|
||||
Some standards (e.g. ERC20) use present tense, and in those cases the
|
||||
standard specification is used.
|
||||
|
||||
* Interface names should have a capital I prefix.
|
||||
|
||||
```solidity
|
||||
interface IERC777 {
|
||||
```
|
||||
|
||||
* Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted.
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Smart Contract Solutions, Inc.
|
||||
Copyright (c) 2016-2023 zOS Global Limited and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
90
README.md
90
README.md
@ -1,58 +1,90 @@
|
||||
# <img src="logo.png" alt="OpenZeppelin" width="400px">
|
||||
# <img src="logo.svg" alt="OpenZeppelin" height="40px">
|
||||
|
||||
[](https://www.npmjs.org/package/openzeppelin-solidity)
|
||||
[](https://travis-ci.org/OpenZeppelin/openzeppelin-solidity)
|
||||
[](https://coveralls.io/github/OpenZeppelin/openzeppelin-solidity?branch=master)
|
||||
[](https://www.npmjs.org/package/@openzeppelin/contracts)
|
||||
[](https://codecov.io/gh/OpenZeppelin/openzeppelin-contracts)
|
||||
[](https://www.gitpoap.io/gh/OpenZeppelin/openzeppelin-contracts)
|
||||
[](https://docs.openzeppelin.com/contracts)
|
||||
[](https://docs.openzeppelin.com/contracts)
|
||||
|
||||
**OpenZeppelin is a library for secure smart contract development.** It provides implementations of standards like ERC20 and ERC721 which you can deploy as-is or extend to suit your needs, as well as Solidity components to build custom contracts and more complex decentralized systems.
|
||||
**A library for secure smart contract development.** Build on a solid foundation of community-vetted code.
|
||||
|
||||
## Install
|
||||
* Implementations of standards like [ERC20](https://docs.openzeppelin.com/contracts/erc20) and [ERC721](https://docs.openzeppelin.com/contracts/erc721).
|
||||
* Flexible [role-based permissioning](https://docs.openzeppelin.com/contracts/access-control) scheme.
|
||||
* Reusable [Solidity components](https://docs.openzeppelin.com/contracts/utilities) to build custom contracts and complex decentralized systems.
|
||||
|
||||
:mage: **Not sure how to get started?** Check out [Contracts Wizard](https://wizard.openzeppelin.com/) — an interactive smart contract generator.
|
||||
|
||||
:building_construction: **Want to scale your decentralized application?** Check out [OpenZeppelin Defender](https://openzeppelin.com/defender) — a secure platform for automating and monitoring your operations.
|
||||
|
||||
## Overview
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
npm install openzeppelin-solidity
|
||||
$ npm install @openzeppelin/contracts
|
||||
```
|
||||
|
||||
## Usage
|
||||
OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/contracts/releases-stability#api-stability), which means that your contracts won't break unexpectedly when upgrading to a newer minor version.
|
||||
|
||||
To write your custom contracts, import ours and extend them through inheritance.
|
||||
An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch.
|
||||
|
||||
### Usage
|
||||
|
||||
Once installed, you can use the contracts in the library by importing them:
|
||||
|
||||
```solidity
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol';
|
||||
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol';
|
||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
|
||||
contract MyNFT is ERC721Full, ERC721Mintable {
|
||||
constructor() ERC721Full("MyNFT", "MNFT") public {
|
||||
}
|
||||
contract MyCollectible is ERC721 {
|
||||
constructor() ERC721("MyCollectible", "MCO") {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> You need an ethereum development framework for the above import statements to work! Check out these guides for [Truffle] or [Embark].
|
||||
_If you're new to smart contract development, head to [Developing Smart Contracts](https://docs.openzeppelin.com/learn/developing-smart-contracts) to learn about creating a new project and compiling your contracts._
|
||||
|
||||
On our site you will find a few [guides] to learn about the diferent parts of OpenZeppelin, as well as [documentation for the API][API docs]. Keep in mind that the API docs are work in progress, and don’t hesitate to ask questions in [our Slack][Slack].
|
||||
To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs.
|
||||
|
||||
## Learn More
|
||||
|
||||
The guides in the [documentation site](https://docs.openzeppelin.com/contracts) will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides:
|
||||
|
||||
* [Access Control](https://docs.openzeppelin.com/contracts/access-control): decide who can perform each of the actions on your system.
|
||||
* [Tokens](https://docs.openzeppelin.com/contracts/tokens): create tradeable assets or collectives, and distribute them via [Crowdsales](https://docs.openzeppelin.com/contracts/crowdsales).
|
||||
* [Utilities](https://docs.openzeppelin.com/contracts/utilities): generic useful tools including non-overflowing math, signature verification, and trustless paying systems.
|
||||
|
||||
The [full API](https://docs.openzeppelin.com/contracts/api/token/ERC20) is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts's development in the [community forum](https://forum.openzeppelin.com).
|
||||
|
||||
Finally, you may want to take a look at the [guides on our blog](https://blog.openzeppelin.com/guides), which cover several common use cases and good practices. The following articles provide great background reading, though please note that some of the referenced tools have changed, as the tooling in the ecosystem continues to rapidly evolve.
|
||||
|
||||
* [The Hitchhiker’s Guide to Smart Contracts in Ethereum](https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05) will help you get an overview of the various tools available for smart contract development, and help you set up your environment.
|
||||
* [A Gentle Introduction to Ethereum Programming, Part 1](https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094) provides very useful information on an introductory level, including many basic concepts from the Ethereum platform.
|
||||
* For a more in-depth dive, you may read the guide [Designing the Architecture for Your Ethereum Application](https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317), which discusses how to better structure your application and its relationship to the real world.
|
||||
|
||||
## Security
|
||||
|
||||
OpenZeppelin the project is maintained by [Zeppelin] the company, and developed following our high standards for code quality and security. OpenZeppelin is meant to provide tested and community-audited code, but please use common sense when doing anything that deals with real money! We take no responsibility for your implementation decisions and any security problems you might experience.
|
||||
This project is maintained by [OpenZeppelin](https://openzeppelin.com) with the goal of providing a secure and reliable library of smart contract components for the ecosystem. We address security through risk management in various areas such as engineering and open source best practices, scoping and API design, multi-layered review processes, and incident response preparedness.
|
||||
|
||||
The core development principles and strategies that OpenZeppelin is based on include: security in depth, simple and modular code, clarity-driven naming conventions, comprehensive unit testing, pre-and-post-condition sanity checks, code consistency, and regular audits.
|
||||
The security policy is detailed in [`SECURITY.md`](./SECURITY.md), and specifies how you can report security vulnerabilities, which versions will receive security patches, and how to stay informed about them. We run a [bug bounty program on Immunefi](https://immunefi.com/bounty/openzeppelin) to reward the responsible disclosure of vulnerabilities.
|
||||
|
||||
Please report any security issues you find to security@openzeppelin.org.
|
||||
The engineering guidelines we follow to promote project quality can be found in [`GUIDELINES.md`](./GUIDELINES.md).
|
||||
|
||||
Past audits can be found in [`audits/`](./audits).
|
||||
|
||||
Smart contracts are a nascent technology and carry a high level of technical risk and uncertainty. Although OpenZeppelin is well known for its security audits, using OpenZeppelin Contracts is not a substitute for a security audit.
|
||||
|
||||
OpenZeppelin Contracts is made available under the MIT License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including OpenZeppelin. As set out further in the Terms, you acknowledge that you are solely responsible for any use of OpenZeppelin Contracts and you assume all risks associated with any such use.
|
||||
|
||||
## Contribute
|
||||
|
||||
OpenZeppelin exists thanks to its contributors. There are many ways you can participate and help build high quality software. Check out the [contribution guide]!
|
||||
OpenZeppelin Contracts exists thanks to its contributors. There are many ways you can participate and help build high quality software. Check out the [contribution guide](CONTRIBUTING.md)!
|
||||
|
||||
## License
|
||||
|
||||
OpenZeppelin is released under the [MIT License](LICENSE).
|
||||
OpenZeppelin Contracts is released under the [MIT License](LICENSE).
|
||||
|
||||
## Legal
|
||||
|
||||
[API docs]: https://openzeppelin.org/api/docs/token_ERC721_ERC721BasicToken.html
|
||||
[guides]: https://openzeppelin.org/api/docs/get-started.html
|
||||
[Slack]: https://slack.openzeppelin.org
|
||||
[Zeppelin]: https://zeppelin.solutions
|
||||
[contribution guide]: CONTRIBUTING.md
|
||||
[Truffle]: https://truffleframework.com/docs/truffle/quickstart
|
||||
[Embark]: https://embark.status.im/docs/quick_start.html
|
||||
Your use of this Project is governed by the terms found at www.openzeppelin.com/tos (the "Terms").
|
||||
|
||||
123
RELEASING.md
123
RELEASING.md
@ -1,96 +1,47 @@
|
||||
# Releasing
|
||||
|
||||
This document describes our release process, and contains the steps to be followed by an OpenZeppelin maintainer at the several stages of a release.
|
||||
> Visit the documentation for [details about release schedule](https://docs.openzeppelin.com/contracts/releases-stability).
|
||||
|
||||
We release a new version of OpenZeppelin monthly. Release cycles are tracked in the [issue milestones](https://github.com/OpenZeppelin/openzeppelin-solidity/milestones).
|
||||
OpenZeppelin Contracts uses a fully automated release process that takes care of compiling, packaging, and publishing the library, all of which is carried out in a clean CI environment (GitHub Actions), implemented in the ([`release-cycle`](.github/workflows/release-cycle.yml)) workflow. This helps to reduce the potential for human error and inconsistencies, and ensures that the release process is ongoing and reliable.
|
||||
|
||||
Each release has at least one release candidate published first, intended for community review and any critical fixes that may come out of it. At the moment we leave 1 week between the first release candidate and the final release.
|
||||
## Changesets
|
||||
|
||||
Before starting make sure to verify the following items.
|
||||
* Your local `master` branch is in sync with your `upstream` remote (it may have another name depending on your setup).
|
||||
* Your repo is clean, particularly with no untracked files in the contracts and tests directories. Verify with `git clean -n`.
|
||||
[Changesets](https://github.com/changesets/changesets/) is used as part of our release process for `CHANGELOG.md` management. Each change that is relevant for the codebase is expected to include a changeset.
|
||||
|
||||
## Branching model
|
||||
|
||||
## Creating the release branch
|
||||
The release cycle happens on release branches called `release-vX.Y`. Each of these branches starts as a release candidate (rc) and is eventually promoted to final.
|
||||
|
||||
We'll refer to a release `vX.Y.Z`.
|
||||
A release branch can be updated with cherry-picked patches from `master`, or may sometimes be committed to directly in the case of old releases. These commits will lead to a new release candidate or a patch increment depending on the state of the release branch.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'gitGraph': {'mainBranchName': 'master'}} }%%
|
||||
gitGraph
|
||||
commit id: "Feature A"
|
||||
commit id: "Feature B"
|
||||
branch release-vX.Y
|
||||
commit id: "Start release"
|
||||
commit id: "Release vX.Y.0-rc.0"
|
||||
|
||||
checkout master
|
||||
commit id: "Feature C"
|
||||
commit id: "Fix A"
|
||||
|
||||
checkout release-vX.Y
|
||||
cherry-pick id: "Fix A" tag: ""
|
||||
commit id: "Release vX.Y.0-rc.1"
|
||||
commit id: "Release vX.Y.0"
|
||||
|
||||
checkout master
|
||||
merge release-vX.Y
|
||||
commit id: "Feature D"
|
||||
commit id: "Patch B"
|
||||
|
||||
checkout release-vX.Y
|
||||
cherry-pick id: "Patch B" tag: ""
|
||||
commit id: "Release vX.Y.1"
|
||||
|
||||
checkout master
|
||||
merge release-vX.Y
|
||||
commit id: "Feature E"
|
||||
```
|
||||
git checkout master
|
||||
git checkout -b release-vX.Y.Z
|
||||
```
|
||||
|
||||
## Creating a release candidate
|
||||
|
||||
Once in the release branch, change the version string in `package.json`, `package-lock.json` and `ethpm.json` to `X.Y.Z-rc.R`. (This will be `X.Y.Z-rc.1` for the first release candidate.) Commit these changes and tag the commit as `vX.Y.Z-rc.R`.
|
||||
|
||||
```
|
||||
git add package.json package-lock.json ethpm.json
|
||||
git commit -m "Release candidate vX.Y.Z-rc.R"
|
||||
git tag -a vX.Y.Z-rc.R
|
||||
git push upstream release-vX.Y.Z
|
||||
git push upstream vX.Y.Z-rc.R
|
||||
```
|
||||
|
||||
Draft the release notes in our [GitHub releases](https://github.com/OpenZeppelin/openzeppelin-solidity/releases). Make sure to mark it as a pre-release! Try to be consistent with our previous release notes in the title and format of the text. Release candidates don't need a detailed changelog, but make sure to include a link to GitHub's compare page.
|
||||
|
||||
Once the CI run for the new tag is green, publish on npm under the `next` tag.
|
||||
|
||||
```
|
||||
npm publish --tag next
|
||||
```
|
||||
|
||||
Publish the release notes on GitHub and ask our community manager to announce the release candidate on at least Slack and Twitter.
|
||||
|
||||
## Creating the final release
|
||||
|
||||
Make sure to have the latest changes from `upstream` in your local release branch.
|
||||
|
||||
```
|
||||
git checkout release-vX.Y.Z
|
||||
git pull upstream
|
||||
```
|
||||
|
||||
Change the version string in `package.json`, `package-lock.json` and `ethpm.json` removing the "-rc.R" suffix. Commit these changes and tag the commit as `vX.Y.Z`.
|
||||
|
||||
```
|
||||
git add package.json package-lock.json ethpm.json
|
||||
git commit -m "Release vX.Y.Z"
|
||||
git tag -a vX.Y.Z
|
||||
git push upstream vX.Y.Z
|
||||
```
|
||||
|
||||
Draft the release notes in GitHub releases. Try to be consistent with our previous release notes in the title and format of the text. Make sure to include a detailed changelog.
|
||||
|
||||
Once the CI run for the new tag is green, publish on npm.
|
||||
|
||||
```
|
||||
npm publish
|
||||
```
|
||||
|
||||
Publish the release notes on GitHub and ask our community manager to announce the release!
|
||||
|
||||
Delete the `next` tag in the npm package as there is no longer a release candidate.
|
||||
|
||||
```
|
||||
npm dist-tag rm --otp $2FA_CODE openzeppelin-solidity next
|
||||
```
|
||||
|
||||
## Merging the release branch
|
||||
|
||||
After the final release, the release branch should be merged back into `master`. This merge must not be squashed because it would lose the tagged release commit. Since the GitHub repo is set up to only allow squashed merges, the merge should be done locally and pushed.
|
||||
|
||||
Make sure to have the latest changes from `upstream` in your local release branch.
|
||||
|
||||
```
|
||||
git checkout release-vX.Y.Z
|
||||
git pull upstream
|
||||
```
|
||||
|
||||
```
|
||||
git checkout master
|
||||
git merge --no-ff release-vX.Y.Z
|
||||
git push upstream master
|
||||
```
|
||||
|
||||
The release branch can then be deleted on GitHub.
|
||||
|
||||
42
SECURITY.md
Normal file
42
SECURITY.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Security Policy
|
||||
|
||||
Security vulnerabilities should be disclosed to the project maintainers through [Immunefi], or alternatively by email to security@openzeppelin.com.
|
||||
|
||||
[Immunefi]: https://immunefi.com/bounty/openzeppelin
|
||||
|
||||
## Bug Bounty
|
||||
|
||||
Responsible disclosure of security vulnerabilities is rewarded through a bug bounty program on [Immunefi].
|
||||
|
||||
There is a bonus reward for issues introduced in release candidates that are reported before making it into a stable release.
|
||||
|
||||
## Security Patches
|
||||
|
||||
Security vulnerabilities will be patched as soon as responsibly possible, and published as an advisory on this repository (see [advisories]) and on the affected npm packages.
|
||||
|
||||
[advisories]: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories
|
||||
|
||||
Projects that build on OpenZeppelin Contracts are encouraged to clearly state, in their source code and websites, how to be contacted about security issues in the event that a direct notification is considered necessary. We recommend including it in the NatSpec for the contract as `/// @custom:security-contact security@example.com`.
|
||||
|
||||
Additionally, we recommend installing the library through npm and setting up vulnerability alerts such as [Dependabot].
|
||||
|
||||
[Dependabot]: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security#what-is-dependabot
|
||||
|
||||
### Supported Versions
|
||||
|
||||
Security patches will be released for the latest minor of a given major release. For example, if an issue is found in versions >=4.6.0 and the latest is 4.8.0, the patch will be released only in version 4.8.1.
|
||||
|
||||
Only critical severity bug fixes will be backported to past major releases.
|
||||
|
||||
| Version | Critical security fixes | Other security fixes |
|
||||
| ------- | ----------------------- | -------------------- |
|
||||
| 4.x | :white_check_mark: | :white_check_mark: |
|
||||
| 3.4 | :white_check_mark: | :x: |
|
||||
| 2.5 | :white_check_mark: | :x: |
|
||||
| < 2.0 | :x: | :x: |
|
||||
|
||||
Note as well that the Solidity language itself only guarantees security updates for the latest release.
|
||||
|
||||
## Legal
|
||||
|
||||
Smart contracts are a nascent techology and carry a high level of technical risk and uncertainty. OpenZeppelin Contracts is made available under the MIT License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including OpenZeppelin. Your use of the project is also governed by the terms found at www.openzeppelin.com/tos (the "Terms"). As set out in the Terms, you are solely responsible for any use of OpenZeppelin Contracts and you assume all risks associated with any such use. This Security Policy in no way evidences or represents an on-going duty by any contributor, including OpenZeppelin, to correct any flaws or alert you to all or any of the potential risks of utilizing the project.
|
||||
@ -1,5 +1,7 @@
|
||||
# OpenZeppelin Audit
|
||||
|
||||
NOTE ON 2021-07-19: This report makes reference to Zeppelin, OpenZeppelin, OpenZeppelin Contracts, the OpenZeppelin team, and OpenZeppelin library. Many of these things have since been renamed and know that this audit applies to what is currently called the OpenZeppelin Contracts which are maintained by the OpenZeppelin Contracts Community.
|
||||
|
||||
March, 2017
|
||||
Authored by Dennis Peterson and Peter Vessenes
|
||||
|
||||
@ -131,7 +133,7 @@ I presume that the goal of this contract is to allow and annotate a migration to
|
||||
|
||||
We like these pauses! Note that these allow significant griefing potential by owners, and that this might not be obvious to participants in smart contracts using the OpenZeppelin framework. We would recommend that additional sample logic be added to for instance the TokenContract showing safer use of the pause and resume functions. In particular, we would recommend a timelock after which anyone could unpause the contract.
|
||||
|
||||
The modifers use the pattern `if(bool){_;}`. This is fine for functions that return false upon failure, but could be problematic for functions expected to throw upon failure. See our comments above on standardizing on `throw` or `return(false)`.
|
||||
The modifiers use the pattern `if(bool){_;}`. This is fine for functions that return false upon failure, but could be problematic for functions expected to throw upon failure. See our comments above on standardizing on `throw` or `return(false)`.
|
||||
|
||||
## Ownership
|
||||
|
||||
BIN
audits/2018-10.pdf
Normal file
BIN
audits/2018-10.pdf
Normal file
Binary file not shown.
BIN
audits/2022-10-Checkpoints.pdf
Normal file
BIN
audits/2022-10-Checkpoints.pdf
Normal file
Binary file not shown.
BIN
audits/2022-10-ERC4626.pdf
Normal file
BIN
audits/2022-10-ERC4626.pdf
Normal file
Binary file not shown.
BIN
audits/2023-05-v4.9.pdf
Normal file
BIN
audits/2023-05-v4.9.pdf
Normal file
Binary file not shown.
16
audits/README.md
Normal file
16
audits/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Audits
|
||||
|
||||
| Date | Version | Commit | Auditor | Scope | Links |
|
||||
| ------------ | ------- | --------- | ------------ | -------------------- | ----------------------------------------------------------- |
|
||||
| May 2023 | v4.9.0 | `91df66c` | OpenZeppelin | v4.9 Changes | [🔗](./2023-05-v4.9.pdf) |
|
||||
| October 2022 | v4.8.0 | `14f98db` | OpenZeppelin | ERC4626, Checkpoints | [🔗](./2022-10-ERC4626.pdf) [🔗](./2022-10-Checkpoints.pdf) |
|
||||
| October 2018 | v2.0.0 | `dac5bcc` | LevelK | Everything | [🔗](./2018-10.pdf) |
|
||||
| March 2017 | v1.0.4 | `9c5975a` | New Alchemy | Everything | [🔗](./2017-03.md) |
|
||||
|
||||
# Formal Verification
|
||||
|
||||
| Date | Version | Commit | Tool | Scope | Links |
|
||||
| ------------ | ------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| May 2022 | v4.7.0 | `109778c` | Certora | Initializable, GovernorPreventLateQuorum, ERC1155Burnable, ERC1155Pausable, ERC1155Supply, ERC1155Holder, ERC1155Receiver | [🔗](../certora/reports/2022-05.pdf) |
|
||||
| March 2022 | v4.4.0 | `4088540` | Certora | ERC20Votes, ERC20FlashMint, ERC20Wrapper, TimelockController, ERC721Votes, Votes, AccessControl, ERC1155 | [🔗](../certora/reports/2022-03.pdf) |
|
||||
| October 2021 | v4.4.0 | `4088540` | Certora | Governor, GovernorCountingSimple, GovernorProposalThreshold, GovernorTimelockControl, GovernorVotes, GovernorVotesQuorumFraction | [🔗](../certora/reports/2021-10.pdf) |
|
||||
1
certora/.gitignore
vendored
Normal file
1
certora/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
patched
|
||||
54
certora/Makefile
Normal file
54
certora/Makefile
Normal file
@ -0,0 +1,54 @@
|
||||
default: help
|
||||
|
||||
SRC := ../contracts
|
||||
DST := patched
|
||||
DIFF := diff
|
||||
SRCS := $(shell find $(SRC) -type f)
|
||||
DSTS := $(shell find $(DST) -type f)
|
||||
DIFFS := $(shell find $(DIFF) -type f)
|
||||
|
||||
###############################################################################
|
||||
# Apply all patches in the $DIFF folder to the $DST folder
|
||||
apply: $(DST) $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$(DIFFS)))
|
||||
|
||||
# Reset the $DST folder
|
||||
$(DST): FORCE
|
||||
@rm -rf $@
|
||||
@cp -r $(SRC) $@
|
||||
|
||||
# Update a solidity file in the $DST directory using the corresponding patch
|
||||
$(DST)/%.sol: FORCE
|
||||
@echo Applying patch to $@
|
||||
@patch -p0 -d $(DST) < $(patsubst $(DST)_%,$(DIFF)/%.patch,$(subst /,_,$@))
|
||||
|
||||
###############################################################################
|
||||
# Record all difference between $SRC and $DST in patches
|
||||
record: $(DIFF) $(patsubst %,$(DIFF)/%.patch,$(subst /,_,$(subst $(SRC)/,,$(SRCS)) $(subst $(DST)/,,$(DSTS))))
|
||||
|
||||
# Create the $DIFF folder
|
||||
$(DIFF): FORCE
|
||||
@rm -rf $@
|
||||
@mkdir $@
|
||||
|
||||
# Create the patch file by comparing the source and the destination
|
||||
$(DIFF)/%.patch: FORCE
|
||||
@echo Generating patch $@
|
||||
@diff -ruN \
|
||||
$(patsubst $(DIFF)/%.patch,$(SRC)/%,$(subst _,/,$@)) \
|
||||
$(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$@)) \
|
||||
| sed 's+$(SRC)/++g' \
|
||||
| sed 's+$(DST)/++g' \
|
||||
> $@
|
||||
@[ -s $@ ] || rm $@
|
||||
|
||||
###############################################################################
|
||||
help:
|
||||
@echo "usage:"
|
||||
@echo " make apply: create $(DST) directory by applying the patches to $(SRC)"
|
||||
@echo " make record: record the patches capturing the differences between $(SRC) and $(DST)"
|
||||
@echo " make clean: remove all generated files (those ignored by git)"
|
||||
|
||||
clean:
|
||||
git clean -fdX
|
||||
|
||||
FORCE: ;
|
||||
60
certora/README.md
Normal file
60
certora/README.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Running the certora verification tool
|
||||
|
||||
These instructions detail the process for running Certora Verification Tool on OpenZeppelin Contracts.
|
||||
|
||||
Documentation for CVT and the specification language are available [here](https://certora.atlassian.net/wiki/spaces/CPD/overview).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Follow the [Certora installation guide](https://docs.certora.com/en/latest/docs/user-guide/getting-started/install.html) in order to get the Certora Prover Package and the `solc` executable folder in your path.
|
||||
|
||||
> **Note**
|
||||
> An API Key is required for local testing. Although the prover will run on a Github Actions' CI environment on selected Pull Requests.
|
||||
|
||||
## Running the verification
|
||||
|
||||
The Certora Verification Tool proves specs for contracts, which are defined by the `./specs.json` file along with their pre-configured options.
|
||||
|
||||
The verification script `./run.js` is used to submit verification jobs to the Certora Verification service.
|
||||
|
||||
You can run it from the root of the repository with the following command:
|
||||
|
||||
```bash
|
||||
node certora/run.js [[CONTRACT_NAME:]SPEC_NAME] [OPTIONS...]
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `CONTRACT_NAME` matches the `contract` key in the `./spec.json` file and may be empty. It will run all matching contracts if not provided.
|
||||
- `SPEC_NAME` refers to a `spec` key from the `./specs.json` file. It will run every spec if not provided.
|
||||
- `OPTIONS` extend the [Certora Prover CLI options](https://docs.certora.com/en/latest/docs/prover/cli/options.html#certora-prover-cli-options) and will respect the preconfigured options in the `specs.json` file.
|
||||
|
||||
> **Note**
|
||||
> A single spec may be configured to run for multiple contracts, whereas a single contract may run multiple specs.
|
||||
|
||||
Example usage:
|
||||
|
||||
```bash
|
||||
node certora/run.js AccessControl # Run the AccessControl spec against every contract implementing it
|
||||
```
|
||||
|
||||
## Adapting to changes in the contracts
|
||||
|
||||
Some of our rules require the code to be simplified in various ways. Our primary tool for performing these simplifications is to run verification on a contract that extends the original contracts and overrides some of the methods. These "harness" contracts can be found in the `certora/harness` directory.
|
||||
|
||||
This pattern does require some modifications to the original code: some methods need to be made virtual or public, for example. These changes are handled by applying a patch
|
||||
to the code before verification by running:
|
||||
|
||||
```bash
|
||||
make -C certora apply
|
||||
```
|
||||
|
||||
Before running the `certora/run.js` script, it's required to apply the corresponding patches to the `contracts` directory, placing the output in the `certora/patched` directory. Then, the contracts are verified by running the verification for the `certora/patched` directory.
|
||||
|
||||
If the original contracts change, it is possible to create a conflict with the patch. In this case, the verify scripts will report an error message and output rejected changes in the `patched` directory. After merging the changes, run `make record` in the `certora` directory; this will regenerate the patch file, which can then be checked into git.
|
||||
|
||||
For more information about the `make` scripts available, run:
|
||||
|
||||
```bash
|
||||
make -C certora help
|
||||
```
|
||||
14
certora/diff/token_ERC721_ERC721.sol.patch
Normal file
14
certora/diff/token_ERC721_ERC721.sol.patch
Normal file
@ -0,0 +1,14 @@
|
||||
--- token/ERC721/ERC721.sol 2023-03-07 10:48:47.736822221 +0100
|
||||
+++ token/ERC721/ERC721.sol 2023-03-09 19:49:39.669338673 +0100
|
||||
@@ -199,6 +199,11 @@
|
||||
return _owners[tokenId];
|
||||
}
|
||||
|
||||
+ // FV
|
||||
+ function _getApproved(uint256 tokenId) internal view returns (address) {
|
||||
+ return _tokenApprovals[tokenId];
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* @dev Returns whether `tokenId` exists.
|
||||
*
|
||||
47
certora/harnesses/AccessControlDefaultAdminRulesHarness.sol
Normal file
47
certora/harnesses/AccessControlDefaultAdminRulesHarness.sol
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/access/AccessControlDefaultAdminRules.sol";
|
||||
|
||||
contract AccessControlDefaultAdminRulesHarness is AccessControlDefaultAdminRules {
|
||||
uint48 private _delayIncreaseWait;
|
||||
|
||||
constructor(
|
||||
uint48 initialDelay,
|
||||
address initialDefaultAdmin,
|
||||
uint48 delayIncreaseWait
|
||||
) AccessControlDefaultAdminRules(initialDelay, initialDefaultAdmin) {
|
||||
_delayIncreaseWait = delayIncreaseWait;
|
||||
}
|
||||
|
||||
// FV
|
||||
function pendingDefaultAdmin_() external view returns (address) {
|
||||
(address newAdmin, ) = pendingDefaultAdmin();
|
||||
return newAdmin;
|
||||
}
|
||||
|
||||
function pendingDefaultAdminSchedule_() external view returns (uint48) {
|
||||
(, uint48 schedule) = pendingDefaultAdmin();
|
||||
return schedule;
|
||||
}
|
||||
|
||||
function pendingDelay_() external view returns (uint48) {
|
||||
(uint48 newDelay, ) = pendingDefaultAdminDelay();
|
||||
return newDelay;
|
||||
}
|
||||
|
||||
function pendingDelaySchedule_() external view returns (uint48) {
|
||||
(, uint48 schedule) = pendingDefaultAdminDelay();
|
||||
return schedule;
|
||||
}
|
||||
|
||||
function delayChangeWait_(uint48 newDelay) external view returns (uint48) {
|
||||
return _delayChangeWait(newDelay);
|
||||
}
|
||||
|
||||
// Overrides
|
||||
function defaultAdminDelayIncreaseWait() public view override returns (uint48) {
|
||||
return _delayIncreaseWait;
|
||||
}
|
||||
}
|
||||
7
certora/harnesses/AccessControlHarness.sol
Normal file
7
certora/harnesses/AccessControlHarness.sol
Normal file
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/access/AccessControl.sol";
|
||||
|
||||
contract AccessControlHarness is AccessControl {}
|
||||
59
certora/harnesses/DoubleEndedQueueHarness.sol
Normal file
59
certora/harnesses/DoubleEndedQueueHarness.sol
Normal file
@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/utils/structs/DoubleEndedQueue.sol";
|
||||
|
||||
contract DoubleEndedQueueHarness {
|
||||
using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque;
|
||||
|
||||
DoubleEndedQueue.Bytes32Deque private _deque;
|
||||
|
||||
function pushFront(bytes32 value) external {
|
||||
_deque.pushFront(value);
|
||||
}
|
||||
|
||||
function pushBack(bytes32 value) external {
|
||||
_deque.pushBack(value);
|
||||
}
|
||||
|
||||
function popFront() external returns (bytes32 value) {
|
||||
return _deque.popFront();
|
||||
}
|
||||
|
||||
function popBack() external returns (bytes32 value) {
|
||||
return _deque.popBack();
|
||||
}
|
||||
|
||||
function clear() external {
|
||||
_deque.clear();
|
||||
}
|
||||
|
||||
function begin() external view returns (int128) {
|
||||
return _deque._begin;
|
||||
}
|
||||
|
||||
function end() external view returns (int128) {
|
||||
return _deque._end;
|
||||
}
|
||||
|
||||
function length() external view returns (uint256) {
|
||||
return _deque.length();
|
||||
}
|
||||
|
||||
function empty() external view returns (bool) {
|
||||
return _deque.empty();
|
||||
}
|
||||
|
||||
function front() external view returns (bytes32 value) {
|
||||
return _deque.front();
|
||||
}
|
||||
|
||||
function back() external view returns (bytes32 value) {
|
||||
return _deque.back();
|
||||
}
|
||||
|
||||
function at_(uint256 index) external view returns (bytes32 value) {
|
||||
return _deque.at(index);
|
||||
}
|
||||
}
|
||||
36
certora/harnesses/ERC20FlashMintHarness.sol
Normal file
36
certora/harnesses/ERC20FlashMintHarness.sol
Normal file
@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/token/ERC20/ERC20.sol";
|
||||
import "../patched/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import "../patched/token/ERC20/extensions/ERC20FlashMint.sol";
|
||||
|
||||
contract ERC20FlashMintHarness is ERC20, ERC20Permit, ERC20FlashMint {
|
||||
uint256 someFee;
|
||||
address someFeeReceiver;
|
||||
|
||||
constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
|
||||
|
||||
function mint(address account, uint256 amount) external {
|
||||
_mint(account, amount);
|
||||
}
|
||||
|
||||
function burn(address account, uint256 amount) external {
|
||||
_burn(account, amount);
|
||||
}
|
||||
|
||||
// public accessor
|
||||
function flashFeeReceiver() public view returns (address) {
|
||||
return someFeeReceiver;
|
||||
}
|
||||
|
||||
// internal hook
|
||||
function _flashFee(address, uint256) internal view override returns (uint256) {
|
||||
return someFee;
|
||||
}
|
||||
|
||||
function _flashFeeReceiver() internal view override returns (address) {
|
||||
return someFeeReceiver;
|
||||
}
|
||||
}
|
||||
17
certora/harnesses/ERC20PermitHarness.sol
Normal file
17
certora/harnesses/ERC20PermitHarness.sol
Normal file
@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/token/ERC20/extensions/ERC20Permit.sol";
|
||||
|
||||
contract ERC20PermitHarness is ERC20Permit {
|
||||
constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
|
||||
|
||||
function mint(address account, uint256 amount) external {
|
||||
_mint(account, amount);
|
||||
}
|
||||
|
||||
function burn(address account, uint256 amount) external {
|
||||
_burn(account, amount);
|
||||
}
|
||||
}
|
||||
25
certora/harnesses/ERC20WrapperHarness.sol
Normal file
25
certora/harnesses/ERC20WrapperHarness.sol
Normal file
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/token/ERC20/extensions/ERC20Wrapper.sol";
|
||||
|
||||
contract ERC20WrapperHarness is ERC20Wrapper {
|
||||
constructor(IERC20 _underlying, string memory _name, string memory _symbol) ERC20(_name, _symbol) ERC20Wrapper(_underlying) {}
|
||||
|
||||
function underlyingTotalSupply() public view returns (uint256) {
|
||||
return underlying().totalSupply();
|
||||
}
|
||||
|
||||
function underlyingBalanceOf(address account) public view returns (uint256) {
|
||||
return underlying().balanceOf(account);
|
||||
}
|
||||
|
||||
function underlyingAllowanceToThis(address account) public view returns (uint256) {
|
||||
return underlying().allowance(account, address(this));
|
||||
}
|
||||
|
||||
function recover(address account) public returns (uint256) {
|
||||
return _recover(account);
|
||||
}
|
||||
}
|
||||
13
certora/harnesses/ERC3156FlashBorrowerHarness.sol
Normal file
13
certora/harnesses/ERC3156FlashBorrowerHarness.sol
Normal file
@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import "../patched/interfaces/IERC3156FlashBorrower.sol";
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower {
|
||||
bytes32 somethingToReturn;
|
||||
|
||||
function onFlashLoan(address, address, uint256, uint256, bytes calldata) external view override returns (bytes32) {
|
||||
return somethingToReturn;
|
||||
}
|
||||
}
|
||||
37
certora/harnesses/ERC721Harness.sol
Normal file
37
certora/harnesses/ERC721Harness.sol
Normal file
@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/token/ERC721/ERC721.sol";
|
||||
|
||||
contract ERC721Harness is ERC721 {
|
||||
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
|
||||
|
||||
function mint(address account, uint256 tokenId) external {
|
||||
_mint(account, tokenId);
|
||||
}
|
||||
|
||||
function safeMint(address to, uint256 tokenId) external {
|
||||
_safeMint(to, tokenId);
|
||||
}
|
||||
|
||||
function safeMint(address to, uint256 tokenId, bytes memory data) external {
|
||||
_safeMint(to, tokenId, data);
|
||||
}
|
||||
|
||||
function burn(uint256 tokenId) external {
|
||||
_burn(tokenId);
|
||||
}
|
||||
|
||||
function tokenExists(uint256 tokenId) external view returns (bool) {
|
||||
return _exists(tokenId);
|
||||
}
|
||||
|
||||
function unsafeOwnerOf(uint256 tokenId) external view returns (address) {
|
||||
return _ownerOf(tokenId);
|
||||
}
|
||||
|
||||
function unsafeGetApproved(uint256 tokenId) external view returns (address) {
|
||||
return _getApproved(tokenId);
|
||||
}
|
||||
}
|
||||
11
certora/harnesses/ERC721ReceiverHarness.sol
Normal file
11
certora/harnesses/ERC721ReceiverHarness.sol
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/interfaces/IERC721Receiver.sol";
|
||||
|
||||
contract ERC721ReceiverHarness is IERC721Receiver {
|
||||
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
|
||||
return this.onERC721Received.selector;
|
||||
}
|
||||
}
|
||||
55
certora/harnesses/EnumerableMapHarness.sol
Normal file
55
certora/harnesses/EnumerableMapHarness.sol
Normal file
@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/utils/structs/EnumerableMap.sol";
|
||||
|
||||
contract EnumerableMapHarness {
|
||||
using EnumerableMap for EnumerableMap.Bytes32ToBytes32Map;
|
||||
|
||||
EnumerableMap.Bytes32ToBytes32Map private _map;
|
||||
|
||||
function set(bytes32 key, bytes32 value) public returns (bool) {
|
||||
return _map.set(key, value);
|
||||
}
|
||||
|
||||
function remove(bytes32 key) public returns (bool) {
|
||||
return _map.remove(key);
|
||||
}
|
||||
|
||||
function contains(bytes32 key) public view returns (bool) {
|
||||
return _map.contains(key);
|
||||
}
|
||||
|
||||
function length() public view returns (uint256) {
|
||||
return _map.length();
|
||||
}
|
||||
|
||||
function key_at(uint256 index) public view returns (bytes32) {
|
||||
(bytes32 key,) = _map.at(index);
|
||||
return key;
|
||||
}
|
||||
|
||||
function value_at(uint256 index) public view returns (bytes32) {
|
||||
(,bytes32 value) = _map.at(index);
|
||||
return value;
|
||||
}
|
||||
|
||||
function tryGet_contains(bytes32 key) public view returns (bool) {
|
||||
(bool contained,) = _map.tryGet(key);
|
||||
return contained;
|
||||
}
|
||||
|
||||
function tryGet_value(bytes32 key) public view returns (bytes32) {
|
||||
(,bytes32 value) = _map.tryGet(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
function get(bytes32 key) public view returns (bytes32) {
|
||||
return _map.get(key);
|
||||
}
|
||||
|
||||
function _indexOf(bytes32 key) public view returns (uint256) {
|
||||
return _map._keys._inner._indexes[key];
|
||||
}
|
||||
}
|
||||
35
certora/harnesses/EnumerableSetHarness.sol
Normal file
35
certora/harnesses/EnumerableSetHarness.sol
Normal file
@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/utils/structs/EnumerableSet.sol";
|
||||
|
||||
contract EnumerableSetHarness {
|
||||
using EnumerableSet for EnumerableSet.Bytes32Set;
|
||||
|
||||
EnumerableSet.Bytes32Set private _set;
|
||||
|
||||
function add(bytes32 value) public returns (bool) {
|
||||
return _set.add(value);
|
||||
}
|
||||
|
||||
function remove(bytes32 value) public returns (bool) {
|
||||
return _set.remove(value);
|
||||
}
|
||||
|
||||
function contains(bytes32 value) public view returns (bool) {
|
||||
return _set.contains(value);
|
||||
}
|
||||
|
||||
function length() public view returns (uint256) {
|
||||
return _set.length();
|
||||
}
|
||||
|
||||
function at_(uint256 index) public view returns (bytes32) {
|
||||
return _set.at(index);
|
||||
}
|
||||
|
||||
function _indexOf(bytes32 value) public view returns (uint256) {
|
||||
return _set._inner._indexes[value];
|
||||
}
|
||||
}
|
||||
23
certora/harnesses/InitializableHarness.sol
Normal file
23
certora/harnesses/InitializableHarness.sol
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.2;
|
||||
|
||||
import "../patched/proxy/utils/Initializable.sol";
|
||||
|
||||
contract InitializableHarness is Initializable {
|
||||
function initialize() public initializer {}
|
||||
function reinitialize(uint8 n) public reinitializer(n) {}
|
||||
function disable() public { _disableInitializers(); }
|
||||
|
||||
function nested_init_init() public initializer { initialize(); }
|
||||
function nested_init_reinit(uint8 m) public initializer { reinitialize(m); }
|
||||
function nested_reinit_init(uint8 n) public reinitializer(n) { initialize(); }
|
||||
function nested_reinit_reinit(uint8 n, uint8 m) public reinitializer(n) { reinitialize(m); }
|
||||
|
||||
function version() public view returns (uint8) {
|
||||
return _getInitializedVersion();
|
||||
}
|
||||
|
||||
function initializing() public view returns (bool) {
|
||||
return _isInitializing();
|
||||
}
|
||||
}
|
||||
9
certora/harnesses/Ownable2StepHarness.sol
Normal file
9
certora/harnesses/Ownable2StepHarness.sol
Normal file
@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/access/Ownable2Step.sol";
|
||||
|
||||
contract Ownable2StepHarness is Ownable2Step {
|
||||
function restricted() external onlyOwner {}
|
||||
}
|
||||
9
certora/harnesses/OwnableHarness.sol
Normal file
9
certora/harnesses/OwnableHarness.sol
Normal file
@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/access/Ownable.sol";
|
||||
|
||||
contract OwnableHarness is Ownable {
|
||||
function restricted() external onlyOwner {}
|
||||
}
|
||||
19
certora/harnesses/PausableHarness.sol
Normal file
19
certora/harnesses/PausableHarness.sol
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/security/Pausable.sol";
|
||||
|
||||
contract PausableHarness is Pausable {
|
||||
function pause() external {
|
||||
_pause();
|
||||
}
|
||||
|
||||
function unpause() external {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
function onlyWhenPaused() external whenPaused {}
|
||||
|
||||
function onlyWhenNotPaused() external whenNotPaused {}
|
||||
}
|
||||
12
certora/harnesses/TimelockControllerHarness.sol
Normal file
12
certora/harnesses/TimelockControllerHarness.sol
Normal file
@ -0,0 +1,12 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../patched/governance/TimelockController.sol";
|
||||
|
||||
contract TimelockControllerHarness is TimelockController {
|
||||
constructor(
|
||||
uint256 minDelay,
|
||||
address[] memory proposers,
|
||||
address[] memory executors,
|
||||
address admin
|
||||
) TimelockController(minDelay, proposers, executors, admin) {}
|
||||
}
|
||||
BIN
certora/reports/2021-10.pdf
Normal file
BIN
certora/reports/2021-10.pdf
Normal file
Binary file not shown.
BIN
certora/reports/2022-03.pdf
Normal file
BIN
certora/reports/2022-03.pdf
Normal file
Binary file not shown.
BIN
certora/reports/2022-05.pdf
Normal file
BIN
certora/reports/2022-05.pdf
Normal file
Binary file not shown.
146
certora/run.js
Executable file
146
certora/run.js
Executable file
@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// USAGE:
|
||||
// node certora/run.js [[CONTRACT_NAME:]SPEC_NAME]* [--all] [--options OPTIONS...] [--specs PATH]
|
||||
// EXAMPLES:
|
||||
// node certora/run.js --all
|
||||
// node certora/run.js AccessControl
|
||||
// node certora/run.js AccessControlHarness:AccessControl
|
||||
|
||||
const proc = require('child_process');
|
||||
const { PassThrough } = require('stream');
|
||||
const events = require('events');
|
||||
|
||||
const argv = require('yargs')
|
||||
.env('')
|
||||
.options({
|
||||
all: {
|
||||
alias: 'a',
|
||||
type: 'boolean',
|
||||
},
|
||||
spec: {
|
||||
alias: 's',
|
||||
type: 'string',
|
||||
default: __dirname + '/specs.json',
|
||||
},
|
||||
parallel: {
|
||||
alias: 'p',
|
||||
type: 'number',
|
||||
default: 4,
|
||||
},
|
||||
options: {
|
||||
alias: 'o',
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
}).argv;
|
||||
|
||||
function match(entry, request) {
|
||||
const [reqSpec, reqContract] = request.split(':').reverse();
|
||||
return entry.spec == reqSpec && (!reqContract || entry.contract == reqContract);
|
||||
}
|
||||
|
||||
const specs = require(argv.spec).filter(s => argv.all || argv._.some(r => match(s, r)));
|
||||
const limit = require('p-limit')(argv.parallel);
|
||||
|
||||
if (argv._.length == 0 && !argv.all) {
|
||||
console.error(`Warning: No specs requested. Did you forgot to toggle '--all'?`);
|
||||
}
|
||||
|
||||
for (const r of argv._) {
|
||||
if (!specs.some(s => match(s, r))) {
|
||||
console.error(`Error: Requested spec '${r}' not found in ${argv.spec}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.exitCode) {
|
||||
process.exit(process.exitCode);
|
||||
}
|
||||
|
||||
for (const { spec, contract, files, options = [] } of specs) {
|
||||
limit(runCertora, spec, contract, files, [...options.flatMap(opt => opt.split(' ')), ...argv.options]);
|
||||
}
|
||||
|
||||
// Run certora, aggregate the output and print it at the end
|
||||
async function runCertora(spec, contract, files, options = []) {
|
||||
const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options];
|
||||
const child = proc.spawn('certoraRun', args);
|
||||
|
||||
const stream = new PassThrough();
|
||||
const output = collect(stream);
|
||||
|
||||
child.stdout.pipe(stream, { end: false });
|
||||
child.stderr.pipe(stream, { end: false });
|
||||
|
||||
// as soon as we have a job id, print the output link
|
||||
stream.on('data', function logStatusUrl(data) {
|
||||
const { '-DjobId': jobId, '-DuserId': userId } = Object.fromEntries(
|
||||
data
|
||||
.toString('utf8')
|
||||
.match(/-D\S+=\S+/g)
|
||||
?.map(s => s.split('=')) || [],
|
||||
);
|
||||
|
||||
if (jobId && userId) {
|
||||
console.error(`[${spec}] https://prover.certora.com/output/${userId}/${jobId}/`);
|
||||
stream.off('data', logStatusUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// wait for process end
|
||||
const [code, signal] = await events.once(child, 'exit');
|
||||
|
||||
// error
|
||||
if (code || signal) {
|
||||
console.error(`[${spec}] Exited with code ${code || signal}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
// get all output
|
||||
stream.end();
|
||||
|
||||
// write results in markdown format
|
||||
writeEntry(spec, contract, code || signal, (await output).match(/https:\/\/prover.certora.com\/output\/\S*/)?.[0]);
|
||||
|
||||
// write all details
|
||||
console.error(`+ certoraRun ${args.join(' ')}\n` + (await output));
|
||||
}
|
||||
|
||||
// Collects stream data into a string
|
||||
async function collect(stream) {
|
||||
const buffers = [];
|
||||
for await (const data of stream) {
|
||||
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
||||
buffers.push(buf);
|
||||
}
|
||||
return Buffer.concat(buffers).toString('utf8');
|
||||
}
|
||||
|
||||
// Formatting
|
||||
let hasHeader = false;
|
||||
|
||||
function formatRow(...array) {
|
||||
return ['', ...array, ''].join(' | ');
|
||||
}
|
||||
|
||||
function writeHeader() {
|
||||
console.log(formatRow('spec', 'contract', 'result', 'status', 'output'));
|
||||
console.log(formatRow('-', '-', '-', '-', '-'));
|
||||
}
|
||||
|
||||
function writeEntry(spec, contract, success, url) {
|
||||
if (!hasHeader) {
|
||||
hasHeader = true;
|
||||
writeHeader();
|
||||
}
|
||||
console.log(
|
||||
formatRow(
|
||||
spec,
|
||||
contract,
|
||||
success ? ':x:' : ':heavy_check_mark:',
|
||||
url ? `[link](${url?.replace('/output/', '/jobStatus/')})` : 'error',
|
||||
url ? `[link](${url})` : 'error',
|
||||
),
|
||||
);
|
||||
}
|
||||
86
certora/specs.json
Normal file
86
certora/specs.json
Normal file
@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"spec": "Pausable",
|
||||
"contract": "PausableHarness",
|
||||
"files": ["certora/harnesses/PausableHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "AccessControl",
|
||||
"contract": "AccessControlHarness",
|
||||
"files": ["certora/harnesses/AccessControlHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "AccessControlDefaultAdminRules",
|
||||
"contract": "AccessControlDefaultAdminRulesHarness",
|
||||
"files": ["certora/harnesses/AccessControlDefaultAdminRulesHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "DoubleEndedQueue",
|
||||
"contract": "DoubleEndedQueueHarness",
|
||||
"files": ["certora/harnesses/DoubleEndedQueueHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "Ownable",
|
||||
"contract": "OwnableHarness",
|
||||
"files": ["certora/harnesses/OwnableHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "Ownable2Step",
|
||||
"contract": "Ownable2StepHarness",
|
||||
"files": ["certora/harnesses/Ownable2StepHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "ERC20",
|
||||
"contract": "ERC20PermitHarness",
|
||||
"files": ["certora/harnesses/ERC20PermitHarness.sol"],
|
||||
"options": ["--optimistic_loop"]
|
||||
},
|
||||
{
|
||||
"spec": "ERC20FlashMint",
|
||||
"contract": "ERC20FlashMintHarness",
|
||||
"files": [
|
||||
"certora/harnesses/ERC20FlashMintHarness.sol",
|
||||
"certora/harnesses/ERC3156FlashBorrowerHarness.sol"
|
||||
],
|
||||
"options": ["--optimistic_loop"]
|
||||
},
|
||||
{
|
||||
"spec": "ERC20Wrapper",
|
||||
"contract": "ERC20WrapperHarness",
|
||||
"files": [
|
||||
"certora/harnesses/ERC20PermitHarness.sol",
|
||||
"certora/harnesses/ERC20WrapperHarness.sol"
|
||||
],
|
||||
"options": [
|
||||
"--link ERC20WrapperHarness:_underlying=ERC20PermitHarness",
|
||||
"--optimistic_loop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"spec": "ERC721",
|
||||
"contract": "ERC721Harness",
|
||||
"files": ["certora/harnesses/ERC721Harness.sol", "certora/harnesses/ERC721ReceiverHarness.sol"],
|
||||
"options": ["--optimistic_loop"]
|
||||
},
|
||||
{
|
||||
"spec": "Initializable",
|
||||
"contract": "InitializableHarness",
|
||||
"files": ["certora/harnesses/InitializableHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "EnumerableSet",
|
||||
"contract": "EnumerableSetHarness",
|
||||
"files": ["certora/harnesses/EnumerableSetHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "EnumerableMap",
|
||||
"contract": "EnumerableMapHarness",
|
||||
"files": ["certora/harnesses/EnumerableMapHarness.sol"]
|
||||
},
|
||||
{
|
||||
"spec": "TimelockController",
|
||||
"contract": "TimelockControllerHarness",
|
||||
"files": ["certora/harnesses/TimelockControllerHarness.sol"],
|
||||
"options": ["--optimistic_hashing", "--optimistic_loop"]
|
||||
}
|
||||
]
|
||||
126
certora/specs/AccessControl.spec
Normal file
126
certora/specs/AccessControl.spec
Normal file
@ -0,0 +1,126 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IAccessControl.spec"
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Definitions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
definition DEFAULT_ADMIN_ROLE() returns bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000;
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Identify entrypoints: only grantRole, revokeRole and renounceRole can alter permissions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyGrantCanGrant(env e, method f, bytes32 role, address account) {
|
||||
calldataarg args;
|
||||
|
||||
bool hasRoleBefore = hasRole(role, account);
|
||||
f(e, args);
|
||||
bool hasRoleAfter = hasRole(role, account);
|
||||
|
||||
assert (
|
||||
!hasRoleBefore &&
|
||||
hasRoleAfter
|
||||
) => (
|
||||
f.selector == grantRole(bytes32, address).selector
|
||||
);
|
||||
|
||||
assert (
|
||||
hasRoleBefore &&
|
||||
!hasRoleAfter
|
||||
) => (
|
||||
f.selector == revokeRole(bytes32, address).selector ||
|
||||
f.selector == renounceRole(bytes32, address).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: grantRole only affects the specified user/role combo │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule grantRoleEffect(env e, bytes32 role) {
|
||||
require nonpayable(e);
|
||||
|
||||
bytes32 otherRole;
|
||||
address account;
|
||||
address otherAccount;
|
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender);
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount);
|
||||
|
||||
grantRole@withrevert(e, role, account);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount);
|
||||
|
||||
// liveness
|
||||
assert success <=> isCallerAdmin;
|
||||
|
||||
// effect
|
||||
assert success => hasRole(role, account);
|
||||
|
||||
// no side effect
|
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: revokeRole only affects the specified user/role combo │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule revokeRoleEffect(env e, bytes32 role) {
|
||||
require nonpayable(e);
|
||||
|
||||
bytes32 otherRole;
|
||||
address account;
|
||||
address otherAccount;
|
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender);
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount);
|
||||
|
||||
revokeRole@withrevert(e, role, account);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount);
|
||||
|
||||
// liveness
|
||||
assert success <=> isCallerAdmin;
|
||||
|
||||
// effect
|
||||
assert success => !hasRole(role, account);
|
||||
|
||||
// no side effect
|
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: renounceRole only affects the specified user/role combo │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule renounceRoleEffect(env e, bytes32 role) {
|
||||
require nonpayable(e);
|
||||
|
||||
bytes32 otherRole;
|
||||
address account;
|
||||
address otherAccount;
|
||||
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount);
|
||||
|
||||
renounceRole@withrevert(e, role, account);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount);
|
||||
|
||||
// liveness
|
||||
assert success <=> account == e.msg.sender;
|
||||
|
||||
// effect
|
||||
assert success => !hasRole(role, account);
|
||||
|
||||
// no side effect
|
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount);
|
||||
}
|
||||
500
certora/specs/AccessControlDefaultAdminRules.spec
Normal file
500
certora/specs/AccessControlDefaultAdminRules.spec
Normal file
@ -0,0 +1,500 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IAccessControlDefaultAdminRules.spec"
|
||||
import "methods/IAccessControl.spec"
|
||||
import "AccessControl.spec"
|
||||
|
||||
use rule onlyGrantCanGrant filtered {
|
||||
f -> f.selector != acceptDefaultAdminTransfer().selector
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
|
||||
function max_uint48() returns mathint {
|
||||
return (1 << 48) - 1;
|
||||
}
|
||||
|
||||
function nonZeroAccount(address account) returns bool {
|
||||
return account != 0;
|
||||
}
|
||||
|
||||
function timeSanity(env e) returns bool {
|
||||
return
|
||||
e.block.timestamp > 0 && // Avoids 0 schedules
|
||||
e.block.timestamp + defaultAdminDelay(e) < max_uint48();
|
||||
}
|
||||
|
||||
function delayChangeWaitSanity(env e, uint48 newDelay) returns bool {
|
||||
return e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48();
|
||||
}
|
||||
|
||||
function isSet(uint48 schedule) returns bool {
|
||||
return schedule != 0;
|
||||
}
|
||||
|
||||
function hasPassed(env e, uint48 schedule) returns bool {
|
||||
return schedule < e.block.timestamp;
|
||||
}
|
||||
|
||||
function min(uint48 a, uint48 b) returns mathint {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
function increasingDelaySchedule(env e, uint48 newDelay) returns mathint {
|
||||
return e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait());
|
||||
}
|
||||
|
||||
function decreasingDelaySchedule(env e, uint48 newDelay) returns mathint {
|
||||
return e.block.timestamp + defaultAdminDelay(e) - newDelay;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: defaultAdmin holds the DEFAULT_ADMIN_ROLE │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant defaultAdminConsistency(address account)
|
||||
defaultAdmin() == account <=> hasRole(DEFAULT_ADMIN_ROLE(), account)
|
||||
{
|
||||
preserved {
|
||||
// defaultAdmin() returns the zero address when there's no default admin
|
||||
require nonZeroAccount(account);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: Only one account holds the DEFAULT_ADMIN_ROLE │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant singleDefaultAdmin(address account, address another)
|
||||
hasRole(DEFAULT_ADMIN_ROLE(), account) && hasRole(DEFAULT_ADMIN_ROLE(), another) => another == account
|
||||
// We filter here because we couldn't find a way to force Certora to have an initial state with
|
||||
// only one DEFAULT_ADMIN_ROLE enforced, so a counter example is a different default admin since inception
|
||||
// triggering the transfer, which is known to be impossible by definition.
|
||||
filtered { f -> f.selector != acceptDefaultAdminTransfer().selector }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: DEFAULT_ADMIN_ROLE's admin is always DEFAULT_ADMIN_ROLE │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant defaultAdminRoleAdminConsistency()
|
||||
getRoleAdmin(DEFAULT_ADMIN_ROLE()) == DEFAULT_ADMIN_ROLE()
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: owner is the defaultAdmin │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant ownerConsistency()
|
||||
defaultAdmin() == owner()
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: revokeRole only affects the specified user/role combo │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule revokeRoleEffect(env e, bytes32 role) {
|
||||
require nonpayable(e);
|
||||
|
||||
bytes32 otherRole;
|
||||
address account;
|
||||
address otherAccount;
|
||||
|
||||
bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender);
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount);
|
||||
|
||||
revokeRole@withrevert(e, role, account);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount);
|
||||
|
||||
// liveness
|
||||
assert success <=> isCallerAdmin && role != DEFAULT_ADMIN_ROLE(),
|
||||
"roles can only be revoked by their owner except for the default admin role";
|
||||
|
||||
// effect
|
||||
assert success => !hasRole(role, account), "role is revoked";
|
||||
|
||||
// no side effect
|
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount),
|
||||
"no other role is affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: renounceRole only affects the specified user/role combo │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule renounceRoleEffect(env e, bytes32 role) {
|
||||
require nonpayable(e);
|
||||
|
||||
bytes32 otherRole;
|
||||
address account;
|
||||
address otherAccount;
|
||||
|
||||
bool hasOtherRoleBefore = hasRole(otherRole, otherAccount);
|
||||
uint48 scheduleBefore = pendingDefaultAdminSchedule_();
|
||||
address pendingAdminBefore = pendingDefaultAdmin_();
|
||||
|
||||
renounceRole@withrevert(e, role, account);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool hasOtherRoleAfter = hasRole(otherRole, otherAccount);
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
account == e.msg.sender &&
|
||||
(
|
||||
(
|
||||
role != DEFAULT_ADMIN_ROLE()
|
||||
) || (
|
||||
role == DEFAULT_ADMIN_ROLE() &&
|
||||
pendingAdminBefore == 0 &&
|
||||
isSet(scheduleBefore) &&
|
||||
hasPassed(e, scheduleBefore)
|
||||
)
|
||||
)
|
||||
), "an account only can renounce by itself with a delay for the default admin role";
|
||||
|
||||
// effect
|
||||
assert success => !hasRole(role, account), "role is renounced";
|
||||
|
||||
// no side effect
|
||||
assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount),
|
||||
"no other role is affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: defaultAdmin is only affected by accepting an admin transfer or renoucing │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noDefaultAdminChange(env e, method f, calldataarg args) {
|
||||
require nonZeroAccount(e.msg.sender);
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
address adminBefore = defaultAdmin();
|
||||
f(e, args);
|
||||
address adminAfter = defaultAdmin();
|
||||
|
||||
assert adminBefore != adminAfter => (
|
||||
f.selector == acceptDefaultAdminTransfer().selector ||
|
||||
f.selector == renounceRole(bytes32,address).selector
|
||||
), "default admin is only affected by accepting an admin transfer or renoucing";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: pendingDefaultAdmin is only affected by beginning, accepting or canceling an admin transfer │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noPendingDefaultAdminChange(env e, method f, calldataarg args) {
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
address pendingAdminBefore = pendingDefaultAdmin_();
|
||||
address scheduleBefore = pendingDefaultAdminSchedule_();
|
||||
f(e, args);
|
||||
address pendingAdminAfter = pendingDefaultAdmin_();
|
||||
address scheduleAfter = pendingDefaultAdminSchedule_();
|
||||
|
||||
assert (
|
||||
pendingAdminBefore != pendingAdminAfter ||
|
||||
scheduleBefore != scheduleAfter
|
||||
) => (
|
||||
f.selector == beginDefaultAdminTransfer(address).selector ||
|
||||
f.selector == acceptDefaultAdminTransfer().selector ||
|
||||
f.selector == cancelDefaultAdminTransfer().selector
|
||||
), "pending admin and its schedule is only affected by beginning, accepting or cancelling an admin transfer";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: defaultAdminDelay can't be changed atomically by any function │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noDefaultAdminDelayChange(env e, method f, calldataarg args) {
|
||||
uint48 delayBefore = defaultAdminDelay(e);
|
||||
f(e, args);
|
||||
uint48 delayAfter = defaultAdminDelay(e);
|
||||
|
||||
assert delayBefore == delayAfter, "delay can't be changed atomically by any function";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: pendingDefaultAdminDelay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noPendingDefaultAdminDelayChange(env e, method f, calldataarg args) {
|
||||
uint48 pendingDelayBefore = pendingDelay_(e);
|
||||
f(e, args);
|
||||
uint48 pendingDelayAfter = pendingDelay_(e);
|
||||
|
||||
assert pendingDelayBefore != pendingDelayAfter => (
|
||||
f.selector == changeDefaultAdminDelay(uint48).selector ||
|
||||
f.selector == rollbackDefaultAdminDelay().selector
|
||||
), "pending delay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: defaultAdminDelayIncreaseWait can't be changed atomically by any function │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noDefaultAdminDelayIncreaseWaitChange(env e, method f, calldataarg args) {
|
||||
uint48 delayIncreaseWaitBefore = defaultAdminDelayIncreaseWait();
|
||||
f(e, args);
|
||||
uint48 delayIncreaseWaitAfter = defaultAdminDelayIncreaseWait();
|
||||
|
||||
assert delayIncreaseWaitBefore == delayIncreaseWaitAfter,
|
||||
"delay increase wait can't be changed atomically by any function";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: beginDefaultAdminTransfer sets a pending default admin and its schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule beginDefaultAdminTransfer(env e, address newAdmin) {
|
||||
require nonpayable(e);
|
||||
require timeSanity(e);
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
beginDefaultAdminTransfer@withrevert(e, newAdmin);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> e.msg.sender == defaultAdmin(),
|
||||
"only the current default admin can begin a transfer";
|
||||
|
||||
// effect
|
||||
assert success => pendingDefaultAdmin_() == newAdmin,
|
||||
"pending default admin is set";
|
||||
assert success => pendingDefaultAdminSchedule_() == e.block.timestamp + defaultAdminDelay(e),
|
||||
"pending default admin delay is set";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: A default admin can't change in less than the applied schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pendingDefaultAdminDelayEnforced(env e1, env e2, method f, calldataarg args, address newAdmin) {
|
||||
require e1.block.timestamp < e2.block.timestamp;
|
||||
|
||||
uint48 delayBefore = defaultAdminDelay(e1);
|
||||
address adminBefore = defaultAdmin();
|
||||
// There might be a better way to generalize this without requiring `beginDefaultAdminTransfer`, but currently
|
||||
// it's the only way in which we can attest that only `delayBefore` has passed before a change.
|
||||
beginDefaultAdminTransfer(e1, newAdmin);
|
||||
f(e2, args);
|
||||
address adminAfter = defaultAdmin();
|
||||
|
||||
assert adminAfter == newAdmin => ((e2.block.timestamp >= e1.block.timestamp + delayBefore) || adminBefore == newAdmin),
|
||||
"A delay can't change in less than applied schedule";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: acceptDefaultAdminTransfer updates defaultAdmin resetting the pending admin and its schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule acceptDefaultAdminTransfer(env e) {
|
||||
require nonpayable(e);
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
address pendingAdminBefore = pendingDefaultAdmin_();
|
||||
uint48 scheduleAfter = pendingDefaultAdminSchedule_();
|
||||
|
||||
acceptDefaultAdminTransfer@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> e.msg.sender == pendingAdminBefore && isSet(scheduleAfter) && hasPassed(e, scheduleAfter),
|
||||
"only the pending default admin can accept the role after the schedule has been set and passed";
|
||||
|
||||
// effect
|
||||
assert success => defaultAdmin() == pendingAdminBefore,
|
||||
"Default admin is set to the previous pending default admin";
|
||||
assert success => pendingDefaultAdmin_() == 0,
|
||||
"Pending default admin is reset";
|
||||
assert success => pendingDefaultAdminSchedule_() == 0,
|
||||
"Pending default admin delay is reset";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: cancelDefaultAdminTransfer resets pending default admin and its schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule cancelDefaultAdminTransfer(env e) {
|
||||
require nonpayable(e);
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
cancelDefaultAdminTransfer@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> e.msg.sender == defaultAdmin(),
|
||||
"only the current default admin can cancel a transfer";
|
||||
|
||||
// effect
|
||||
assert success => pendingDefaultAdmin_() == 0,
|
||||
"Pending default admin is reset";
|
||||
assert success => pendingDefaultAdminSchedule_() == 0,
|
||||
"Pending default admin delay is reset";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: changeDefaultAdminDelay sets a pending default admin delay and its schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule changeDefaultAdminDelay(env e, uint48 newDelay) {
|
||||
require nonpayable(e);
|
||||
require timeSanity(e);
|
||||
require delayChangeWaitSanity(e, newDelay);
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
uint48 delayBefore = defaultAdminDelay(e);
|
||||
|
||||
changeDefaultAdminDelay@withrevert(e, newDelay);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> e.msg.sender == defaultAdmin(),
|
||||
"only the current default admin can begin a delay change";
|
||||
|
||||
// effect
|
||||
assert success => pendingDelay_(e) == newDelay, "pending delay is set";
|
||||
assert success => (
|
||||
pendingDelaySchedule_(e) > e.block.timestamp ||
|
||||
delayBefore == newDelay || // Interpreted as decreasing, x - x = 0
|
||||
defaultAdminDelayIncreaseWait() == 0
|
||||
),
|
||||
"pending delay schedule is set in the future unless accepted edge cases";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: A delay can't change in less than the applied schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pendingDelayWaitEnforced(env e1, env e2, method f, calldataarg args, uint48 newDelay) {
|
||||
require e1.block.timestamp < e2.block.timestamp;
|
||||
|
||||
uint48 delayBefore = defaultAdminDelay(e1);
|
||||
changeDefaultAdminDelay(e1, newDelay);
|
||||
f(e2, args);
|
||||
uint48 delayAfter = defaultAdminDelay(e2);
|
||||
|
||||
mathint delayWait = newDelay > delayBefore ? increasingDelaySchedule(e1, newDelay) : decreasingDelaySchedule(e1, newDelay);
|
||||
|
||||
assert delayAfter == newDelay => (e2.block.timestamp >= delayWait || delayBefore == newDelay),
|
||||
"A delay can't change in less than applied schedule";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: pending delay wait is set depending on increasing or decreasing the delay │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pendingDelayWait(env e, uint48 newDelay) {
|
||||
uint48 oldDelay = defaultAdminDelay(e);
|
||||
changeDefaultAdminDelay(e, newDelay);
|
||||
|
||||
assert newDelay > oldDelay => pendingDelaySchedule_(e) == increasingDelaySchedule(e, newDelay),
|
||||
"Delay wait is the minimum between the new delay and a threshold when the delay is increased";
|
||||
assert newDelay <= oldDelay => pendingDelaySchedule_(e) == decreasingDelaySchedule(e, newDelay),
|
||||
"Delay wait is the difference between the current and the new delay when the delay is decreased";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: rollbackDefaultAdminDelay resets the delay and its schedule │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule rollbackDefaultAdminDelay(env e) {
|
||||
require nonpayable(e);
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
rollbackDefaultAdminDelay@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> e.msg.sender == defaultAdmin(),
|
||||
"only the current default admin can rollback a delay change";
|
||||
|
||||
// effect
|
||||
assert success => pendingDelay_(e) == 0,
|
||||
"Pending default admin is reset";
|
||||
assert success => pendingDelaySchedule_(e) == 0,
|
||||
"Pending default admin delay is reset";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: pending default admin and the delay can only change along with their corresponding schedules │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pendingValueAndScheduleCoupling(env e, address newAdmin, uint48 newDelay) {
|
||||
requireInvariant defaultAdminConsistency(defaultAdmin());
|
||||
requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin());
|
||||
|
||||
// Pending admin
|
||||
address pendingAdminBefore = pendingDefaultAdmin_();
|
||||
uint48 pendingAdminScheduleBefore = pendingDefaultAdminSchedule_();
|
||||
|
||||
beginDefaultAdminTransfer(e, newAdmin);
|
||||
|
||||
address pendingAdminAfter = pendingDefaultAdmin_();
|
||||
uint48 pendingAdminScheduleAfter = pendingDefaultAdminSchedule_();
|
||||
|
||||
assert (
|
||||
pendingAdminScheduleBefore != pendingDefaultAdminSchedule_() &&
|
||||
pendingAdminBefore == pendingAdminAfter
|
||||
) => newAdmin == pendingAdminBefore, "pending admin stays the same if the new admin set is the same";
|
||||
|
||||
assert (
|
||||
pendingAdminBefore != pendingAdminAfter &&
|
||||
pendingAdminScheduleBefore == pendingDefaultAdminSchedule_()
|
||||
) => (
|
||||
// Schedule doesn't change if:
|
||||
// - The defaultAdminDelay was reduced to a value such that added to the block.timestamp is equal to previous schedule
|
||||
e.block.timestamp + defaultAdminDelay(e) == pendingAdminScheduleBefore
|
||||
), "pending admin stays the same if a default admin transfer is begun on accepted edge cases";
|
||||
|
||||
// Pending delay
|
||||
address pendingDelayBefore = pendingDelay_(e);
|
||||
uint48 pendingDelayScheduleBefore = pendingDelaySchedule_(e);
|
||||
|
||||
changeDefaultAdminDelay(e, newDelay);
|
||||
|
||||
address pendingDelayAfter = pendingDelay_(e);
|
||||
uint48 pendingDelayScheduleAfter = pendingDelaySchedule_(e);
|
||||
|
||||
assert (
|
||||
pendingDelayScheduleBefore != pendingDelayScheduleAfter &&
|
||||
pendingDelayBefore == pendingDelayAfter
|
||||
) => newDelay == pendingDelayBefore || pendingDelayBefore == 0, "pending delay stays the same if the new delay set is the same";
|
||||
|
||||
assert (
|
||||
pendingDelayBefore != pendingDelayAfter &&
|
||||
pendingDelayScheduleBefore == pendingDelayScheduleAfter
|
||||
) => (
|
||||
increasingDelaySchedule(e, newDelay) == pendingDelayScheduleBefore ||
|
||||
decreasingDelaySchedule(e, newDelay) == pendingDelayScheduleBefore
|
||||
), "pending delay stays the same if a default admin transfer is begun on accepted edge cases";
|
||||
}
|
||||
366
certora/specs/DoubleEndedQueue.spec
Normal file
366
certora/specs/DoubleEndedQueue.spec
Normal file
@ -0,0 +1,366 @@
|
||||
import "helpers/helpers.spec"
|
||||
|
||||
methods {
|
||||
pushFront(bytes32) envfree
|
||||
pushBack(bytes32) envfree
|
||||
popFront() returns (bytes32) envfree
|
||||
popBack() returns (bytes32) envfree
|
||||
clear() envfree
|
||||
|
||||
// exposed for FV
|
||||
begin() returns (int128) envfree
|
||||
end() returns (int128) envfree
|
||||
|
||||
// view
|
||||
length() returns (uint256) envfree
|
||||
empty() returns (bool) envfree
|
||||
front() returns (bytes32) envfree
|
||||
back() returns (bytes32) envfree
|
||||
at_(uint256) returns (bytes32) envfree // at is a reserved word
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
|
||||
function min_int128() returns mathint {
|
||||
return -(1 << 127);
|
||||
}
|
||||
|
||||
function max_int128() returns mathint {
|
||||
return (1 << 127) - 1;
|
||||
}
|
||||
|
||||
// Could be broken in theory, but not in practice
|
||||
function boundedQueue() returns bool {
|
||||
return
|
||||
max_int128() > to_mathint(end()) &&
|
||||
min_int128() < to_mathint(begin());
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: end is larger or equal than begin │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant boundariesConsistency()
|
||||
end() >= begin()
|
||||
filtered { f -> !f.isView }
|
||||
{ preserved { require boundedQueue(); } }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: length is end minus begin │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant lengthConsistency()
|
||||
length() == to_mathint(end()) - to_mathint(begin())
|
||||
filtered { f -> !f.isView }
|
||||
{ preserved { require boundedQueue(); } }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: empty() is length 0 and no element exists │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant emptiness()
|
||||
empty() <=> length() == 0
|
||||
filtered { f -> !f.isView }
|
||||
{ preserved { require boundedQueue(); } }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: front points to the first index and back points to the last one │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant queueEndings()
|
||||
at_(length() - 1) == back() && at_(0) == front()
|
||||
filtered { f -> !f.isView }
|
||||
{
|
||||
preserved {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: pushFront adds an element at the beginning of the queue │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pushFront(bytes32 value) {
|
||||
require boundedQueue();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
|
||||
pushFront@withrevert(value);
|
||||
|
||||
// liveness
|
||||
assert !lastReverted, "never reverts";
|
||||
|
||||
// effect
|
||||
assert front() == value, "front set to value";
|
||||
assert length() == lengthBefore + 1, "queue extended";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: pushFront preserves the previous values in the queue with a +1 offset │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pushFrontConsistency(uint256 index) {
|
||||
require boundedQueue();
|
||||
|
||||
bytes32 beforeAt = at_(index);
|
||||
|
||||
bytes32 value;
|
||||
pushFront(value);
|
||||
|
||||
// try to read value
|
||||
bytes32 afterAt = at_@withrevert(index + 1);
|
||||
|
||||
assert !lastReverted, "value still there";
|
||||
assert afterAt == beforeAt, "data is preserved";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: pushBack adds an element at the end of the queue │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pushBack(bytes32 value) {
|
||||
require boundedQueue();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
|
||||
pushBack@withrevert(value);
|
||||
|
||||
// liveness
|
||||
assert !lastReverted, "never reverts";
|
||||
|
||||
// effect
|
||||
assert back() == value, "back set to value";
|
||||
assert length() == lengthBefore + 1, "queue increased";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: pushBack preserves the previous values in the queue │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pushBackConsistency(uint256 index) {
|
||||
require boundedQueue();
|
||||
|
||||
bytes32 beforeAt = at_(index);
|
||||
|
||||
bytes32 value;
|
||||
pushBack(value);
|
||||
|
||||
// try to read value
|
||||
bytes32 afterAt = at_@withrevert(index);
|
||||
|
||||
assert !lastReverted, "value still there";
|
||||
assert afterAt == beforeAt, "data is preserved";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: popFront removes an element from the beginning of the queue │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule popFront {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bytes32 frontBefore = front@withrevert();
|
||||
|
||||
bytes32 popped = popFront@withrevert();
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> lengthBefore != 0, "never reverts if not previously empty";
|
||||
|
||||
// effect
|
||||
assert success => frontBefore == popped, "previous front is returned";
|
||||
assert success => length() == lengthBefore - 1, "queue decreased";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront |
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule popFrontConsistency(uint256 index) {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
// Read (any) value that is not the front (this asserts the value exists / the queue is long enough)
|
||||
require index > 1;
|
||||
bytes32 before = at_(index);
|
||||
|
||||
popFront();
|
||||
|
||||
// try to read value
|
||||
bytes32 after = at_@withrevert(index - 1);
|
||||
|
||||
assert !lastReverted, "value still exists in the queue";
|
||||
assert before == after, "values are offset and not modified";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: popBack removes an element from the end of the queue │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule popBack {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bytes32 backBefore = back@withrevert();
|
||||
|
||||
bytes32 popped = popBack@withrevert();
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> lengthBefore != 0, "never reverts if not previously empty";
|
||||
|
||||
// effect
|
||||
assert success => backBefore == popped, "previous back is returned";
|
||||
assert success => length() == lengthBefore - 1, "queue decreased";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: at(x) is preserved after calling popBack |
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule popBackConsistency(uint256 index) {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
// Read (any) value that is not the back (this asserts the value exists / the queue is long enough)
|
||||
require index < length() - 1;
|
||||
bytes32 before = at_(index);
|
||||
|
||||
popBack();
|
||||
|
||||
// try to read value
|
||||
bytes32 after = at_@withrevert(index);
|
||||
|
||||
assert !lastReverted, "value still exists in the queue";
|
||||
assert before == after, "values are offset and not modified";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: clear sets length to 0 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule clear {
|
||||
clear@withrevert();
|
||||
|
||||
// liveness
|
||||
assert !lastReverted, "never reverts";
|
||||
|
||||
// effect
|
||||
assert length() == 0, "sets length to 0";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: front/back access reverts only if the queue is empty or querying out of bounds │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyEmptyRevert(env e) {
|
||||
require nonpayable(e);
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
|
||||
bool emptyBefore = empty();
|
||||
|
||||
f@withrevert(e, args);
|
||||
|
||||
assert lastReverted => (
|
||||
(f.selector == front().selector && emptyBefore) ||
|
||||
(f.selector == back().selector && emptyBefore) ||
|
||||
(f.selector == popFront().selector && emptyBefore) ||
|
||||
(f.selector == popBack().selector && emptyBefore) ||
|
||||
f.selector == at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert
|
||||
), "only revert if empty or out of bounds";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: at(index) only reverts if index is out of bounds |
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyOutOfBoundsRevert(uint256 index) {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
at_@withrevert(index);
|
||||
|
||||
assert lastReverted <=> index >= length(), "only reverts if index is out of bounds";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: only clear/push/pop operations can change the length of the queue │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noLengthChange(env e) {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
f(e, args);
|
||||
uint256 lengthAfter = length();
|
||||
|
||||
assert lengthAfter != lengthBefore => (
|
||||
(f.selector == pushFront(bytes32).selector && lengthAfter == lengthBefore + 1) ||
|
||||
(f.selector == pushBack(bytes32).selector && lengthAfter == lengthBefore + 1) ||
|
||||
(f.selector == popBack().selector && lengthAfter == lengthBefore - 1) ||
|
||||
(f.selector == popFront().selector && lengthAfter == lengthBefore - 1) ||
|
||||
(f.selector == clear().selector && lengthAfter == 0)
|
||||
), "length is only affected by clear/pop/push operations";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: only push/pop can change values bounded in the queue (outside values aren't cleared) │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noDataChange(env e) {
|
||||
requireInvariant boundariesConsistency();
|
||||
require boundedQueue();
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
|
||||
uint256 index;
|
||||
bytes32 atBefore = at_(index);
|
||||
f(e, args);
|
||||
bytes32 atAfter = at_@withrevert(index);
|
||||
bool atAfterSuccess = !lastReverted;
|
||||
|
||||
assert !atAfterSuccess <=> (
|
||||
f.selector == clear().selector ||
|
||||
(f.selector == popBack().selector && index == length()) ||
|
||||
(f.selector == popFront().selector && index == length())
|
||||
), "indexes of the queue are only removed by clear or pop";
|
||||
|
||||
assert atAfterSuccess && atAfter != atBefore => (
|
||||
f.selector == popFront().selector ||
|
||||
f.selector == pushFront(bytes32).selector
|
||||
), "values of the queue are only changed by popFront or pushFront";
|
||||
}
|
||||
414
certora/specs/ERC20.spec
Normal file
414
certora/specs/ERC20.spec
Normal file
@ -0,0 +1,414 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IERC20.spec"
|
||||
import "methods/IERC2612.spec"
|
||||
|
||||
methods {
|
||||
// non standard ERC20 functions
|
||||
increaseAllowance(address,uint256) returns (bool)
|
||||
decreaseAllowance(address,uint256) returns (bool)
|
||||
|
||||
// exposed for FV
|
||||
mint(address,uint256)
|
||||
burn(address,uint256)
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ghost & hooks: sum of all balances │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
ghost sumOfBalances() returns uint256 {
|
||||
init_state axiom sumOfBalances() == 0;
|
||||
}
|
||||
|
||||
hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
|
||||
havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newValue - oldValue;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: totalSupply is the sum of all balances │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant totalSupplyIsSumOfBalances()
|
||||
totalSupply() == sumOfBalances()
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: balance of address(0) is 0 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant zeroAddressNoBalance()
|
||||
balanceOf(0) == 0
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: only mint and burn can change total supply │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noChangeTotalSupply(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
|
||||
uint256 totalSupplyBefore = totalSupply();
|
||||
f(e, args);
|
||||
uint256 totalSupplyAfter = totalSupply();
|
||||
|
||||
assert totalSupplyAfter > totalSupplyBefore => f.selector == mint(address,uint256).selector;
|
||||
assert totalSupplyAfter < totalSupplyBefore => f.selector == burn(address,uint256).selector;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: only the token holder or an approved third party can reduce an account's balance │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyAuthorizedCanTransfer(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
address account;
|
||||
|
||||
uint256 allowanceBefore = allowance(account, e.msg.sender);
|
||||
uint256 balanceBefore = balanceOf(account);
|
||||
f(e, args);
|
||||
uint256 balanceAfter = balanceOf(account);
|
||||
|
||||
assert (
|
||||
balanceAfter < balanceBefore
|
||||
) => (
|
||||
f.selector == burn(address,uint256).selector ||
|
||||
e.msg.sender == account ||
|
||||
balanceBefore - balanceAfter <= allowanceBefore
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: only the token holder (or a permit) can increase allowance. The spender can decrease it by using it │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyHolderOfSpenderCanChangeAllowance(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
address holder;
|
||||
address spender;
|
||||
|
||||
uint256 allowanceBefore = allowance(holder, spender);
|
||||
f(e, args);
|
||||
uint256 allowanceAfter = allowance(holder, spender);
|
||||
|
||||
assert (
|
||||
allowanceAfter > allowanceBefore
|
||||
) => (
|
||||
(f.selector == approve(address,uint256).selector && e.msg.sender == holder) ||
|
||||
(f.selector == increaseAllowance(address,uint256).selector && e.msg.sender == holder) ||
|
||||
(f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
|
||||
);
|
||||
|
||||
assert (
|
||||
allowanceAfter < allowanceBefore
|
||||
) => (
|
||||
(f.selector == transferFrom(address,address,uint256).selector && e.msg.sender == spender) ||
|
||||
(f.selector == approve(address,uint256).selector && e.msg.sender == holder ) ||
|
||||
(f.selector == decreaseAllowance(address,uint256).selector && e.msg.sender == holder ) ||
|
||||
(f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: mint behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule mint(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
require nonpayable(e);
|
||||
|
||||
address to;
|
||||
address other;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 toBalanceBefore = balanceOf(to);
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
uint256 totalSupplyBefore = totalSupply();
|
||||
|
||||
// run transaction
|
||||
mint@withrevert(e, to, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert to == 0 || totalSupplyBefore + amount > max_uint256;
|
||||
} else {
|
||||
// updates balance and totalSupply
|
||||
assert balanceOf(to) == toBalanceBefore + amount;
|
||||
assert totalSupply() == totalSupplyBefore + amount;
|
||||
|
||||
// no other balance is modified
|
||||
assert balanceOf(other) != otherBalanceBefore => other == to;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: burn behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule burn(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
require nonpayable(e);
|
||||
|
||||
address from;
|
||||
address other;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 fromBalanceBefore = balanceOf(from);
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
uint256 totalSupplyBefore = totalSupply();
|
||||
|
||||
// run transaction
|
||||
burn@withrevert(e, from, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert from == 0 || fromBalanceBefore < amount;
|
||||
} else {
|
||||
// updates balance and totalSupply
|
||||
assert balanceOf(from) == fromBalanceBefore - amount;
|
||||
assert totalSupply() == totalSupplyBefore - amount;
|
||||
|
||||
// no other balance is modified
|
||||
assert balanceOf(other) != otherBalanceBefore => other == from;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: transfer behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule transfer(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
require nonpayable(e);
|
||||
|
||||
address holder = e.msg.sender;
|
||||
address recipient;
|
||||
address other;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 holderBalanceBefore = balanceOf(holder);
|
||||
uint256 recipientBalanceBefore = balanceOf(recipient);
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
|
||||
// run transaction
|
||||
transfer@withrevert(e, recipient, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert holder == 0 || recipient == 0 || amount > holderBalanceBefore;
|
||||
} else {
|
||||
// balances of holder and recipient are updated
|
||||
assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
|
||||
assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
|
||||
|
||||
// no other balance is modified
|
||||
assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: transferFrom behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule transferFrom(env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
require nonpayable(e);
|
||||
|
||||
address spender = e.msg.sender;
|
||||
address holder;
|
||||
address recipient;
|
||||
address other;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 allowanceBefore = allowance(holder, spender);
|
||||
uint256 holderBalanceBefore = balanceOf(holder);
|
||||
uint256 recipientBalanceBefore = balanceOf(recipient);
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
|
||||
// run transaction
|
||||
transferFrom@withrevert(e, holder, recipient, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert holder == 0 || recipient == 0 || spender == 0 || amount > holderBalanceBefore || amount > allowanceBefore;
|
||||
} else {
|
||||
// allowance is valid & updated
|
||||
assert allowanceBefore >= amount;
|
||||
assert allowance(holder, spender) == (allowanceBefore == max_uint256 ? to_uint256(max_uint256) : allowanceBefore - amount);
|
||||
|
||||
// balances of holder and recipient are updated
|
||||
assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
|
||||
assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
|
||||
|
||||
// no other balance is modified
|
||||
assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: approve behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approve(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address holder = e.msg.sender;
|
||||
address spender;
|
||||
address otherHolder;
|
||||
address otherSpender;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
|
||||
|
||||
// run transaction
|
||||
approve@withrevert(e, spender, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert holder == 0 || spender == 0;
|
||||
} else {
|
||||
// allowance is updated
|
||||
assert allowance(holder, spender) == amount;
|
||||
|
||||
// other allowances are untouched
|
||||
assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: increaseAllowance behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule increaseAllowance(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address holder = e.msg.sender;
|
||||
address spender;
|
||||
address otherHolder;
|
||||
address otherSpender;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 allowanceBefore = allowance(holder, spender);
|
||||
uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
|
||||
|
||||
// run transaction
|
||||
increaseAllowance@withrevert(e, spender, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert holder == 0 || spender == 0 || allowanceBefore + amount > max_uint256;
|
||||
} else {
|
||||
// allowance is updated
|
||||
assert allowance(holder, spender) == allowanceBefore + amount;
|
||||
|
||||
// other allowances are untouched
|
||||
assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: decreaseAllowance behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule decreaseAllowance(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address holder = e.msg.sender;
|
||||
address spender;
|
||||
address otherHolder;
|
||||
address otherSpender;
|
||||
uint256 amount;
|
||||
|
||||
// cache state
|
||||
uint256 allowanceBefore = allowance(holder, spender);
|
||||
uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
|
||||
|
||||
// run transaction
|
||||
decreaseAllowance@withrevert(e, spender, amount);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
assert holder == 0 || spender == 0 || allowanceBefore < amount;
|
||||
} else {
|
||||
// allowance is updated
|
||||
assert allowance(holder, spender) == allowanceBefore - amount;
|
||||
|
||||
// other allowances are untouched
|
||||
assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: permit behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule permit(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address holder;
|
||||
address spender;
|
||||
uint256 amount;
|
||||
uint256 deadline;
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
|
||||
address account1;
|
||||
address account2;
|
||||
address account3;
|
||||
|
||||
// cache state
|
||||
uint256 nonceBefore = nonces(holder);
|
||||
uint256 otherNonceBefore = nonces(account1);
|
||||
uint256 otherAllowanceBefore = allowance(account2, account3);
|
||||
|
||||
// sanity: nonce overflow, which possible in theory, is assumed to be impossible in practice
|
||||
require nonceBefore < max_uint256;
|
||||
require otherNonceBefore < max_uint256;
|
||||
|
||||
// run transaction
|
||||
permit@withrevert(e, holder, spender, amount, deadline, v, r, s);
|
||||
|
||||
// check outcome
|
||||
if (lastReverted) {
|
||||
// Without formally checking the signature, we can't verify exactly the revert causes
|
||||
assert true;
|
||||
} else {
|
||||
// allowance and nonce are updated
|
||||
assert allowance(holder, spender) == amount;
|
||||
assert nonces(holder) == nonceBefore + 1;
|
||||
|
||||
// deadline was respected
|
||||
assert deadline >= e.block.timestamp;
|
||||
|
||||
// no other allowance or nonce is modified
|
||||
assert nonces(account1) != otherNonceBefore => account1 == holder;
|
||||
assert allowance(account2, account3) != otherAllowanceBefore => (account2 == holder && account3 == spender);
|
||||
}
|
||||
}
|
||||
48
certora/specs/ERC20FlashMint.spec
Normal file
48
certora/specs/ERC20FlashMint.spec
Normal file
@ -0,0 +1,48 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IERC20.spec"
|
||||
import "methods/IERC3156.spec"
|
||||
|
||||
methods {
|
||||
// non standard ERC3156 functions
|
||||
flashFeeReceiver() returns (address) envfree
|
||||
|
||||
// function summaries below
|
||||
_mint(address account, uint256 amount) => specMint(account, amount)
|
||||
_burn(address account, uint256 amount) => specBurn(account, amount)
|
||||
_transfer(address from, address to, uint256 amount) => specTransfer(from, to, amount)
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ghost: track mint and burns in the CVL │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
ghost mapping(address => uint256) trackedMintAmount;
|
||||
ghost mapping(address => uint256) trackedBurnAmount;
|
||||
ghost mapping(address => mapping(address => uint256)) trackedTransferedAmount;
|
||||
|
||||
function specMint(address account, uint256 amount) returns bool { trackedMintAmount[account] = amount; return true; }
|
||||
function specBurn(address account, uint256 amount) returns bool { trackedBurnAmount[account] = amount; return true; }
|
||||
function specTransfer(address from, address to, uint256 amount) returns bool { trackedTransferedAmount[from][to] = amount; return true; }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: When doing a flashLoan, "amount" is minted and burnt, additionally, the fee is either burnt │
|
||||
│ (if the fee recipient is 0) or transferred (if the fee recipient is not 0) │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule checkMintAndBurn(env e) {
|
||||
address receiver;
|
||||
address token;
|
||||
uint256 amount;
|
||||
bytes data;
|
||||
|
||||
uint256 fees = flashFee(token, amount);
|
||||
address recipient = flashFeeReceiver();
|
||||
|
||||
flashLoan(e, receiver, token, amount, data);
|
||||
|
||||
assert trackedMintAmount[receiver] == amount;
|
||||
assert trackedBurnAmount[receiver] == amount + (recipient == 0 ? fees : 0);
|
||||
assert (fees > 0 && recipient != 0) => trackedTransferedAmount[receiver][recipient] == fees;
|
||||
}
|
||||
198
certora/specs/ERC20Wrapper.spec
Normal file
198
certora/specs/ERC20Wrapper.spec
Normal file
@ -0,0 +1,198 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "ERC20.spec"
|
||||
|
||||
methods {
|
||||
underlying() returns(address) envfree
|
||||
underlyingTotalSupply() returns(uint256) envfree
|
||||
underlyingBalanceOf(address) returns(uint256) envfree
|
||||
underlyingAllowanceToThis(address) returns(uint256) envfree
|
||||
|
||||
depositFor(address, uint256) returns(bool)
|
||||
withdrawTo(address, uint256) returns(bool)
|
||||
recover(address) returns(uint256)
|
||||
}
|
||||
|
||||
use invariant totalSupplyIsSumOfBalances
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
function underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool {
|
||||
return underlyingBalanceOf(a) <= underlyingTotalSupply();
|
||||
}
|
||||
|
||||
function sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool {
|
||||
return a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= underlyingTotalSupply();
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant totalSupplyIsSmallerThanUnderlyingBalance()
|
||||
totalSupply() <= underlyingBalanceOf(currentContract) &&
|
||||
underlyingBalanceOf(currentContract) <= underlyingTotalSupply() &&
|
||||
underlyingTotalSupply() <= max_uint256
|
||||
{
|
||||
preserved {
|
||||
requireInvariant totalSupplyIsSumOfBalances;
|
||||
require underlyingBalancesLowerThanUnderlyingSupply(currentContract);
|
||||
}
|
||||
preserved depositFor(address account, uint256 amount) with (env e) {
|
||||
require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract);
|
||||
}
|
||||
}
|
||||
|
||||
invariant noSelfWrap()
|
||||
currentContract != underlying()
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: depositFor liveness and effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule depositFor(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address sender = e.msg.sender;
|
||||
address receiver;
|
||||
address other;
|
||||
uint256 amount;
|
||||
|
||||
// sanity
|
||||
requireInvariant noSelfWrap;
|
||||
requireInvariant totalSupplyIsSumOfBalances;
|
||||
requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
|
||||
require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender);
|
||||
|
||||
uint256 balanceBefore = balanceOf(receiver);
|
||||
uint256 supplyBefore = totalSupply();
|
||||
uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender);
|
||||
uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender);
|
||||
uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
|
||||
uint256 underlyingSupplyBefore = underlyingTotalSupply();
|
||||
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
|
||||
|
||||
depositFor@withrevert(e, receiver, amount);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
sender != currentContract && // invalid sender
|
||||
sender != 0 && // invalid sender
|
||||
receiver != 0 && // invalid receiver
|
||||
amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
|
||||
amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
|
||||
);
|
||||
|
||||
// effects
|
||||
assert success => (
|
||||
balanceOf(receiver) == balanceBefore + amount &&
|
||||
totalSupply() == supplyBefore + amount &&
|
||||
underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore + amount &&
|
||||
underlyingBalanceOf(sender) == senderUnderlyingBalanceBefore - amount
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert underlyingTotalSupply() == underlyingSupplyBefore;
|
||||
assert balanceOf(other) != otherBalanceBefore => other == receiver;
|
||||
assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: withdrawTo liveness and effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule withdrawTo(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address sender = e.msg.sender;
|
||||
address receiver;
|
||||
address other;
|
||||
uint256 amount;
|
||||
|
||||
// sanity
|
||||
requireInvariant noSelfWrap;
|
||||
requireInvariant totalSupplyIsSumOfBalances;
|
||||
requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
|
||||
require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver);
|
||||
|
||||
uint256 balanceBefore = balanceOf(sender);
|
||||
uint256 supplyBefore = totalSupply();
|
||||
uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver);
|
||||
uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
|
||||
uint256 underlyingSupplyBefore = underlyingTotalSupply();
|
||||
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
|
||||
|
||||
withdrawTo@withrevert(e, receiver, amount);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
sender != 0 && // invalid sender
|
||||
receiver != 0 && // invalid receiver
|
||||
amount <= balanceBefore // withdraw doesn't exceed balance
|
||||
);
|
||||
|
||||
// effects
|
||||
assert success => (
|
||||
balanceOf(sender) == balanceBefore - amount &&
|
||||
totalSupply() == supplyBefore - amount &&
|
||||
underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
|
||||
underlyingBalanceOf(receiver) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0)
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert underlyingTotalSupply() == underlyingSupplyBefore;
|
||||
assert balanceOf(other) != otherBalanceBefore => other == sender;
|
||||
assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: recover liveness and effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule recover(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address receiver;
|
||||
address other;
|
||||
|
||||
// sanity
|
||||
requireInvariant noSelfWrap;
|
||||
requireInvariant totalSupplyIsSumOfBalances;
|
||||
requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
|
||||
|
||||
uint256 value = underlyingBalanceOf(currentContract) - totalSupply();
|
||||
uint256 supplyBefore = totalSupply();
|
||||
uint256 balanceBefore = balanceOf(receiver);
|
||||
|
||||
uint256 otherBalanceBefore = balanceOf(other);
|
||||
uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
|
||||
|
||||
recover@withrevert(e, receiver);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> receiver != 0;
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
balanceOf(receiver) == balanceBefore + value &&
|
||||
totalSupply() == supplyBefore + value &&
|
||||
totalSupply() == underlyingBalanceOf(currentContract)
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore;
|
||||
assert balanceOf(other) != otherBalanceBefore => other == receiver;
|
||||
}
|
||||
589
certora/specs/ERC721.spec
Normal file
589
certora/specs/ERC721.spec
Normal file
@ -0,0 +1,589 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IERC721.spec"
|
||||
|
||||
methods {
|
||||
// exposed for FV
|
||||
mint(address,uint256)
|
||||
safeMint(address,uint256)
|
||||
safeMint(address,uint256,bytes)
|
||||
burn(uint256)
|
||||
|
||||
tokenExists(uint256) returns (bool) envfree
|
||||
unsafeOwnerOf(uint256) returns (address) envfree
|
||||
unsafeGetApproved(uint256) returns (address) envfree
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
|
||||
// Could be broken in theory, but not in practice
|
||||
function balanceLimited(address account) returns bool {
|
||||
return balanceOf(account) < max_uint256;
|
||||
}
|
||||
|
||||
function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) {
|
||||
if (f.selector == transferFrom(address,address,uint256).selector) {
|
||||
transferFrom@withrevert(e, from, to, tokenId);
|
||||
} else if (f.selector == safeTransferFrom(address,address,uint256).selector) {
|
||||
safeTransferFrom@withrevert(e, from, to, tokenId);
|
||||
} else if (f.selector == safeTransferFrom(address,address,uint256,bytes).selector) {
|
||||
bytes params;
|
||||
require params.length < 0xffff;
|
||||
safeTransferFrom@withrevert(e, from, to, tokenId, params);
|
||||
} else {
|
||||
calldataarg args;
|
||||
f@withrevert(e, args);
|
||||
}
|
||||
}
|
||||
|
||||
function helperMintWithRevert(env e, method f, address to, uint256 tokenId) {
|
||||
if (f.selector == mint(address,uint256).selector) {
|
||||
mint@withrevert(e, to, tokenId);
|
||||
} else if (f.selector == safeMint(address,uint256).selector) {
|
||||
safeMint@withrevert(e, to, tokenId);
|
||||
} else if (f.selector == safeMint(address,uint256,bytes).selector) {
|
||||
bytes params;
|
||||
require params.length < 0xffff;
|
||||
safeMint@withrevert(e, to, tokenId, params);
|
||||
} else {
|
||||
require false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ghost & hooks: ownership count │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
ghost ownedTotal() returns uint256 {
|
||||
init_state axiom ownedTotal() == 0;
|
||||
}
|
||||
|
||||
ghost mapping(address => uint256) ownedByUser {
|
||||
init_state axiom forall address a. ownedByUser[a] == 0;
|
||||
}
|
||||
|
||||
hook Sstore _owners[KEY uint256 tokenId] address newOwner (address oldOwner) STORAGE {
|
||||
ownedByUser[newOwner] = ownedByUser[newOwner] + to_uint256(newOwner != 0 ? 1 : 0);
|
||||
ownedByUser[oldOwner] = ownedByUser[oldOwner] - to_uint256(oldOwner != 0 ? 1 : 0);
|
||||
|
||||
havoc ownedTotal assuming ownedTotal@new() == ownedTotal@old()
|
||||
+ to_uint256(newOwner != 0 ? 1 : 0)
|
||||
- to_uint256(oldOwner != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ghost & hooks: sum of all balances │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
ghost sumOfBalances() returns uint256 {
|
||||
init_state axiom sumOfBalances() == 0;
|
||||
}
|
||||
|
||||
hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
|
||||
havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newValue - oldValue;
|
||||
}
|
||||
|
||||
ghost mapping(address => uint256) ghostBalanceOf {
|
||||
init_state axiom forall address a. ghostBalanceOf[a] == 0;
|
||||
}
|
||||
|
||||
hook Sload uint256 value _balances[KEY address user] STORAGE {
|
||||
require ghostBalanceOf[user] == value;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: ownedTotal is the sum of all balances │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant ownedTotalIsSumOfBalances()
|
||||
ownedTotal() == sumOfBalances()
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: balanceOf is the number of tokens owned │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant balanceOfConsistency(address user)
|
||||
balanceOf(user) == ownedByUser[user] &&
|
||||
balanceOf(user) == ghostBalanceOf[user]
|
||||
{
|
||||
preserved {
|
||||
require balanceLimited(user);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: owner of a token must have some balance │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant ownerHasBalance(uint256 tokenId)
|
||||
balanceOf(ownerOf(tokenId)) > 0
|
||||
{
|
||||
preserved {
|
||||
requireInvariant balanceOfConsistency(ownerOf(tokenId));
|
||||
require balanceLimited(ownerOf(tokenId));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: tokens that do not exist are not owned and not approved │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant notMintedUnset(uint256 tokenId)
|
||||
(!tokenExists(tokenId) <=> unsafeOwnerOf(tokenId) == 0) &&
|
||||
(!tokenExists(tokenId) => unsafeGetApproved(tokenId) == 0)
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: ownerOf and getApproved revert if token does not exist │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule notMintedRevert(uint256 tokenId) {
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
|
||||
bool e = tokenExists(tokenId);
|
||||
|
||||
address owner = ownerOf@withrevert(tokenId);
|
||||
assert e <=> !lastReverted;
|
||||
assert e => owner == unsafeOwnerOf(tokenId); // notMintedUnset tells us this is non-zero
|
||||
|
||||
address approved = getApproved@withrevert(tokenId);
|
||||
assert e <=> !lastReverted;
|
||||
assert e => approved == unsafeGetApproved(tokenId);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: unsafeOwnerOf and unsafeGetApproved don't revert │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule unsafeDontRevert(uint256 tokenId) {
|
||||
unsafeOwnerOf@withrevert(tokenId);
|
||||
assert !lastReverted;
|
||||
|
||||
unsafeGetApproved@withrevert(tokenId);
|
||||
assert !lastReverted;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: balance of address(0) is 0 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule zeroAddressBalanceRevert() {
|
||||
balanceOf@withrevert(0);
|
||||
assert lastReverted;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: total supply can only change through mint and burn │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule supplyChange(env e) {
|
||||
uint256 supplyBefore = ownedTotal();
|
||||
method f; calldataarg args; f(e, args);
|
||||
uint256 supplyAfter = ownedTotal();
|
||||
|
||||
assert supplyAfter > supplyBefore => (
|
||||
supplyAfter == supplyBefore + 1 &&
|
||||
(
|
||||
f.selector == mint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256,bytes).selector
|
||||
)
|
||||
);
|
||||
assert supplyAfter < supplyBefore => (
|
||||
supplyAfter == supplyBefore - 1 &&
|
||||
f.selector == burn(uint256).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: balanceOf can only change through mint, burn or transfers. balanceOf cannot change by more than 1. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule balanceChange(env e, address account) {
|
||||
requireInvariant balanceOfConsistency(account);
|
||||
require balanceLimited(account);
|
||||
|
||||
uint256 balanceBefore = balanceOf(account);
|
||||
method f; calldataarg args; f(e, args);
|
||||
uint256 balanceAfter = balanceOf(account);
|
||||
|
||||
// balance can change by at most 1
|
||||
assert balanceBefore != balanceAfter => (
|
||||
balanceAfter == balanceBefore - 1 ||
|
||||
balanceAfter == balanceBefore + 1
|
||||
);
|
||||
|
||||
// only selected function can change balances
|
||||
assert balanceBefore != balanceAfter => (
|
||||
f.selector == transferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256,bytes).selector ||
|
||||
f.selector == mint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256,bytes).selector ||
|
||||
f.selector == burn(uint256).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: ownership can only change through mint, burn or transfers. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule ownershipChange(env e, uint256 tokenId) {
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
method f; calldataarg args; f(e, args);
|
||||
address ownerAfter = unsafeOwnerOf(tokenId);
|
||||
|
||||
assert ownerBefore == 0 && ownerAfter != 0 => (
|
||||
f.selector == mint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256,bytes).selector
|
||||
);
|
||||
|
||||
assert ownerBefore != 0 && ownerAfter == 0 => (
|
||||
f.selector == burn(uint256).selector
|
||||
);
|
||||
|
||||
assert (ownerBefore != ownerAfter && ownerBefore != 0 && ownerAfter != 0) => (
|
||||
f.selector == transferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256,bytes).selector
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: token approval can only change through approve or transfers (implicitly). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approvalChange(env e, uint256 tokenId) {
|
||||
address approvalBefore = unsafeGetApproved(tokenId);
|
||||
method f; calldataarg args; f(e, args);
|
||||
address approvalAfter = unsafeGetApproved(tokenId);
|
||||
|
||||
// approve can set any value, other functions reset
|
||||
assert approvalBefore != approvalAfter => (
|
||||
f.selector == approve(address,uint256).selector ||
|
||||
(
|
||||
(
|
||||
f.selector == transferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256,bytes).selector ||
|
||||
f.selector == burn(uint256).selector
|
||||
) && approvalAfter == 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: approval for all tokens can only change through isApprovedForAll. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approvedForAllChange(env e, address owner, address spender) {
|
||||
bool approvedForAllBefore = isApprovedForAll(owner, spender);
|
||||
method f; calldataarg args; f(e, args);
|
||||
bool approvedForAllAfter = isApprovedForAll(owner, spender);
|
||||
|
||||
assert approvedForAllBefore != approvedForAllAfter => f.selector == setApprovalForAll(address,bool).selector;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: transferFrom behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule transferFrom(env e, address from, address to, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
|
||||
address operator = e.msg.sender;
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
require balanceLimited(to);
|
||||
|
||||
uint256 balanceOfFromBefore = balanceOf(from);
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
address approvalBefore = unsafeGetApproved(tokenId);
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
transferFrom@withrevert(e, from, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
from == ownerBefore &&
|
||||
from != 0 &&
|
||||
to != 0 &&
|
||||
(operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator))
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
balanceOf(from) == balanceOfFromBefore - to_uint256(from != to ? 1 : 0) &&
|
||||
balanceOf(to) == balanceOfToBefore + to_uint256(from != to ? 1 : 0) &&
|
||||
unsafeOwnerOf(tokenId) == to &&
|
||||
unsafeGetApproved(tokenId) == 0
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to);
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: safeTransferFrom behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule safeTransferFrom(env e, method f, address from, address to, uint256 tokenId) filtered { f ->
|
||||
f.selector == safeTransferFrom(address,address,uint256).selector ||
|
||||
f.selector == safeTransferFrom(address,address,uint256,bytes).selector
|
||||
} {
|
||||
require nonpayable(e);
|
||||
|
||||
address operator = e.msg.sender;
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
require balanceLimited(to);
|
||||
|
||||
uint256 balanceOfFromBefore = balanceOf(from);
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
address approvalBefore = unsafeGetApproved(tokenId);
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
helperTransferWithRevert(e, f, from, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> (
|
||||
from == ownerBefore &&
|
||||
from != 0 &&
|
||||
to != 0 &&
|
||||
(operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator))
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
balanceOf(from) == balanceOfFromBefore - to_uint256(from != to ? 1: 0) &&
|
||||
balanceOf(to) == balanceOfToBefore + to_uint256(from != to ? 1: 0) &&
|
||||
unsafeOwnerOf(tokenId) == to &&
|
||||
unsafeGetApproved(tokenId) == 0
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to);
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: mint behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule mint(env e, address to, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
require balanceLimited(to);
|
||||
|
||||
uint256 supplyBefore = ownedTotal();
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
|
||||
mint@withrevert(e, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
ownerBefore == 0 &&
|
||||
to != 0
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
ownedTotal() == supplyBefore + 1 &&
|
||||
balanceOf(to) == balanceOfToBefore + 1 &&
|
||||
unsafeOwnerOf(tokenId) == to
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: safeMint behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f ->
|
||||
f.selector == safeMint(address,uint256).selector ||
|
||||
f.selector == safeMint(address,uint256,bytes).selector
|
||||
} {
|
||||
require nonpayable(e);
|
||||
requireInvariant notMintedUnset(tokenId);
|
||||
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
require balanceLimited(to);
|
||||
|
||||
uint256 supplyBefore = ownedTotal();
|
||||
uint256 balanceOfToBefore = balanceOf(to);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
|
||||
helperMintWithRevert(e, f, to, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> (
|
||||
ownerBefore == 0 &&
|
||||
to != 0
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
ownedTotal() == supplyBefore + 1 &&
|
||||
balanceOf(to) == balanceOfToBefore + 1 &&
|
||||
unsafeOwnerOf(tokenId) == to
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to;
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: burn behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule burn(env e, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
|
||||
address from = unsafeOwnerOf(tokenId);
|
||||
uint256 otherTokenId;
|
||||
address otherAccount;
|
||||
|
||||
requireInvariant ownerHasBalance(tokenId);
|
||||
|
||||
uint256 supplyBefore = ownedTotal();
|
||||
uint256 balanceOfFromBefore = balanceOf(from);
|
||||
uint256 balanceOfOtherBefore = balanceOf(otherAccount);
|
||||
address ownerBefore = unsafeOwnerOf(tokenId);
|
||||
address otherOwnerBefore = unsafeOwnerOf(otherTokenId);
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
burn@withrevert(e, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
ownerBefore != 0
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => (
|
||||
ownedTotal() == supplyBefore - 1 &&
|
||||
balanceOf(from) == balanceOfFromBefore - 1 &&
|
||||
unsafeOwnerOf(tokenId) == 0 &&
|
||||
unsafeGetApproved(tokenId) == 0
|
||||
);
|
||||
|
||||
// no side effect
|
||||
assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == from;
|
||||
assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId;
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: approve behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule approve(env e, address spender, uint256 tokenId) {
|
||||
require nonpayable(e);
|
||||
|
||||
address caller = e.msg.sender;
|
||||
address owner = unsafeOwnerOf(tokenId);
|
||||
uint256 otherTokenId;
|
||||
|
||||
address otherApprovalBefore = unsafeGetApproved(otherTokenId);
|
||||
|
||||
approve@withrevert(e, spender, tokenId);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
owner != 0 &&
|
||||
owner != spender &&
|
||||
(owner == caller || isApprovedForAll(owner, caller))
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => unsafeGetApproved(tokenId) == spender;
|
||||
|
||||
// no side effect
|
||||
assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: setApprovalForAll behavior and side effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule setApprovalForAll(env e, address operator, bool approved) {
|
||||
require nonpayable(e);
|
||||
|
||||
address owner = e.msg.sender;
|
||||
address otherOwner;
|
||||
address otherOperator;
|
||||
|
||||
bool otherIsApprovedForAllBefore = isApprovedForAll(otherOwner, otherOperator);
|
||||
|
||||
setApprovalForAll@withrevert(e, operator, approved);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> owner != operator;
|
||||
|
||||
// effect
|
||||
assert success => isApprovedForAll(owner, operator) == approved;
|
||||
|
||||
// no side effect
|
||||
assert isApprovedForAll(otherOwner, otherOperator) != otherIsApprovedForAllBefore => (
|
||||
otherOwner == owner &&
|
||||
otherOperator == operator
|
||||
);
|
||||
}
|
||||
334
certora/specs/EnumerableMap.spec
Normal file
334
certora/specs/EnumerableMap.spec
Normal file
@ -0,0 +1,334 @@
|
||||
import "helpers/helpers.spec"
|
||||
|
||||
methods {
|
||||
// library
|
||||
set(bytes32,bytes32) returns (bool) envfree
|
||||
remove(bytes32) returns (bool) envfree
|
||||
contains(bytes32) returns (bool) envfree
|
||||
length() returns (uint256) envfree
|
||||
key_at(uint256) returns (bytes32) envfree
|
||||
value_at(uint256) returns (bytes32) envfree
|
||||
tryGet_contains(bytes32) returns (bool) envfree
|
||||
tryGet_value(bytes32) returns (bytes32) envfree
|
||||
get(bytes32) returns (bytes32) envfree
|
||||
|
||||
// FV
|
||||
_indexOf(bytes32) returns (uint256) envfree
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
function sanity() returns bool {
|
||||
return length() < max_uint256;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: the value mapping is empty for keys that are not in the EnumerableMap. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant noValueIfNotContained(bytes32 key)
|
||||
!contains(key) => tryGet_value(key) == 0
|
||||
{
|
||||
preserved set(bytes32 otherKey, bytes32 someValue) {
|
||||
require sanity();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: All indexed keys are contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant indexedContained(uint256 index)
|
||||
index < length() => contains(key_at(index))
|
||||
{
|
||||
preserved {
|
||||
requireInvariant consistencyIndex(index);
|
||||
requireInvariant consistencyIndex(to_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: A value can only be stored at a single location │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant atUniqueness(uint256 index1, uint256 index2)
|
||||
index1 == index2 <=> key_at(index1) == key_at(index2)
|
||||
{
|
||||
preserved remove(bytes32 key) {
|
||||
requireInvariant atUniqueness(index1, to_uint256(length() - 1));
|
||||
requireInvariant atUniqueness(index2, to_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: index <> value relationship is consistent │
|
||||
│ │
|
||||
│ Note that the two consistencyXxx invariants, put together, prove that at_ and _indexOf are inverse of one another. │
|
||||
│ This proves that we have a bijection between indices (the enumerability part) and keys (the entries that are set │
|
||||
│ and removed from the EnumerableMap). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant consistencyIndex(uint256 index)
|
||||
index < length() => _indexOf(key_at(index)) == index + 1
|
||||
{
|
||||
preserved remove(bytes32 key) {
|
||||
requireInvariant consistencyIndex(to_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
invariant consistencyKey(bytes32 key)
|
||||
contains(key) => (
|
||||
_indexOf(key) > 0 &&
|
||||
_indexOf(key) <= length() &&
|
||||
key_at(to_uint256(_indexOf(key) - 1)) == key
|
||||
)
|
||||
{
|
||||
preserved remove(bytes32 otherKey) {
|
||||
requireInvariant consistencyKey(otherKey);
|
||||
requireInvariant atUniqueness(
|
||||
to_uint256(_indexOf(key) - 1),
|
||||
to_uint256(_indexOf(otherKey) - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: state only changes by setting or removing elements │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule stateChange(env e, bytes32 key) {
|
||||
require sanity();
|
||||
requireInvariant consistencyKey(key);
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bytes32 valueBefore = tryGet_value(key);
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
uint256 lengthAfter = length();
|
||||
bool containsAfter = contains(key);
|
||||
bytes32 valueAfter = tryGet_value(key);
|
||||
|
||||
assert lengthBefore != lengthAfter => (
|
||||
(f.selector == set(bytes32,bytes32).selector && lengthAfter == lengthBefore + 1) ||
|
||||
(f.selector == remove(bytes32).selector && lengthAfter == lengthBefore - 1)
|
||||
);
|
||||
|
||||
assert containsBefore != containsAfter => (
|
||||
(f.selector == set(bytes32,bytes32).selector && containsAfter) ||
|
||||
(f.selector == remove(bytes32).selector && !containsAfter)
|
||||
);
|
||||
|
||||
assert valueBefore != valueAfter => (
|
||||
(f.selector == set(bytes32,bytes32).selector && containsAfter) ||
|
||||
(f.selector == remove(bytes32).selector && !containsAfter && valueAfter == 0)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: check liveness of view functions. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule liveness_1(bytes32 key) {
|
||||
requireInvariant consistencyKey(key);
|
||||
|
||||
// contains never revert
|
||||
bool contains = contains@withrevert(key);
|
||||
assert !lastReverted;
|
||||
|
||||
// tryGet never reverts (key)
|
||||
tryGet_contains@withrevert(key);
|
||||
assert !lastReverted;
|
||||
|
||||
// tryGet never reverts (value)
|
||||
tryGet_value@withrevert(key);
|
||||
assert !lastReverted;
|
||||
|
||||
// get reverts iff the key is not in the map
|
||||
get@withrevert(key);
|
||||
assert !lastReverted <=> contains;
|
||||
}
|
||||
|
||||
rule liveness_2(uint256 index) {
|
||||
requireInvariant consistencyIndex(index);
|
||||
|
||||
// length never revert
|
||||
uint256 length = length@withrevert();
|
||||
assert !lastReverted;
|
||||
|
||||
// key_at reverts iff the index is out of bound
|
||||
key_at@withrevert(index);
|
||||
assert !lastReverted <=> index < length;
|
||||
|
||||
// value_at reverts iff the index is out of bound
|
||||
value_at@withrevert(index);
|
||||
assert !lastReverted <=> index < length;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: get and tryGet return the expected values. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule getAndTryGet(bytes32 key) {
|
||||
requireInvariant noValueIfNotContained(key);
|
||||
|
||||
bool contained = contains(key);
|
||||
bool tryContained = tryGet_contains(key);
|
||||
bytes32 tryValue = tryGet_value(key);
|
||||
bytes32 value = get@withrevert(key); // revert is not contained
|
||||
|
||||
assert contained == tryContained;
|
||||
assert contained => tryValue == value;
|
||||
assert !contained => tryValue == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: set key-value in EnumerableMap │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule set(bytes32 key, bytes32 value, bytes32 otherKey) {
|
||||
require sanity();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bool containsOtherBefore = contains(otherKey);
|
||||
bytes32 otherValueBefore = tryGet_value(otherKey);
|
||||
|
||||
bool added = set@withrevert(key, value);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success && contains(key) && get(key) == value,
|
||||
"liveness & immediate effect";
|
||||
|
||||
assert added <=> !containsBefore,
|
||||
"return value: added iff not contained";
|
||||
|
||||
assert length() == lengthBefore + to_mathint(added ? 1 : 0),
|
||||
"effect: length increases iff added";
|
||||
|
||||
assert added => (key_at(lengthBefore) == key && value_at(lengthBefore) == value),
|
||||
"effect: add at the end";
|
||||
|
||||
assert containsOtherBefore != contains(otherKey) => (added && key == otherKey),
|
||||
"side effect: other keys are not affected";
|
||||
|
||||
assert otherValueBefore != tryGet_value(otherKey) => key == otherKey,
|
||||
"side effect: values attached to other keys are not affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: remove key from EnumerableMap │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule remove(bytes32 key, bytes32 otherKey) {
|
||||
requireInvariant consistencyKey(key);
|
||||
requireInvariant consistencyKey(otherKey);
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bool containsOtherBefore = contains(otherKey);
|
||||
bytes32 otherValueBefore = tryGet_value(otherKey);
|
||||
|
||||
bool removed = remove@withrevert(key);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success && !contains(key),
|
||||
"liveness & immediate effect";
|
||||
|
||||
assert removed <=> containsBefore,
|
||||
"return value: removed iff contained";
|
||||
|
||||
assert length() == lengthBefore - to_mathint(removed ? 1 : 0),
|
||||
"effect: length decreases iff removed";
|
||||
|
||||
assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey),
|
||||
"side effect: other keys are not affected";
|
||||
|
||||
assert otherValueBefore != tryGet_value(otherKey) => key == otherKey,
|
||||
"side effect: values attached to other keys are not affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: when adding a new key, the other keys remain in set, at the same index. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule setEnumerability(bytes32 key, bytes32 value, uint256 index) {
|
||||
require sanity();
|
||||
|
||||
bytes32 atKeyBefore = key_at(index);
|
||||
bytes32 atValueBefore = value_at(index);
|
||||
|
||||
set(key, value);
|
||||
|
||||
bytes32 atKeyAfter = key_at@withrevert(index);
|
||||
assert !lastReverted;
|
||||
|
||||
bytes32 atValueAfter = value_at@withrevert(index);
|
||||
assert !lastReverted;
|
||||
|
||||
assert atKeyAfter == atKeyBefore;
|
||||
assert atValueAfter != atValueBefore => (
|
||||
key == atKeyBefore &&
|
||||
value == atValueAfter
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule removeEnumerability(bytes32 key, uint256 index) {
|
||||
uint256 last = length() - 1;
|
||||
|
||||
requireInvariant consistencyKey(key);
|
||||
requireInvariant consistencyIndex(index);
|
||||
requireInvariant consistencyIndex(last);
|
||||
|
||||
bytes32 atKeyBefore = key_at(index);
|
||||
bytes32 atValueBefore = value_at(index);
|
||||
bytes32 lastKeyBefore = key_at(last);
|
||||
bytes32 lastValueBefore = value_at(last);
|
||||
|
||||
remove(key);
|
||||
|
||||
// can't read last value & keys (length decreased)
|
||||
bytes32 atKeyAfter = key_at@withrevert(index);
|
||||
assert lastReverted <=> index == last;
|
||||
|
||||
bytes32 atValueAfter = value_at@withrevert(index);
|
||||
assert lastReverted <=> index == last;
|
||||
|
||||
// One value that is allowed to change is if previous value was removed,
|
||||
// in that case the last value before took its place.
|
||||
assert (
|
||||
index != last &&
|
||||
atKeyBefore != atKeyAfter
|
||||
) => (
|
||||
atKeyBefore == key &&
|
||||
atKeyAfter == lastKeyBefore
|
||||
);
|
||||
|
||||
assert (
|
||||
index != last &&
|
||||
atValueBefore != atValueAfter
|
||||
) => (
|
||||
atValueAfter == lastValueBefore
|
||||
);
|
||||
}
|
||||
247
certora/specs/EnumerableSet.spec
Normal file
247
certora/specs/EnumerableSet.spec
Normal file
@ -0,0 +1,247 @@
|
||||
import "helpers/helpers.spec"
|
||||
|
||||
methods {
|
||||
// library
|
||||
add(bytes32) returns (bool) envfree
|
||||
remove(bytes32) returns (bool) envfree
|
||||
contains(bytes32) returns (bool) envfree
|
||||
length() returns (uint256) envfree
|
||||
at_(uint256) returns (bytes32) envfree
|
||||
|
||||
// FV
|
||||
_indexOf(bytes32) returns (uint256) envfree
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
function sanity() returns bool {
|
||||
return length() < max_uint256;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: All indexed keys are contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant indexedContained(uint256 index)
|
||||
index < length() => contains(at_(index))
|
||||
{
|
||||
preserved {
|
||||
requireInvariant consistencyIndex(index);
|
||||
requireInvariant consistencyIndex(to_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: A value can only be stored at a single location │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant atUniqueness(uint256 index1, uint256 index2)
|
||||
index1 == index2 <=> at_(index1) == at_(index2)
|
||||
{
|
||||
preserved remove(bytes32 key) {
|
||||
requireInvariant atUniqueness(index1, to_uint256(length() - 1));
|
||||
requireInvariant atUniqueness(index2, to_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: index <> key relationship is consistent │
|
||||
│ │
|
||||
│ Note that the two consistencyXxx invariants, put together, prove that at_ and _indexOf are inverse of one another. │
|
||||
│ This proves that we have a bijection between indices (the enumerability part) and keys (the entries that are added │
|
||||
│ and removed from the EnumerableSet). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant consistencyIndex(uint256 index)
|
||||
index < length() => _indexOf(at_(index)) == index + 1
|
||||
{
|
||||
preserved remove(bytes32 key) {
|
||||
requireInvariant consistencyIndex(to_uint256(length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
invariant consistencyKey(bytes32 key)
|
||||
contains(key) => (
|
||||
_indexOf(key) > 0 &&
|
||||
_indexOf(key) <= length() &&
|
||||
at_(to_uint256(_indexOf(key) - 1)) == key
|
||||
)
|
||||
{
|
||||
preserved remove(bytes32 otherKey) {
|
||||
requireInvariant consistencyKey(otherKey);
|
||||
requireInvariant atUniqueness(
|
||||
to_uint256(_indexOf(key) - 1),
|
||||
to_uint256(_indexOf(otherKey) - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: state only changes by adding or removing elements │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule stateChange(env e, bytes32 key) {
|
||||
require sanity();
|
||||
requireInvariant consistencyKey(key);
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
|
||||
method f;
|
||||
calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
uint256 lengthAfter = length();
|
||||
bool containsAfter = contains(key);
|
||||
|
||||
assert lengthBefore != lengthAfter => (
|
||||
(f.selector == add(bytes32).selector && lengthAfter == lengthBefore + 1) ||
|
||||
(f.selector == remove(bytes32).selector && lengthAfter == lengthBefore - 1)
|
||||
);
|
||||
|
||||
assert containsBefore != containsAfter => (
|
||||
(f.selector == add(bytes32).selector && containsAfter) ||
|
||||
(f.selector == remove(bytes32).selector && containsBefore)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: check liveness of view functions. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule liveness_1(bytes32 key) {
|
||||
requireInvariant consistencyKey(key);
|
||||
|
||||
// contains never revert
|
||||
contains@withrevert(key);
|
||||
assert !lastReverted;
|
||||
}
|
||||
|
||||
rule liveness_2(uint256 index) {
|
||||
requireInvariant consistencyIndex(index);
|
||||
|
||||
// length never revert
|
||||
uint256 length = length@withrevert();
|
||||
assert !lastReverted;
|
||||
|
||||
// at reverts iff the index is out of bound
|
||||
at_@withrevert(index);
|
||||
assert !lastReverted <=> index < length;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: add key to EnumerableSet if not already contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule add(bytes32 key, bytes32 otherKey) {
|
||||
require sanity();
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bool containsOtherBefore = contains(otherKey);
|
||||
|
||||
bool added = add@withrevert(key);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success && contains(key),
|
||||
"liveness & immediate effect";
|
||||
|
||||
assert added <=> !containsBefore,
|
||||
"return value: added iff not contained";
|
||||
|
||||
assert length() == lengthBefore + to_mathint(added ? 1 : 0),
|
||||
"effect: length increases iff added";
|
||||
|
||||
assert added => at_(lengthBefore) == key,
|
||||
"effect: add at the end";
|
||||
|
||||
assert containsOtherBefore != contains(otherKey) => (added && key == otherKey),
|
||||
"side effect: other keys are not affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: remove key from EnumerableSet if already contained │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule remove(bytes32 key, bytes32 otherKey) {
|
||||
requireInvariant consistencyKey(key);
|
||||
requireInvariant consistencyKey(otherKey);
|
||||
|
||||
uint256 lengthBefore = length();
|
||||
bool containsBefore = contains(key);
|
||||
bool containsOtherBefore = contains(otherKey);
|
||||
|
||||
bool removed = remove@withrevert(key);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success && !contains(key),
|
||||
"liveness & immediate effect";
|
||||
|
||||
assert removed <=> containsBefore,
|
||||
"return value: removed iff contained";
|
||||
|
||||
assert length() == lengthBefore - to_mathint(removed ? 1 : 0),
|
||||
"effect: length decreases iff removed";
|
||||
|
||||
assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey),
|
||||
"side effect: other keys are not affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: when adding a new key, the other keys remain in set, at the same index. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule addEnumerability(bytes32 key, uint256 index) {
|
||||
require sanity();
|
||||
|
||||
bytes32 atBefore = at_(index);
|
||||
add(key);
|
||||
bytes32 atAfter = at_@withrevert(index);
|
||||
bool atAfterSuccess = !lastReverted;
|
||||
|
||||
assert atAfterSuccess;
|
||||
assert atBefore == atAfter;
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule removeEnumerability(bytes32 key, uint256 index) {
|
||||
uint256 last = length() - 1;
|
||||
|
||||
requireInvariant consistencyKey(key);
|
||||
requireInvariant consistencyIndex(index);
|
||||
requireInvariant consistencyIndex(last);
|
||||
|
||||
bytes32 atBefore = at_(index);
|
||||
bytes32 lastBefore = at_(last);
|
||||
|
||||
remove(key);
|
||||
|
||||
// can't read last value (length decreased)
|
||||
bytes32 atAfter = at_@withrevert(index);
|
||||
assert lastReverted <=> index == last;
|
||||
|
||||
// One value that is allowed to change is if previous value was removed,
|
||||
// in that case the last value before took its place.
|
||||
assert (
|
||||
index != last &&
|
||||
atBefore != atAfter
|
||||
) => (
|
||||
atBefore == key &&
|
||||
atAfter == lastBefore
|
||||
);
|
||||
}
|
||||
165
certora/specs/Initializable.spec
Normal file
165
certora/specs/Initializable.spec
Normal file
@ -0,0 +1,165 @@
|
||||
import "helpers/helpers.spec"
|
||||
|
||||
methods {
|
||||
// initialize, reinitialize, disable
|
||||
initialize() envfree
|
||||
reinitialize(uint8) envfree
|
||||
disable() envfree
|
||||
|
||||
nested_init_init() envfree
|
||||
nested_init_reinit(uint8) envfree
|
||||
nested_reinit_init(uint8) envfree
|
||||
nested_reinit_reinit(uint8,uint8) envfree
|
||||
|
||||
// view
|
||||
version() returns uint8 envfree
|
||||
initializing() returns bool envfree
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Definitions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
definition isUninitialized() returns bool = version() == 0;
|
||||
definition isInitialized() returns bool = version() > 0;
|
||||
definition isDisabled() returns bool = version() == 255;
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: A contract must only ever be in an initializing state while in the middle of a transaction execution. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant notInitializing()
|
||||
!initializing()
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: The version cannot decrease & disable state is irrevocable. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule increasingVersion(env e) {
|
||||
uint8 versionBefore = version();
|
||||
bool disabledBefore = isDisabled();
|
||||
|
||||
method f; calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
assert versionBefore <= version(), "_initialized must only increase";
|
||||
assert disabledBefore => isDisabled(), "a disabled initializer must stay disabled";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Cannot initialize a contract that is already initialized. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule cannotInitializeTwice() {
|
||||
require isInitialized();
|
||||
|
||||
initialize@withrevert();
|
||||
|
||||
assert lastReverted, "contract must only be initialized once";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Cannot initialize once disabled. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule cannotInitializeOnceDisabled() {
|
||||
require isDisabled();
|
||||
|
||||
initialize@withrevert();
|
||||
|
||||
assert lastReverted, "contract is disabled";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Cannot reinitialize once disabled. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule cannotReinitializeOnceDisabled() {
|
||||
require isDisabled();
|
||||
|
||||
uint8 n;
|
||||
reinitialize@withrevert(n);
|
||||
|
||||
assert lastReverted, "contract is disabled";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Cannot nest initializers (after construction). │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule cannotNestInitializers_init_init() {
|
||||
nested_init_init@withrevert();
|
||||
assert lastReverted, "nested initializers";
|
||||
}
|
||||
|
||||
rule cannotNestInitializers_init_reinit(uint8 m) {
|
||||
nested_init_reinit@withrevert(m);
|
||||
assert lastReverted, "nested initializers";
|
||||
}
|
||||
|
||||
rule cannotNestInitializers_reinit_init(uint8 n) {
|
||||
nested_reinit_init@withrevert(n);
|
||||
assert lastReverted, "nested initializers";
|
||||
}
|
||||
|
||||
rule cannotNestInitializers_reinit_reinit(uint8 n, uint8 m) {
|
||||
nested_reinit_reinit@withrevert(n, m);
|
||||
assert lastReverted, "nested initializers";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Initialize correctly sets the version. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule initializeEffects() {
|
||||
requireInvariant notInitializing();
|
||||
|
||||
bool isUninitializedBefore = isUninitialized();
|
||||
|
||||
initialize@withrevert();
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> isUninitializedBefore, "can only initialize uninitialized contracts";
|
||||
assert success => version() == 1, "initialize must set version() to 1";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Reinitialize correctly sets the version. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule reinitializeEffects() {
|
||||
requireInvariant notInitializing();
|
||||
|
||||
uint8 versionBefore = version();
|
||||
|
||||
uint8 n;
|
||||
reinitialize@withrevert(n);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> versionBefore < n, "can only reinitialize to a latter versions";
|
||||
assert success => version() == n, "reinitialize must set version() to n";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: Can disable. │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule disableEffect() {
|
||||
requireInvariant notInitializing();
|
||||
|
||||
disable@withrevert();
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success, "call to _disableInitializers failed";
|
||||
assert isDisabled(), "disable state not set";
|
||||
}
|
||||
78
certora/specs/Ownable.spec
Normal file
78
certora/specs/Ownable.spec
Normal file
@ -0,0 +1,78 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IOwnable.spec"
|
||||
|
||||
methods {
|
||||
restricted()
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: transferOwnership changes ownership │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule transferOwnership(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address newOwner;
|
||||
address current = owner();
|
||||
|
||||
transferOwnership@withrevert(e, newOwner);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> (e.msg.sender == current && newOwner != 0), "unauthorized caller or invalid arg";
|
||||
assert success => owner() == newOwner, "current owner changed";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: renounceOwnership removes the owner │
|
||||
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule renounceOwnership(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address current = owner();
|
||||
|
||||
renounceOwnership@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller";
|
||||
assert success => owner() == 0, "owner not cleared";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Access control: only current owner can call restricted functions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyCurrentOwnerCanCallOnlyOwner(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address current = owner();
|
||||
|
||||
calldataarg args;
|
||||
restricted@withrevert(e, args);
|
||||
|
||||
assert !lastReverted <=> e.msg.sender == current, "access control failed";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: ownership can only change in specific ways │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e) {
|
||||
address oldCurrent = owner();
|
||||
|
||||
method f; calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
address newCurrent = owner();
|
||||
|
||||
// If owner changes, must be either transferOwnership or renounceOwnership
|
||||
assert oldCurrent != newCurrent => (
|
||||
(e.msg.sender == oldCurrent && newCurrent != 0 && f.selector == transferOwnership(address).selector) ||
|
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && f.selector == renounceOwnership().selector)
|
||||
);
|
||||
}
|
||||
108
certora/specs/Ownable2Step.spec
Normal file
108
certora/specs/Ownable2Step.spec
Normal file
@ -0,0 +1,108 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IOwnable2Step.spec"
|
||||
|
||||
methods {
|
||||
restricted()
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: transferOwnership sets the pending owner │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule transferOwnership(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address newOwner;
|
||||
address current = owner();
|
||||
|
||||
transferOwnership@withrevert(e, newOwner);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller";
|
||||
assert success => pendingOwner() == newOwner, "pending owner not set";
|
||||
assert success => owner() == current, "current owner changed";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: renounceOwnership removes the owner and the pendingOwner │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule renounceOwnership(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address current = owner();
|
||||
|
||||
renounceOwnership@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> e.msg.sender == current, "unauthorized caller";
|
||||
assert success => pendingOwner() == 0, "pending owner not cleared";
|
||||
assert success => owner() == 0, "owner not cleared";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: acceptOwnership changes owner and reset pending owner │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule acceptOwnership(env e) {
|
||||
|
||||
require nonpayable(e);
|
||||
|
||||
address current = owner();
|
||||
address pending = pendingOwner();
|
||||
|
||||
acceptOwnership@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
assert success <=> e.msg.sender == pending, "unauthorized caller";
|
||||
assert success => pendingOwner() == 0, "pending owner not cleared";
|
||||
assert success => owner() == pending, "owner not transferred";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Access control: only current owner can call restricted functions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule onlyCurrentOwnerCanCallOnlyOwner(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
address current = owner();
|
||||
|
||||
calldataarg args;
|
||||
restricted@withrevert(e, args);
|
||||
|
||||
assert !lastReverted <=> e.msg.sender == current, "access control failed";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: ownership and pending ownership can only change in specific ways │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule ownerOrPendingOwnerChange(env e, method f) {
|
||||
address oldCurrent = owner();
|
||||
address oldPending = pendingOwner();
|
||||
|
||||
calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
address newCurrent = owner();
|
||||
address newPending = pendingOwner();
|
||||
|
||||
// If owner changes, must be either acceptOwnership or renounceOwnership
|
||||
assert oldCurrent != newCurrent => (
|
||||
(e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == acceptOwnership().selector) ||
|
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == renounceOwnership().selector)
|
||||
);
|
||||
|
||||
// If pending changes, must be either acceptance or reset
|
||||
assert oldPending != newPending => (
|
||||
(e.msg.sender == oldCurrent && newCurrent == oldCurrent && f.selector == transferOwnership(address).selector) ||
|
||||
(e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == acceptOwnership().selector) ||
|
||||
(e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == renounceOwnership().selector)
|
||||
);
|
||||
}
|
||||
96
certora/specs/Pausable.spec
Normal file
96
certora/specs/Pausable.spec
Normal file
@ -0,0 +1,96 @@
|
||||
import "helpers/helpers.spec"
|
||||
|
||||
methods {
|
||||
paused() returns (bool) envfree
|
||||
pause()
|
||||
unpause()
|
||||
onlyWhenPaused()
|
||||
onlyWhenNotPaused()
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: _pause pauses the contract │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule pause(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
bool pausedBefore = paused();
|
||||
|
||||
pause@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool pausedAfter = paused();
|
||||
|
||||
// liveness
|
||||
assert success <=> !pausedBefore, "works if and only if the contract was not paused before";
|
||||
|
||||
// effect
|
||||
assert success => pausedAfter, "contract must be paused after a successful call";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: _unpause unpauses the contract │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule unpause(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
bool pausedBefore = paused();
|
||||
|
||||
unpause@withrevert(e);
|
||||
bool success = !lastReverted;
|
||||
|
||||
bool pausedAfter = paused();
|
||||
|
||||
// liveness
|
||||
assert success <=> pausedBefore, "works if and only if the contract was paused before";
|
||||
|
||||
// effect
|
||||
assert success => !pausedAfter, "contract must be unpaused after a successful call";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: whenPaused modifier can only be called if the contract is paused │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule whenPaused(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
onlyWhenPaused@withrevert(e);
|
||||
assert !lastReverted <=> paused(), "works if and only if the contract is paused";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Function correctness: whenNotPaused modifier can only be called if the contract is not paused │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule whenNotPaused(env e) {
|
||||
require nonpayable(e);
|
||||
|
||||
onlyWhenNotPaused@withrevert(e);
|
||||
assert !lastReverted <=> !paused(), "works if and only if the contract is not paused";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rules: only _pause and _unpause can change paused status │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule noPauseChange(env e) {
|
||||
method f;
|
||||
calldataarg args;
|
||||
|
||||
bool pausedBefore = paused();
|
||||
f(e, args);
|
||||
bool pausedAfter = paused();
|
||||
|
||||
assert pausedBefore != pausedAfter => (
|
||||
(!pausedAfter && f.selector == unpause().selector) ||
|
||||
(pausedAfter && f.selector == pause().selector)
|
||||
), "contract's paused status can only be changed by _pause() or _unpause()";
|
||||
}
|
||||
275
certora/specs/TimelockController.spec
Normal file
275
certora/specs/TimelockController.spec
Normal file
@ -0,0 +1,275 @@
|
||||
import "helpers/helpers.spec"
|
||||
import "methods/IAccessControl.spec"
|
||||
|
||||
methods {
|
||||
TIMELOCK_ADMIN_ROLE() returns (bytes32) envfree
|
||||
PROPOSER_ROLE() returns (bytes32) envfree
|
||||
EXECUTOR_ROLE() returns (bytes32) envfree
|
||||
CANCELLER_ROLE() returns (bytes32) envfree
|
||||
isOperation(bytes32) returns (bool) envfree
|
||||
isOperationPending(bytes32) returns (bool) envfree
|
||||
isOperationReady(bytes32) returns (bool)
|
||||
isOperationDone(bytes32) returns (bool) envfree
|
||||
getTimestamp(bytes32) returns (uint256) envfree
|
||||
getMinDelay() returns (uint256) envfree
|
||||
|
||||
hashOperation(address, uint256, bytes, bytes32, bytes32) returns(bytes32) envfree
|
||||
hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) returns(bytes32) envfree
|
||||
|
||||
schedule(address, uint256, bytes, bytes32, bytes32, uint256)
|
||||
scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256)
|
||||
execute(address, uint256, bytes, bytes32, bytes32)
|
||||
executeBatch(address[], uint256[], bytes[], bytes32, bytes32)
|
||||
cancel(bytes32)
|
||||
|
||||
updateDelay(uint256)
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Helpers │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
// Uniformly handle scheduling of batched and non-batched operations.
|
||||
function helperScheduleWithRevert(env e, method f, bytes32 id, uint256 delay) {
|
||||
if (f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector) {
|
||||
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
|
||||
require hashOperation(target, value, data, predecessor, salt) == id; // Correlation
|
||||
schedule@withrevert(e, target, value, data, predecessor, salt, delay);
|
||||
} else if (f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector) {
|
||||
address[] targets; uint256[] values; bytes[] payloads; bytes32 predecessor; bytes32 salt;
|
||||
require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation
|
||||
scheduleBatch@withrevert(e, targets, values, payloads, predecessor, salt, delay);
|
||||
} else {
|
||||
calldataarg args;
|
||||
f@withrevert(e, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Uniformly handle execution of batched and non-batched operations.
|
||||
function helperExecuteWithRevert(env e, method f, bytes32 id, bytes32 predecessor) {
|
||||
if (f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector) {
|
||||
address target; uint256 value; bytes data; bytes32 salt;
|
||||
require hashOperation(target, value, data, predecessor, salt) == id; // Correlation
|
||||
execute@withrevert(e, target, value, data, predecessor, salt);
|
||||
} else if (f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector) {
|
||||
address[] targets; uint256[] values; bytes[] payloads; bytes32 salt;
|
||||
require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation
|
||||
executeBatch@withrevert(e, targets, values, payloads, predecessor, salt);
|
||||
} else {
|
||||
calldataarg args;
|
||||
f@withrevert(e, args);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Definitions │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
definition DONE_TIMESTAMP() returns uint256 = 1;
|
||||
definition UNSET() returns uint8 = 0x1;
|
||||
definition PENDING() returns uint8 = 0x2;
|
||||
definition DONE() returns uint8 = 0x4;
|
||||
|
||||
definition isUnset(bytes32 id) returns bool = !isOperation(id);
|
||||
definition isPending(bytes32 id) returns bool = isOperationPending(id);
|
||||
definition isDone(bytes32 id) returns bool = isOperationDone(id);
|
||||
definition state(bytes32 id) returns uint8 = (isUnset(id) ? UNSET() : 0) | (isPending(id) ? PENDING() : 0) | (isDone(id) ? DONE() : 0);
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariants: consistency of accessors │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant isOperationCheck(bytes32 id)
|
||||
isOperation(id) <=> getTimestamp(id) > 0
|
||||
filtered { f -> !f.isView }
|
||||
|
||||
invariant isOperationPendingCheck(bytes32 id)
|
||||
isOperationPending(id) <=> getTimestamp(id) > DONE_TIMESTAMP()
|
||||
filtered { f -> !f.isView }
|
||||
|
||||
invariant isOperationDoneCheck(bytes32 id)
|
||||
isOperationDone(id) <=> getTimestamp(id) == DONE_TIMESTAMP()
|
||||
filtered { f -> !f.isView }
|
||||
|
||||
invariant isOperationReadyCheck(env e, bytes32 id)
|
||||
isOperationReady(e, id) <=> (isOperationPending(id) && getTimestamp(id) <= e.block.timestamp)
|
||||
filtered { f -> !f.isView }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Invariant: a proposal id is either unset, pending or done │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
invariant stateConsistency(bytes32 id, env e)
|
||||
// Check states are mutually exclusive
|
||||
(isUnset(id) <=> (!isPending(id) && !isDone(id) )) &&
|
||||
(isPending(id) <=> (!isUnset(id) && !isDone(id) )) &&
|
||||
(isDone(id) <=> (!isUnset(id) && !isPending(id))) &&
|
||||
// Check that the state helper behaves as expected:
|
||||
(isUnset(id) <=> state(id) == UNSET() ) &&
|
||||
(isPending(id) <=> state(id) == PENDING() ) &&
|
||||
(isDone(id) <=> state(id) == DONE() ) &&
|
||||
// Check substate
|
||||
isOperationReady(e, id) => isPending(id)
|
||||
filtered { f -> !f.isView }
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: state transition rules │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule stateTransition(bytes32 id, env e, method f, calldataarg args) {
|
||||
require e.block.timestamp > 1; // Sanity
|
||||
|
||||
uint8 stateBefore = state(id);
|
||||
f(e, args);
|
||||
uint8 stateAfter = state(id);
|
||||
|
||||
// Cannot jump from UNSET to DONE
|
||||
assert stateBefore == UNSET() => stateAfter != DONE();
|
||||
|
||||
// UNSET → PENDING: schedule or scheduleBatch
|
||||
assert stateBefore == UNSET() && stateAfter == PENDING() => (
|
||||
f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector ||
|
||||
f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector
|
||||
);
|
||||
|
||||
// PENDING → UNSET: cancel
|
||||
assert stateBefore == PENDING() && stateAfter == UNSET() => (
|
||||
f.selector == cancel(bytes32).selector
|
||||
);
|
||||
|
||||
// PENDING → DONE: execute or executeBatch
|
||||
assert stateBefore == PENDING() && stateAfter == DONE() => (
|
||||
f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector ||
|
||||
f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector
|
||||
);
|
||||
|
||||
// DONE is final
|
||||
assert stateBefore == DONE() => stateAfter == DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: minimum delay can only be updated through a timelock execution │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule minDelayOnlyChange(env e) {
|
||||
uint256 delayBefore = getMinDelay();
|
||||
|
||||
method f; calldataarg args;
|
||||
f(e, args);
|
||||
|
||||
assert delayBefore != getMinDelay() => (e.msg.sender == currentContract && f.selector == updateDelay(uint256).selector), "Unauthorized delay update";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: schedule liveness and effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule schedule(env e, method f, bytes32 id, uint256 delay) filtered { f ->
|
||||
f.selector == schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector ||
|
||||
f.selector == scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector
|
||||
} {
|
||||
require nonpayable(e);
|
||||
|
||||
// Basic timestamp assumptions
|
||||
require e.block.timestamp > 1;
|
||||
require e.block.timestamp + delay < max_uint256;
|
||||
require e.block.timestamp + getMinDelay() < max_uint256;
|
||||
|
||||
bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId);
|
||||
|
||||
uint8 stateBefore = state(id);
|
||||
bool isDelaySufficient = delay >= getMinDelay();
|
||||
bool isProposerBefore = hasRole(PROPOSER_ROLE(), e.msg.sender);
|
||||
|
||||
helperScheduleWithRevert(e, f, id, delay);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
stateBefore == UNSET() &&
|
||||
isDelaySufficient &&
|
||||
isProposerBefore
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => state(id) == PENDING(), "State transition violation";
|
||||
assert success => getTimestamp(id) == to_uint256(e.block.timestamp + delay), "Proposal timestamp not correctly set";
|
||||
|
||||
// no side effect
|
||||
assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: execute liveness and effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule execute(env e, method f, bytes32 id, bytes32 predecessor) filtered { f ->
|
||||
f.selector == execute(address, uint256, bytes, bytes32, bytes32).selector ||
|
||||
f.selector == executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector
|
||||
} {
|
||||
bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId);
|
||||
|
||||
uint8 stateBefore = state(id);
|
||||
bool isOperationReadyBefore = isOperationReady(e, id);
|
||||
bool isExecutorOrOpen = hasRole(EXECUTOR_ROLE(), e.msg.sender) || hasRole(EXECUTOR_ROLE(), 0);
|
||||
bool predecessorDependency = predecessor == 0 || isDone(predecessor);
|
||||
|
||||
helperExecuteWithRevert(e, f, id, predecessor);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// The underlying transaction can revert, and that would cause the execution to revert. We can check that all non
|
||||
// reverting calls meet the requirements in terms of proposal readiness, access control and predecessor dependency.
|
||||
// We can't however guarantee that these requirements being meet ensure liveness of the proposal, because the
|
||||
// proposal can revert for reasons beyond our control.
|
||||
|
||||
// liveness, should be `<=>` but can only check `=>` (see comment above)
|
||||
assert success => (
|
||||
stateBefore == PENDING() &&
|
||||
isOperationReadyBefore &&
|
||||
predecessorDependency &&
|
||||
isExecutorOrOpen
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => state(id) == DONE(), "State transition violation";
|
||||
|
||||
// no side effect
|
||||
assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected";
|
||||
}
|
||||
|
||||
/*
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Rule: cancel liveness and effects │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
*/
|
||||
rule cancel(env e, bytes32 id) {
|
||||
require nonpayable(e);
|
||||
|
||||
bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId);
|
||||
|
||||
uint8 stateBefore = state(id);
|
||||
bool isCanceller = hasRole(CANCELLER_ROLE(), e.msg.sender);
|
||||
|
||||
cancel@withrevert(e, id);
|
||||
bool success = !lastReverted;
|
||||
|
||||
// liveness
|
||||
assert success <=> (
|
||||
stateBefore == PENDING() &&
|
||||
isCanceller
|
||||
);
|
||||
|
||||
// effect
|
||||
assert success => state(id) == UNSET(), "State transition violation";
|
||||
|
||||
// no side effect
|
||||
assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected";
|
||||
}
|
||||
1
certora/specs/helpers/helpers.spec
Normal file
1
certora/specs/helpers/helpers.spec
Normal file
@ -0,0 +1 @@
|
||||
definition nonpayable(env e) returns bool = e.msg.value == 0;
|
||||
7
certora/specs/methods/IAccessControl.spec
Normal file
7
certora/specs/methods/IAccessControl.spec
Normal file
@ -0,0 +1,7 @@
|
||||
methods {
|
||||
hasRole(bytes32, address) returns(bool) envfree
|
||||
getRoleAdmin(bytes32) returns(bytes32) envfree
|
||||
grantRole(bytes32, address)
|
||||
revokeRole(bytes32, address)
|
||||
renounceRole(bytes32, address)
|
||||
}
|
||||
36
certora/specs/methods/IAccessControlDefaultAdminRules.spec
Normal file
36
certora/specs/methods/IAccessControlDefaultAdminRules.spec
Normal file
@ -0,0 +1,36 @@
|
||||
import "./IERC5313.spec"
|
||||
|
||||
methods {
|
||||
// === View ==
|
||||
|
||||
// Default Admin
|
||||
defaultAdmin() returns(address) envfree
|
||||
pendingDefaultAdmin() returns(address, uint48) envfree
|
||||
|
||||
// Default Admin Delay
|
||||
defaultAdminDelay() returns(uint48)
|
||||
pendingDefaultAdminDelay() returns(uint48, uint48)
|
||||
defaultAdminDelayIncreaseWait() returns(uint48) envfree
|
||||
|
||||
// === Mutations ==
|
||||
|
||||
// Default Admin
|
||||
beginDefaultAdminTransfer(address)
|
||||
cancelDefaultAdminTransfer()
|
||||
acceptDefaultAdminTransfer()
|
||||
|
||||
// Default Admin Delay
|
||||
changeDefaultAdminDelay(uint48)
|
||||
rollbackDefaultAdminDelay()
|
||||
|
||||
// == FV ==
|
||||
|
||||
// Default Admin
|
||||
pendingDefaultAdmin_() returns (address) envfree
|
||||
pendingDefaultAdminSchedule_() returns (uint48) envfree
|
||||
|
||||
// Default Admin Delay
|
||||
pendingDelay_() returns (uint48)
|
||||
pendingDelaySchedule_() returns (uint48)
|
||||
delayChangeWait_(uint48) returns (uint48)
|
||||
}
|
||||
11
certora/specs/methods/IERC20.spec
Normal file
11
certora/specs/methods/IERC20.spec
Normal file
@ -0,0 +1,11 @@
|
||||
methods {
|
||||
name() returns (string) envfree => DISPATCHER(true)
|
||||
symbol() returns (string) envfree => DISPATCHER(true)
|
||||
decimals() returns (uint8) envfree => DISPATCHER(true)
|
||||
totalSupply() returns (uint256) envfree => DISPATCHER(true)
|
||||
balanceOf(address) returns (uint256) envfree => DISPATCHER(true)
|
||||
allowance(address,address) returns (uint256) envfree => DISPATCHER(true)
|
||||
approve(address,uint256) returns (bool) => DISPATCHER(true)
|
||||
transfer(address,uint256) returns (bool) => DISPATCHER(true)
|
||||
transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true)
|
||||
}
|
||||
5
certora/specs/methods/IERC2612.spec
Normal file
5
certora/specs/methods/IERC2612.spec
Normal file
@ -0,0 +1,5 @@
|
||||
methods {
|
||||
permit(address,address,uint256,uint256,uint8,bytes32,bytes32) => DISPATCHER(true)
|
||||
nonces(address) returns (uint256) envfree => DISPATCHER(true)
|
||||
DOMAIN_SEPARATOR() returns (bytes32) envfree => DISPATCHER(true)
|
||||
}
|
||||
5
certora/specs/methods/IERC3156.spec
Normal file
5
certora/specs/methods/IERC3156.spec
Normal file
@ -0,0 +1,5 @@
|
||||
methods {
|
||||
maxFlashLoan(address) returns (uint256) envfree => DISPATCHER(true)
|
||||
flashFee(address,uint256) returns (uint256) envfree => DISPATCHER(true)
|
||||
flashLoan(address,address,uint256,bytes) returns (bool) => DISPATCHER(true)
|
||||
}
|
||||
3
certora/specs/methods/IERC5313.spec
Normal file
3
certora/specs/methods/IERC5313.spec
Normal file
@ -0,0 +1,3 @@
|
||||
methods {
|
||||
owner() returns (address) envfree
|
||||
}
|
||||
20
certora/specs/methods/IERC721.spec
Normal file
20
certora/specs/methods/IERC721.spec
Normal file
@ -0,0 +1,20 @@
|
||||
methods {
|
||||
// IERC721
|
||||
balanceOf(address) returns (uint256) envfree => DISPATCHER(true)
|
||||
ownerOf(uint256) returns (address) envfree => DISPATCHER(true)
|
||||
getApproved(uint256) returns (address) envfree => DISPATCHER(true)
|
||||
isApprovedForAll(address,address) returns (bool) envfree => DISPATCHER(true)
|
||||
safeTransferFrom(address,address,uint256,bytes) => DISPATCHER(true)
|
||||
safeTransferFrom(address,address,uint256) => DISPATCHER(true)
|
||||
transferFrom(address,address,uint256) => DISPATCHER(true)
|
||||
approve(address,uint256) => DISPATCHER(true)
|
||||
setApprovalForAll(address,bool) => DISPATCHER(true)
|
||||
|
||||
// IERC721Metadata
|
||||
name() returns (string) => DISPATCHER(true)
|
||||
symbol() returns (string) => DISPATCHER(true)
|
||||
tokenURI(uint256) returns (string) => DISPATCHER(true)
|
||||
|
||||
// IERC721Receiver
|
||||
onERC721Received(address,address,uint256,bytes) returns (bytes4) => DISPATCHER(true)
|
||||
}
|
||||
5
certora/specs/methods/IOwnable.spec
Normal file
5
certora/specs/methods/IOwnable.spec
Normal file
@ -0,0 +1,5 @@
|
||||
methods {
|
||||
owner() returns (address) envfree
|
||||
transferOwnership(address)
|
||||
renounceOwnership()
|
||||
}
|
||||
7
certora/specs/methods/IOwnable2Step.spec
Normal file
7
certora/specs/methods/IOwnable2Step.spec
Normal file
@ -0,0 +1,7 @@
|
||||
methods {
|
||||
owner() returns (address) envfree
|
||||
pendingOwner() returns (address) envfree
|
||||
transferOwnership(address)
|
||||
acceptOwnership()
|
||||
renounceOwnership()
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
mocks
|
||||
examples
|
||||
@ -1,27 +0,0 @@
|
||||
## Architecture
|
||||
|
||||
The following provides visibility into how OpenZeppelin's contracts are organized:
|
||||
|
||||
- **access** - Smart contracts that enable functionality that can be used for selective restrictions and basic authorization control functions.
|
||||
- **crowdsale** - A collection of smart contracts used to manage token crowdsales that allow investors to purchase tokens with ETH. Includes a base contract which implements fundamental crowdsale functionality in its simplest form. The base contract can be extended in order to satisfy your crowdsale’s specific requirements.
|
||||
- **distribution** - Includes extensions of the base crowdsale contract which can be used to customize the completion of a crowdsale.
|
||||
- **emission** - Includes extensions of the base crowdsale contract which can be used to mint and manage how tokens are issued to purchasers.
|
||||
- **price** - Includes extensions of the crowdsale contract that can be used to manage changes in token prices.
|
||||
- **validation** - Includes extensions of the crowdsale contract that can be used to enforce restraints and limit access to token purchases.
|
||||
- **examples** - A collection of simple smart contracts that demonstrate how to add new features to base contracts through multiple inheritance.
|
||||
- **introspection** - An interface that can be used to make a contract comply with the ERC-165 standard as well as a contract that implements ERC-165 using a lookup table.
|
||||
- **lifecycle** - A collection of base contracts used to manage the existence and behavior of your contracts and their funds.
|
||||
- **math** - Libraries with safety checks on operations that throw on errors.
|
||||
- **mocks** - A collection of abstract contracts that are primarily used for unit testing. They also serve as good usage examples and demonstrate how to combine contracts with inheritance when developing your own custom applications.
|
||||
- **ownership** - A collection of smart contracts that can be used to manage contract and token ownership
|
||||
- **payment** - A collection of smart contracts that can be used to manage payments through escrow arrangements, withdrawals, and claims. Includes support for both single payees and multiple payees.
|
||||
- **proposals** - A collection of smart contracts that reflect community Ethereum Improvement Proposals (EIPs). These contracts are under development and standardization. They are not recommended for production, but they are useful for experimentation with pending EIP standards. Go [here](https://github.com/OpenZeppelin/openzeppelin-solidity/wiki/ERC-Process) for more information.
|
||||
|
||||
- **token** - A collection of approved ERC standard tokens -- their interfaces and implementations.
|
||||
- **ERC20** - A standard interface for fungible tokens:
|
||||
- *Interfaces* - Includes the ERC-20 token standard basic interface. I.e., what the contract’s ABI can represent.
|
||||
- *Implementations* - Includes ERC-20 token implementations that include all required and some optional ERC-20 functionality.
|
||||
- **ERC721** - A standard interface for non-fungible tokens
|
||||
- *Interfaces* - Includes the ERC-721 token standard basic interface. I.e., what the contract’s ABI can represent.
|
||||
- *Implementations* - Includes ERC-721 token implementations that include all required and some optional ERC-721 functionality.
|
||||
|
||||
248
contracts/access/AccessControl.sol
Normal file
248
contracts/access/AccessControl.sol
Normal file
@ -0,0 +1,248 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./IAccessControl.sol";
|
||||
import "../utils/Context.sol";
|
||||
import "../utils/Strings.sol";
|
||||
import "../utils/introspection/ERC165.sol";
|
||||
|
||||
/**
|
||||
* @dev Contract module that allows children to implement role-based access
|
||||
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
|
||||
* members except through off-chain means by accessing the contract event logs. Some
|
||||
* applications may benefit from on-chain enumerability, for those cases see
|
||||
* {AccessControlEnumerable}.
|
||||
*
|
||||
* Roles are referred to by their `bytes32` identifier. These should be exposed
|
||||
* in the external API and be unique. The best way to achieve this is by
|
||||
* using `public constant` hash digests:
|
||||
*
|
||||
* ```solidity
|
||||
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
|
||||
* ```
|
||||
*
|
||||
* Roles can be used to represent a set of permissions. To restrict access to a
|
||||
* function call, use {hasRole}:
|
||||
*
|
||||
* ```solidity
|
||||
* function foo() public {
|
||||
* require(hasRole(MY_ROLE, msg.sender));
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Roles can be granted and revoked dynamically via the {grantRole} and
|
||||
* {revokeRole} functions. Each role has an associated admin role, and only
|
||||
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
|
||||
*
|
||||
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
|
||||
* that only accounts with this role will be able to grant or revoke other
|
||||
* roles. More complex role relationships can be created by using
|
||||
* {_setRoleAdmin}.
|
||||
*
|
||||
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
|
||||
* grant and revoke this role. Extra precautions should be taken to secure
|
||||
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
|
||||
* to enforce additional security measures for this role.
|
||||
*/
|
||||
abstract contract AccessControl is Context, IAccessControl, ERC165 {
|
||||
struct RoleData {
|
||||
mapping(address => bool) members;
|
||||
bytes32 adminRole;
|
||||
}
|
||||
|
||||
mapping(bytes32 => RoleData) private _roles;
|
||||
|
||||
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
|
||||
|
||||
/**
|
||||
* @dev Modifier that checks that an account has a specific role. Reverts
|
||||
* with a standardized message including the required role.
|
||||
*
|
||||
* The format of the revert reason is given by the following regular expression:
|
||||
*
|
||||
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
|
||||
*
|
||||
* _Available since v4.1._
|
||||
*/
|
||||
modifier onlyRole(bytes32 role) {
|
||||
_checkRole(role);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {IERC165-supportsInterface}.
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
||||
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns `true` if `account` has been granted `role`.
|
||||
*/
|
||||
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
|
||||
return _roles[role].members[account];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
|
||||
* Overriding this function changes the behavior of the {onlyRole} modifier.
|
||||
*
|
||||
* Format of the revert message is described in {_checkRole}.
|
||||
*
|
||||
* _Available since v4.6._
|
||||
*/
|
||||
function _checkRole(bytes32 role) internal view virtual {
|
||||
_checkRole(role, _msgSender());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revert with a standard message if `account` is missing `role`.
|
||||
*
|
||||
* The format of the revert reason is given by the following regular expression:
|
||||
*
|
||||
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
|
||||
*/
|
||||
function _checkRole(bytes32 role, address account) internal view virtual {
|
||||
if (!hasRole(role, account)) {
|
||||
revert(
|
||||
string(
|
||||
abi.encodePacked(
|
||||
"AccessControl: account ",
|
||||
Strings.toHexString(account),
|
||||
" is missing role ",
|
||||
Strings.toHexString(uint256(role), 32)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the admin role that controls `role`. See {grantRole} and
|
||||
* {revokeRole}.
|
||||
*
|
||||
* To change a role's admin, use {_setRoleAdmin}.
|
||||
*/
|
||||
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
|
||||
return _roles[role].adminRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Grants `role` to `account`.
|
||||
*
|
||||
* If `account` had not been already granted `role`, emits a {RoleGranted}
|
||||
* event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must have ``role``'s admin role.
|
||||
*
|
||||
* May emit a {RoleGranted} event.
|
||||
*/
|
||||
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
|
||||
_grantRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes `role` from `account`.
|
||||
*
|
||||
* If `account` had been granted `role`, emits a {RoleRevoked} event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must have ``role``'s admin role.
|
||||
*
|
||||
* May emit a {RoleRevoked} event.
|
||||
*/
|
||||
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
|
||||
_revokeRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes `role` from the calling account.
|
||||
*
|
||||
* Roles are often managed via {grantRole} and {revokeRole}: this function's
|
||||
* purpose is to provide a mechanism for accounts to lose their privileges
|
||||
* if they are compromised (such as when a trusted device is misplaced).
|
||||
*
|
||||
* If the calling account had been revoked `role`, emits a {RoleRevoked}
|
||||
* event.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - the caller must be `account`.
|
||||
*
|
||||
* May emit a {RoleRevoked} event.
|
||||
*/
|
||||
function renounceRole(bytes32 role, address account) public virtual override {
|
||||
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
|
||||
|
||||
_revokeRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Grants `role` to `account`.
|
||||
*
|
||||
* If `account` had not been already granted `role`, emits a {RoleGranted}
|
||||
* event. Note that unlike {grantRole}, this function doesn't perform any
|
||||
* checks on the calling account.
|
||||
*
|
||||
* May emit a {RoleGranted} event.
|
||||
*
|
||||
* [WARNING]
|
||||
* ====
|
||||
* This function should only be called from the constructor when setting
|
||||
* up the initial roles for the system.
|
||||
*
|
||||
* Using this function in any other way is effectively circumventing the admin
|
||||
* system imposed by {AccessControl}.
|
||||
* ====
|
||||
*
|
||||
* NOTE: This function is deprecated in favor of {_grantRole}.
|
||||
*/
|
||||
function _setupRole(bytes32 role, address account) internal virtual {
|
||||
_grantRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets `adminRole` as ``role``'s admin role.
|
||||
*
|
||||
* Emits a {RoleAdminChanged} event.
|
||||
*/
|
||||
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
|
||||
bytes32 previousAdminRole = getRoleAdmin(role);
|
||||
_roles[role].adminRole = adminRole;
|
||||
emit RoleAdminChanged(role, previousAdminRole, adminRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Grants `role` to `account`.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*
|
||||
* May emit a {RoleGranted} event.
|
||||
*/
|
||||
function _grantRole(bytes32 role, address account) internal virtual {
|
||||
if (!hasRole(role, account)) {
|
||||
_roles[role].members[account] = true;
|
||||
emit RoleGranted(role, account, _msgSender());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes `role` from `account`.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*
|
||||
* May emit a {RoleRevoked} event.
|
||||
*/
|
||||
function _revokeRole(bytes32 role, address account) internal virtual {
|
||||
if (hasRole(role, account)) {
|
||||
_roles[role].members[account] = false;
|
||||
emit RoleRevoked(role, account, _msgSender());
|
||||
}
|
||||
}
|
||||
}
|
||||
45
contracts/access/AccessControlCrossChain.sol
Normal file
45
contracts/access/AccessControlCrossChain.sol
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.6.0) (access/AccessControlCrossChain.sol)
|
||||
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "./AccessControl.sol";
|
||||
import "../crosschain/CrossChainEnabled.sol";
|
||||
|
||||
/**
|
||||
* @dev An extension to {AccessControl} with support for cross-chain access management.
|
||||
* For each role, is extension implements an equivalent "aliased" role that is used for
|
||||
* restricting calls originating from other chains.
|
||||
*
|
||||
* For example, if a function `myFunction` is protected by `onlyRole(SOME_ROLE)`, and
|
||||
* if an address `x` has role `SOME_ROLE`, it would be able to call `myFunction` directly.
|
||||
* A wallet or contract at the same address on another chain would however not be able
|
||||
* to call this function. In order to do so, it would require to have the role
|
||||
* `_crossChainRoleAlias(SOME_ROLE)`.
|
||||
*
|
||||
* This aliasing is required to protect against multiple contracts living at the same
|
||||
* address on different chains but controlled by conflicting entities.
|
||||
*
|
||||
* _Available since v4.6._
|
||||
*/
|
||||
abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
|
||||
bytes32 public constant CROSSCHAIN_ALIAS = keccak256("CROSSCHAIN_ALIAS");
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-_checkRole}.
|
||||
*/
|
||||
function _checkRole(bytes32 role) internal view virtual override {
|
||||
if (_isCrossChain()) {
|
||||
_checkRole(_crossChainRoleAlias(role), _crossChainSender());
|
||||
} else {
|
||||
super._checkRole(role);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the aliased role corresponding to `role`.
|
||||
*/
|
||||
function _crossChainRoleAlias(bytes32 role) internal pure virtual returns (bytes32) {
|
||||
return role ^ CROSSCHAIN_ALIAS;
|
||||
}
|
||||
}
|
||||
383
contracts/access/AccessControlDefaultAdminRules.sol
Normal file
383
contracts/access/AccessControlDefaultAdminRules.sol
Normal file
@ -0,0 +1,383 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol)
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./AccessControl.sol";
|
||||
import "./IAccessControlDefaultAdminRules.sol";
|
||||
import "../utils/math/SafeCast.sol";
|
||||
import "../interfaces/IERC5313.sol";
|
||||
|
||||
/**
|
||||
* @dev Extension of {AccessControl} that allows specifying special rules to manage
|
||||
* the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions
|
||||
* over other roles that may potentially have privileged rights in the system.
|
||||
*
|
||||
* If a specific role doesn't have an admin role assigned, the holder of the
|
||||
* `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it.
|
||||
*
|
||||
* This contract implements the following risk mitigations on top of {AccessControl}:
|
||||
*
|
||||
* * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced.
|
||||
* * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account.
|
||||
* * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted.
|
||||
* * The delay can be changed by scheduling, see {changeDefaultAdminDelay}.
|
||||
* * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```solidity
|
||||
* contract MyToken is AccessControlDefaultAdminRules {
|
||||
* constructor() AccessControlDefaultAdminRules(
|
||||
* 3 days,
|
||||
* msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder
|
||||
* ) {}
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* _Available since v4.9._
|
||||
*/
|
||||
abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl {
|
||||
// pending admin pair read/written together frequently
|
||||
address private _pendingDefaultAdmin;
|
||||
uint48 private _pendingDefaultAdminSchedule; // 0 == unset
|
||||
|
||||
uint48 private _currentDelay;
|
||||
address private _currentDefaultAdmin;
|
||||
|
||||
// pending delay pair read/written together frequently
|
||||
uint48 private _pendingDelay;
|
||||
uint48 private _pendingDelaySchedule; // 0 == unset
|
||||
|
||||
/**
|
||||
* @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address.
|
||||
*/
|
||||
constructor(uint48 initialDelay, address initialDefaultAdmin) {
|
||||
require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin");
|
||||
_currentDelay = initialDelay;
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {IERC165-supportsInterface}.
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
||||
return interfaceId == type(IAccessControlDefaultAdminRules).interfaceId || super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {IERC5313-owner}.
|
||||
*/
|
||||
function owner() public view virtual returns (address) {
|
||||
return defaultAdmin();
|
||||
}
|
||||
|
||||
///
|
||||
/// Override AccessControl role management
|
||||
///
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
|
||||
*/
|
||||
function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
|
||||
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly grant default admin role");
|
||||
super.grantRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
|
||||
*/
|
||||
function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
|
||||
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly revoke default admin role");
|
||||
super.revokeRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-renounceRole}.
|
||||
*
|
||||
* For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling
|
||||
* {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule
|
||||
* has also passed when calling this function.
|
||||
*
|
||||
* After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions.
|
||||
*
|
||||
* NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin},
|
||||
* thereby disabling any functionality that is only available for it, and the possibility of reassigning a
|
||||
* non-administrated role.
|
||||
*/
|
||||
function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
|
||||
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
|
||||
(address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin();
|
||||
require(
|
||||
newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule),
|
||||
"AccessControl: only can renounce in two delayed steps"
|
||||
);
|
||||
delete _pendingDefaultAdminSchedule;
|
||||
}
|
||||
super.renounceRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-_grantRole}.
|
||||
*
|
||||
* For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the
|
||||
* role has been previously renounced.
|
||||
*
|
||||
* NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE`
|
||||
* assignable again. Make sure to guarantee this is the expected behavior in your implementation.
|
||||
*/
|
||||
function _grantRole(bytes32 role, address account) internal virtual override {
|
||||
if (role == DEFAULT_ADMIN_ROLE) {
|
||||
require(defaultAdmin() == address(0), "AccessControl: default admin already granted");
|
||||
_currentDefaultAdmin = account;
|
||||
}
|
||||
super._grantRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-_revokeRole}.
|
||||
*/
|
||||
function _revokeRole(bytes32 role, address account) internal virtual override {
|
||||
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
|
||||
delete _currentDefaultAdmin;
|
||||
}
|
||||
super._revokeRole(role, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`.
|
||||
*/
|
||||
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override {
|
||||
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't violate default admin rules");
|
||||
super._setRoleAdmin(role, adminRole);
|
||||
}
|
||||
|
||||
///
|
||||
/// AccessControlDefaultAdminRules accessors
|
||||
///
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function defaultAdmin() public view virtual returns (address) {
|
||||
return _currentDefaultAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) {
|
||||
return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function defaultAdminDelay() public view virtual returns (uint48) {
|
||||
uint48 schedule = _pendingDelaySchedule;
|
||||
return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) {
|
||||
schedule = _pendingDelaySchedule;
|
||||
return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) {
|
||||
return 5 days;
|
||||
}
|
||||
|
||||
///
|
||||
/// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin
|
||||
///
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
_beginDefaultAdminTransfer(newAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {beginDefaultAdminTransfer}.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*/
|
||||
function _beginDefaultAdminTransfer(address newAdmin) internal virtual {
|
||||
uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay();
|
||||
_setPendingDefaultAdmin(newAdmin, newSchedule);
|
||||
emit DefaultAdminTransferScheduled(newAdmin, newSchedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
_cancelDefaultAdminTransfer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {cancelDefaultAdminTransfer}.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*/
|
||||
function _cancelDefaultAdminTransfer() internal virtual {
|
||||
_setPendingDefaultAdmin(address(0), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function acceptDefaultAdminTransfer() public virtual {
|
||||
(address newDefaultAdmin, ) = pendingDefaultAdmin();
|
||||
require(_msgSender() == newDefaultAdmin, "AccessControl: pending admin must accept");
|
||||
_acceptDefaultAdminTransfer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {acceptDefaultAdminTransfer}.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*/
|
||||
function _acceptDefaultAdminTransfer() internal virtual {
|
||||
(address newAdmin, uint48 schedule) = pendingDefaultAdmin();
|
||||
require(_isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: transfer delay not passed");
|
||||
_revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin());
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
|
||||
delete _pendingDefaultAdmin;
|
||||
delete _pendingDefaultAdminSchedule;
|
||||
}
|
||||
|
||||
///
|
||||
/// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay
|
||||
///
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
_changeDefaultAdminDelay(newDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {changeDefaultAdminDelay}.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*/
|
||||
function _changeDefaultAdminDelay(uint48 newDelay) internal virtual {
|
||||
uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay);
|
||||
_setPendingDelay(newDelay, newSchedule);
|
||||
emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc IAccessControlDefaultAdminRules
|
||||
*/
|
||||
function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
_rollbackDefaultAdminDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {rollbackDefaultAdminDelay}.
|
||||
*
|
||||
* Internal function without access restriction.
|
||||
*/
|
||||
function _rollbackDefaultAdminDelay() internal virtual {
|
||||
_setPendingDelay(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the amount of seconds to wait after the `newDelay` will
|
||||
* become the new {defaultAdminDelay}.
|
||||
*
|
||||
* The value returned guarantees that if the delay is reduced, it will go into effect
|
||||
* after a wait that honors the previously set delay.
|
||||
*
|
||||
* See {defaultAdminDelayIncreaseWait}.
|
||||
*/
|
||||
function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) {
|
||||
uint48 currentDelay = defaultAdminDelay();
|
||||
|
||||
// When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up
|
||||
// to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day
|
||||
// to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new
|
||||
// delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like
|
||||
// using milliseconds instead of seconds.
|
||||
//
|
||||
// When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees
|
||||
// that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled.
|
||||
// For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days.
|
||||
return
|
||||
newDelay > currentDelay
|
||||
? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48
|
||||
: currentDelay - newDelay;
|
||||
}
|
||||
|
||||
///
|
||||
/// Private setters
|
||||
///
|
||||
|
||||
/**
|
||||
* @dev Setter of the tuple for pending admin and its schedule.
|
||||
*
|
||||
* May emit a DefaultAdminTransferCanceled event.
|
||||
*/
|
||||
function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private {
|
||||
(, uint48 oldSchedule) = pendingDefaultAdmin();
|
||||
|
||||
_pendingDefaultAdmin = newAdmin;
|
||||
_pendingDefaultAdminSchedule = newSchedule;
|
||||
|
||||
// An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted.
|
||||
if (_isScheduleSet(oldSchedule)) {
|
||||
// Emit for implicit cancellations when another default admin was scheduled.
|
||||
emit DefaultAdminTransferCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Setter of the tuple for pending delay and its schedule.
|
||||
*
|
||||
* May emit a DefaultAdminDelayChangeCanceled event.
|
||||
*/
|
||||
function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private {
|
||||
uint48 oldSchedule = _pendingDelaySchedule;
|
||||
|
||||
if (_isScheduleSet(oldSchedule)) {
|
||||
if (_hasSchedulePassed(oldSchedule)) {
|
||||
// Materialize a virtual delay
|
||||
_currentDelay = _pendingDelay;
|
||||
} else {
|
||||
// Emit for implicit cancellations when another delay was scheduled.
|
||||
emit DefaultAdminDelayChangeCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
_pendingDelay = newDelay;
|
||||
_pendingDelaySchedule = newSchedule;
|
||||
}
|
||||
|
||||
///
|
||||
/// Private helpers
|
||||
///
|
||||
|
||||
/**
|
||||
* @dev Defines if an `schedule` is considered set. For consistency purposes.
|
||||
*/
|
||||
function _isScheduleSet(uint48 schedule) private pure returns (bool) {
|
||||
return schedule != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Defines if an `schedule` is considered passed. For consistency purposes.
|
||||
*/
|
||||
function _hasSchedulePassed(uint48 schedule) private view returns (bool) {
|
||||
return schedule < block.timestamp;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user