Address Contract Partially Verified
Address
0x10AC0b33e1C4501CF3ec1cB1AE51ebfdbd2d4698
Balance
0 ETH
Nonce
16
Code Size
21772 bytes
Creator
Create2 Deployer at tx 0xa8002229...fb7611
Indexed Transactions
0
Contract Bytecode
21772 bytes
0x608060405234801561000f575f5ffd5b5060043610610416575f3560e01c806381ed8df411610221578063bda262d71161012a578063d3f6b598116100b4578063f00e2f8811610084578063f00e2f8814610b09578063f301061d14610b13578063f3cd2a3314610b3a578063f430b32814610b4d578063f5297a4714610b60575f5ffd5b8063d3f6b59814610a9e578063d4eb908314610ab1578063d5f3cd8614610ac4578063eb91a9b214610ad7575f5ffd5b8063c0c3bbd8116100fa578063c0c3bbd8146109f4578063c25b784f14610a07578063c27bda9b14610a32578063c99d2c8914610a45578063ceb7b7a314610a70575f5ffd5b8063bda262d714610986578063bea3edb114610999578063bef02b8c146109ac578063c06a02e8146109bf575f5ffd5b80639ab4e37b116101ab578063a5256bf21161017b578063a5256bf2146108f0578063a618940f146108f8578063a8485b731461092b578063b0e5173b14610960578063bd579e5514610973575f5ffd5b80639ab4e37b146108ac5780639dc0ad84146108b75780639e7b8c3a146108ca5780639e87cb3f146108dd575f5ffd5b80638e9615c9116101f15780638e9615c91461084b57806392352ed2146108535780639249c3921461087e57806394459ea41461089157806398b0050414610899575f5ffd5b806381ed8df4146107dd57806383aa6836146107e857806386853dfd146107fb578063888e3cde14610838575f5ffd5b806339c7d246116103235780635f4bc1be116102ad5780637825784b1161027d5780637825784b146107675780637911232a1461077a5780637ac432ff1461078d5780637be3d10f146107a05780637e8c1517146107a8575f5ffd5b80635f4bc1be146106fb5780635f853d40146107215780636cbd5796146107345780636fe7965214610754575f5ffd5b806340bb67e1116102f357806340bb67e1146106a757806344648c76146106ba578063464229b4146106cd5780635558c3cc146106e05780635e12b2db146106e8575f5ffd5b806339c7d2461461063a5780633a60c386146106615780633ab973a3146106695780633c24802914610694575f5ffd5b80631a351d62116103a45780631f9b5aaf116103745780631f9b5aaf14610598578063272b7add146105bf57806328f36ff0146105d25780632a90a055146105ea5780632b4bb841146105f2575f5ffd5b80631a351d62146104f25780631a63b6fb1461051e5780631aef3510146105315780631de18ae614610559575f5ffd5b80630a48d243116103ea5780630a48d2431461045a5780630c9431b5146104625780630ff323a3146104b757806312e1ac5a146104ca578063154fd23f146104dd575f5ffd5b8062ee68ba1461041a57806305027eee1461042f57806307cbebc11461044a57806308db890114610452575b5f5ffd5b61042d610428366004614b6c565b610b73565b005b6104375f5481565b6040519081526020015b60405180910390f35b610437610c15565b610437600581565b61042d610c25565b610498610470366004614b96565b6001600160a01b039081165f9081526001602052604090206007810154600990910154911691565b604080516001600160a01b039093168352602083019190915201610441565b61042d6104c5366004614b96565b610cb4565b61042d6104d8366004614bb1565b610d7e565b6104e5610efc565b6040516104419190614bd1565b610437610500366004614b96565b6001600160a01b03165f908152600160208190526040909120015490565b61043761052c366004614b96565b610f08565b61043761053f366004614b96565b6001600160a01b03165f9081526002602052604090205490565b6105807f0000000000000000000000008982cf48eab6616f2892888410afad9b0cd2bc9b81565b6040516001600160a01b039091168152602001610441565b6105807f000000000000000000000000303834cd8681bd6bd31ce7508822b12e2f38d9f281565b61042d6105cd366004614c1c565b610f32565b6105da610fb6565b6040519015158152602001610441565b600b54610437565b610625610600366004614b96565b6001600160a01b03165f908152600160205260409020600b810154600c909101549091565b60408051928352602083019190915201610441565b6105807f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d481565b600f54610437565b610437610677366004614b96565b6001600160a01b03165f9081526001602052604090206002015490565b61042d6106a2366004614c33565b610fcd565b61042d6106b5366004614b96565b611099565b6105806106c8366004614c1c565b611175565b61042d6106db366004614b96565b6111b0565b6104376112a2565b61042d6106f6366004614b6c565b6112ad565b610625610709366004614b96565b60036020525f90815260409020805460019091015482565b6104e561072f366004614b96565b611380565b610747610742366004614c6a565b6113a6565b6040516104419190614d27565b61042d610762366004614b6c565b611661565b61042d610775366004614b96565b611818565b61042d610788366004614b96565b61190a565b61042d61079b366004614b96565b611a24565b600a54610437565b6105da6107b6366004614b96565b6001600160a01b03165f90815260016020526040902060040154600160a01b900460ff1690565b600e5460ff166105da565b6105da6107f6366004614c33565b611b17565b61080e610809366004614d39565b611b3b565b604080516001600160a01b0394851681529284166020840152921691810191909152606001610441565b61042d610846366004614b96565b61227d565b6104e56124e4565b610437610861366004614b96565b6001600160a01b03165f9081526001602052604090206011015490565b61042d61088c366004614b96565b6124f0565b610437612777565b61042d6108a7366004614b96565b612782565b600c54600d54610625565b6105806108c5366004614c1c565b61286e565b6105da6108d8366004614d86565b6128a3565b6105da6108eb366004614c33565b61299a565b6104e56129d4565b610625610906366004614b96565b6001600160a01b03165f908152600160205260409020600f8101546010909101549091565b6105da610939366004614b96565b6001600160a01b03165f90815260016020526040902060040154600160b01b900460ff1690565b61042d61096e366004614c1c565b6129e0565b61042d610981366004614dd2565b612a77565b610580610994366004614c1c565b612da7565b61042d6109a7366004614dd2565b612ddc565b6104376109ba366004614b96565b612f36565b6105da6109cd366004614b96565b6001600160a01b03165f90815260016020526040902060040154600160a81b900460ff1690565b61042d610a02366004614c33565b612f5d565b610437610a15366004614b96565b6001600160a01b03165f9081526001602052604090206003015490565b61042d610a40366004614e1a565b6130d2565b610437610a53366004614b96565b6001600160a01b03165f908152600160205260409020600a015490565b610580610a7e366004614b96565b6001600160a01b039081165f908152600160205260409020600401541690565b61042d610aac366004614b96565b613452565b61042d610abf366004614c1c565b61351d565b61042d610ad2366004614e58565b613708565b6105da610ae5366004614c33565b6001600160a01b039081165f90815260016020526040902060040154811691161490565b6104376201518081565b6105807f000000000000000000000000770abd170404b8ed8182c04f380e567e647b457d81565b61042d610b48366004614b6c565b6137b1565b61042d610b5b366004614f4c565b61389f565b61042d610b6e366004615015565b6139e0565b81610b7d81613ad6565b6001600160a01b038381165f90815260016020526040902060040154163314610bb95760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383165f8181526001602052604090819020600d01849055517fc632e59fe65d75ed53f1e4c619a5cc8b5015d1e9e1db3be7e123adf8b975f06e90610c089085815260200190565b60405180910390a2505050565b5f610c206004613b01565b905090565b600c5480610c465760405163041887bd60e51b815260040160405180910390fd5b600d54421015610c695760405163a09190e160e01b815260040160405180910390fd5b600b8054908290555f600d819055600c5560405182815281907f0360da73fad480d9a31212f8337d4a576e6b9749d68fc663acf171bb07f950ba906020015b60405180910390a25050565b80610cbe81613ad6565b610cc8338361299a565b610ce55760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0382165f90815260016020526040902060040154600160a81b900460ff16610d2757604051633f9da5d160e21b815260040160405180910390fd5b6001600160a01b0382165f8181526001602052604080822060048101805460ff60a81b1916905542601190910155517f9de75c520457842eb5fe159114348bf5358100a1acaf1bcb5052f64f347503df9190a25050565b33610d8881613ad6565b335f8181526001602052604090206004810154600160a81b900460ff1615610dc3576040516314c45ee560e21b815260040160405180910390fd5b6004810154600160a01b900460ff1615610df05760405163d3cfa32160e01b815260040160405180910390fd5b8054808610610e12576040516305e3345d60e41b815260040160405180910390fd5b855f03610e32576040516333bb44cb60e21b815260040160405180910390fd5b5f610e50610e446113ec612710615055565b83906127106001613b0a565b905080871015610e7357604051631edf387160e21b815260040160405180910390fd5b855f03610e93576040516333bb44cb60e21b815260040160405180910390fd5b868355426001840181905560408051848152602081018a905290810188905260608101919091526001600160a01b038516907f482a01d8e596a883e67895d310f80b2c151a445aa5e277325a6499b0be869845906080015b60405180910390a250505050505050565b6060610c206008613b55565b5f81610f1381613ad6565b50506001600160a01b03165f908152600160205260409020600d015490565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d41614610f7b5760405163177c8a7760e21b815260040160405180910390fd5b600a8190556040518181527f132309f91d275ae9cafe2088eea8945ed3b52dac1012d3be4a6e44622488a4f69060200160405180910390a150565b5f600d544210158015610c20575050600b54151590565b81610fd781613ad6565b6001600160a01b038381165f908152600160205260409020600401541633146110135760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383165f9081526001602052604090206110379060050183613b61565b611054576040516307f75fd560e11b815260040160405180910390fd5b816001600160a01b0316836001600160a01b03167f466dd5513a82de99caf12b8d5176cd2174dec8013f9c3ec7be7254e54b2c417c60405160405180910390a3505050565b806110a381613ad6565b6001600160a01b038281165f908152600160205260409020600401541633146110df5760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0382165f90815260016020526040812060100154900361111957604051630e46e8c160e41b815260040160405180910390fd5b6001600160a01b0382165f818152600160209081526040808320600f8101805490859055601090910193909355518281529192917f03ed1b00c79644c2f1066b475c92818992a014d513078a5fd3305c491b28dc679101610c08565b5f6111806004613b01565b821061119f57604051632f8e260760e11b815260040160405180910390fd5b6111aa600483613b75565b92915050565b806111ba81613ad6565b6001600160a01b038281165f908152600160205260409020600401541633146111f65760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b038281165f908152600160205260409020600701541661123057604051633b330f3760e11b815260040160405180910390fd5b6001600160a01b038083165f818152600160205260408082206007810180546001600160a01b0319808216909255600883018054909216909155600990910183905590519316928392917f12a87226e0c76047d6fef7f70bd906fa61a48e44cf4151617f9a7adecda4f47991a3505050565b5f610c206008613b01565b816112b781613ad6565b6001600160a01b038381165f908152600160205260409020600401541633146112f35760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383165f908152600160205260408120600b01839055600a5461131d9042615068565b6001600160a01b0385165f81815260016020908152604091829020600c01849055815187815290810184905292935033927f3c67f914ba911b08519fae976b442675afa7d36b96bab1276ca2ab142c1b171491015b60405180910390a350505050565b6001600160a01b0381165f9081526001602052604090206060906111aa90600501613b55565b60408051608081018252600e5460ff90811615158252600b546020808401919091526001600160a01b0387165f81815260018084528682206004810154600160b01b90049095161515968601969096525292909252600a909101546060828101919091528151909183918061141c575080604001515b1561146c57816001600160401b0381111561143957611439614e73565b604051908082528060200260200182016040528015611462578160200160208202803683370190505b509250505061165a565b816001600160401b0381111561148457611484614e73565b6040519080825280602002602001820160405280156114ad578160200160208202803683370190505b5092505f5b828110156116565761155c8686838181106114cf576114cf61507b565b90506020028101906114e1919061508f565b6114ef906020810190614b96565b8787848181106115015761150161507b565b9050602002810190611513919061508f565b6115219060208101906150ad565b8989868181106115335761153361507b565b9050602002810190611545919061508f565b6115539060408101906150f6565b6001888e613b80565b1561158a5760018482815181106115755761157561507b565b9115156020928302919091019091015261164e565b61162b86868381811061159f5761159f61507b565b90506020028101906115b1919061508f565b6115bf906020810190614b96565b8787848181106115d1576115d161507b565b90506020028101906115e3919061508f565b6115f19060208101906150ad565b8989868181106116035761160361507b565b9050602002810190611615919061508f565b6116239060608101906150f6565b5f888e613b80565b84828151811061163d5761163d61507b565b911515602092830291909101909101525b6001016114b2565b5050505b9392505050565b8161166b81613ad6565b815f0361168b57604051630f6fa54560e41b815260040160405180910390fd5b5f7f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b03166321f8a7217f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b0316637b7630666040518163ffffffff1660e01b8152600401602060405180830381865afa158015611717573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061173b919061513b565b6040518263ffffffff1660e01b815260040161175991815260200190565b602060405180830381865afa158015611774573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117989190615152565b90506117af6001600160a01b038216333086613cce565b6001600160a01b0384165f90815260026020526040812080548592906117d6908490615068565b909155505060405183815233906001600160a01b038616907f0af030417f3b88e41ea1b8c5905f05884e87e393e129c5e207896f8a0e29b15a90602001611372565b8061182281613ad6565b6001600160a01b0382165f908152600160205260409020600b01548061185b57604051633b330f3760e11b815260040160405180910390fd5b6001600160a01b0383165f908152600160205260409020600c01544210156118965760405163a09190e160e01b815260040160405180910390fd5b6001600160a01b0383165f818152600160209081526040808320600a8101805490879055600b8201859055600c9091019390935580518381529182018590529192917f50db1dc8f2cc634e637edbe632acec34656994c29a699c817c89e8de694035f691015b60405180910390a250505050565b8061191481613ad6565b6001600160a01b038281165f908152600160205260409020600401541633146119505760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0382165f908152600260205260408120549081900361198957604051630f6fa54560e41b815260040160405180910390fd5b604051806040016040528082815260200162015180426119a99190615068565b90526001600160a01b0384165f8181526003602090815260409091208351815592015160019092019190915533907f9609899ca39d06bc75b43075ca3e965886967fd4cd3ec9d86aec014aaa5169db83611a066201518042615068565b604080519283526020830191909152015b60405180910390a3505050565b80611a2e81613ad6565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d41614611a775760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0382165f8181526001602052604090819020549051630401465d60e31b81526004810182905290919063200a32e8906024015f604051808303815f87803b158015611ac7575f5ffd5b505af1158015611ad9573d5f5f3e3d5ffd5b50506040518392506001600160a01b03861691507f38e30977945dee262f7d3e5f9f9ece987d5ecad1b6889f39dfcf011923dbdf2b905f90a3505050565b6001600160a01b0381165f90815260016020526040812061165a9060050184613d35565b5f808080611b4c6020860186614b96565b6001600160a01b03161480611b7857505f611b6d6080860160608701614b96565b6001600160a01b0316145b80611b9c57505f611b9161014086016101208701614b96565b6001600160a01b0316145b15611bba5760405163538ba4f960e01b815260040160405180910390fd5b611bc760208501856150ad565b15905080611be15750611bdd60408501856150ad565b1590505b15611bff576040516330b3d5b160e11b815260040160405180910390fd5b611c2060405180606001604052805f81526020015f81526020015f81525090565b600f8054905f611c2f8361516d565b90915550815233611c436020870187614b96565b611c5060208801886150ad565b611c5d60408a018a6150ad565b8651604051611c7597969594939291906020016151ad565b60408051601f198184030181529190528051602091820120908201819052611cc7906001600160a01b037f000000000000000000000000303834cd8681bd6bd31ce7508822b12e2f38d9f21690613d56565b9350611d0981602001517f0000000000000000000000008982cf48eab6616f2892888410afad9b0cd2bc9b6001600160a01b0316613d5690919063ffffffff16565b9150611d4b81602001517f000000000000000000000000770abd170404b8ed8182c04f380e567e647b457d6001600160a01b0316613d5690919063ffffffff16565b92506001600160a01b038416636cf1dbed611d696020880188614b96565b611d7660208901896150ad565b611d8360408b018b6150ad565b89896040518863ffffffff1660e01b8152600401611da79796959493929190615201565b5f604051808303815f87803b158015611dbe575f5ffd5b505af1158015611dd0573d5f5f3e3d5ffd5b505060405163189acdbd60e31b81526001600160a01b0387811660048301528516925063c4d66de891506024015f604051808303815f87803b158015611e14575f5ffd5b505af1158015611e26573d5f5f3e3d5ffd5b50506040516364ee8c0f60e11b81526001600160a01b038616925063c9dd181e9150611e5b90879060e08a0190600401615260565b5f604051808303815f87803b158015611e72575f5ffd5b505af1158015611e84573d5f5f3e3d5ffd5b50505050611e9c846004613d6290919063ffffffff16565b50611ea8600684613d62565b50611eb4600883613d62565b505f80611ed5611ec76020890189614b96565b6001600160a01b0316613d76565b9150915081611ef7576040516333bb44cb60e21b815260040160405180910390fd5b611f0281600a615390565b8360400181815250507f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b03166329f059766040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f67573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f8b919061513b565b8760c001351015611faf57604051633c04450760e21b815260040160405180910390fd5b8660c001358760a0013510611fd7576040516330b3d5b160e11b815260040160405180910390fd5b6040808401516001600160a01b0387165f90815260016020819052929020908155429181019190915560a0880135600282015560c08801356003820155600401805460ff60a81b191690556120326080880160608901614b96565b6001600160a01b038681165f90815260016020526040812060040180546001600160a01b0319169390921692909217905561207060808901896150f6565b91505060058111156120955760405163e232ecaf60e01b815260040160405180910390fd5b5f5b81811015612184575f6120ad60808b018b6150f6565b838181106120bd576120bd61507b565b90506020020160208101906120d29190614b96565b90506001600160a01b0381166120fb5760405163538ba4f960e01b815260040160405180910390fd5b6001600160a01b038881165f9081526001602052604090206004015481831691160361213a57604051634c683a1560e11b815260040160405180910390fd5b6001600160a01b0388165f90815260016020526040902061215e9060050182613d62565b61217b576040516322f10c1d60e11b815260040160405180910390fd5b50600101612097565b506001600160a01b038087165f8181526001602090815260409091206706f05b59d3b20000600d909101558651928a16907fb71e4c3b886bfa372037021505c466d28e41fc077044f0f8be29eeff137133479089906121e5908e018e614b96565b6121f260208f018f6150ad565b8f806040019061220291906150ad565b6040516122149695949392919061539e565b60405180910390a46040848101516001600160a01b0388165f81815260016020818152918590200154845193845290830152917fdacecd2f542e0332ebba232014aeb2099cc06386862bc8d88d8446b826382ece910160405180910390a2505050509193909250565b8061228781613ad6565b6001600160a01b0382165f9081526003602090815260408083208151808301909252805482526001015491810182905291036122d6576040516320f49eb160e21b815260040160405180910390fd5b80602001514210156122fb57604051633c96184760e01b815260040160405180910390fd5b6001600160a01b0383165f9081526002602052604081205482519091908210612325578251612327565b815b9050805f0361234957604051630f6fa54560e41b815260040160405180910390fd5b6001600160a01b038086165f9081526003602090815260408083208381556001018390558051633dbb183360e11b8152905192937f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d416926321f8a721928492637b76306692600480830193928290030181865afa1580156123cc573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123f0919061513b565b6040518263ffffffff1660e01b815260040161240e91815260200190565b602060405180830381865afa158015612429573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061244d9190615152565b6001600160a01b038088165f9081526002602090815260408083208054889003905560019091529020600401549192509081169061248e9083168285613e74565b806001600160a01b0316876001600160a01b03167f5e02dcaf3d3c04e0d6054134d489d13868467dbcff5930e7dd0d5cdd9ed59443856040516124d391815260200190565b60405180910390a350505050505050565b6060610c206006613b55565b806124fa81613ad6565b6001600160a01b038281165f908152600160205260409020600701541661253457604051633b330f3760e11b815260040160405180910390fd5b6001600160a01b0382165f9081526001602052604090206009015442101561256f5760405163089cf88f60e21b815260040160405180910390fd5b6001600160a01b038083165f908152600160205260409020600781015460089091015490821691168115806125ab57506001600160a01b038116155b156125c95760405163538ba4f960e01b815260040160405180910390fd5b6001600160a01b038085165f9081526001602052604090206004810154909116906125f690600501613ea5565b6001600160a01b0385165f908152600360205260409020600101541561265d576001600160a01b0385165f81815260036020526040808220828155600101829055517f8076f2f1c856d087aea9a80a6ee119648f2b95063efe6acf798797367d37210c9190a25b6001600160a01b038581165f81815260016020526040808220600b8101839055600c8101839055600f81018390556010810192909255600491820180546001600160a01b03191688861617905551632360407160e01b815292851690830152906323604071906024015f604051808303815f87803b1580156126dd575f5ffd5b505af11580156126ef573d5f5f3e3d5ffd5b505050506001600160a01b038581165f8181526001602090815260408083206007810180546001600160a01b03199081169091556008820180549091169055600901929092559051858416815286841693851692917f7e62e1ec80a87937e5d40f5a5e42a516befc032971f2b16e0dc1839c9b08b5b791015b60405180910390a45050505050565b5f610c206006613b01565b8061278c81613ad6565b6001600160a01b0382165f9081526001602052604081206010015490036127c657604051630e46e8c160e41b815260040160405180910390fd5b6001600160a01b0382165f908152600160205260409020601001544210156128015760405163089cf88f60e21b815260040160405180910390fd5b6001600160a01b0382165f818152600160209081526040808320600f81018054600283018054928790556010909301959095559084905581518181529283018490529293917ffdbb1b47608591d414ef3f42c654d856dc78d11a78b029627cc9fff1645d2a5691016118fc565b5f6128796006613b01565b821061289857604051632f8e260760e11b815260040160405180910390fd5b6111aa600683613b75565b60408051608081018252600e5460ff90811615158252600b546020808401919091526001600160a01b0386165f81815260018084528682206004810154600160b01b9004909516151596860196909652908152939052600a01546060820152805180612910575080604001515b1561291e575f9150506111aa565b61295161292e6020850185614b96565b61293b60208601866150ad565b61294860408801886150f6565b6001878b613b80565b156129605760019150506111aa565b6129926129706020850185614b96565b61297d60208601866150ad565b61298a60608801886150f6565b5f878b613b80565b949350505050565b6001600160a01b038082165f9081526001602052604081206004810154919290918116908516148061299257506129926005820185613d35565b6060610c206004613b55565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d41614612a295760405163177c8a7760e21b815260040160405180910390fd5b600c819055600a545f90612a3d9042615068565b600d81905560405181815290915082907f71e72baaa2bffcb51d34de71bba7ea258c9c5667a86e69b1c684f0e1ecb4f39590602001610ca8565b82612a8181613ad6565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d41614612aca5760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383161580612ae757506001600160a01b038216155b15612b055760405163538ba4f960e01b815260040160405180910390fd5b6001600160a01b038085165f90815260016020526040902060040154811690841603612b44576040516322f10c1d60e11b815260040160405180910390fd5b6001600160a01b038085165f90815260016020526040812060048101546007820180546001600160a01b0319908116909155600983018490556008830180549091169055600b8201839055600c8201839055600f82018390556010820183905590921691612bb490600501613b55565b90505f5b8151811015612c7457612c12828281518110612bd657612bd661507b565b602002602001015160015f8a6001600160a01b03166001600160a01b031681526020019081526020015f20600501613b6190919063ffffffff16565b50818181518110612c2557612c2561507b565b60200260200101516001600160a01b0316876001600160a01b03167f466dd5513a82de99caf12b8d5176cd2174dec8013f9c3ec7be7254e54b2c417c60405160405180910390a3600101612bb8565b506001600160a01b0386165f9081526003602052604090206001015415612cdc576001600160a01b0386165f81815260036020526040808220828155600101829055517f8076f2f1c856d087aea9a80a6ee119648f2b95063efe6acf798797367d37210c9190a25b6001600160a01b038681165f8181526001602052604090819020600490810180546001600160a01b0319168a86161790559051632360407160e01b815292871690830152906323604071906024015f604051808303815f87803b158015612d41575f5ffd5b505af1158015612d53573d5f5f3e3d5ffd5b50506040516001600160a01b038781168252808916935085811692508916907f7e62e1ec80a87937e5d40f5a5e42a516befc032971f2b16e0dc1839c9b08b5b79060200160405180910390a4505050505050565b5f612db26008613b01565b8210612dd157604051632f8e260760e11b815260040160405180910390fd5b6111aa600883613b75565b82612de681613ad6565b6001600160a01b0384165f908152600160205260409020612e0a9060050133613d35565b612e275760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383161580612e4457506001600160a01b038216155b15612e625760405163538ba4f960e01b815260040160405180910390fd5b6001600160a01b038085165f90815260016020526040902060040154811690841603612ea1576040516322f10c1d60e11b815260040160405180910390fd5b5f612eaf62093a8042615068565b6001600160a01b038681165f818152600160209081526040918290206007810180548b87166001600160a01b03199182168117909255600883018054978c1697909116871790556009909101869055825194855290840185905293945033927ffb67e6bbbfba8245b27d33889b32a324c5cdc2b0c52d738d9e65642d770a21649101612768565b5f81612f4181613ad6565b50506001600160a01b03165f9081526001602052604090205490565b81612f6781613ad6565b6001600160a01b038381165f90815260016020526040902060040154163314612fa35760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b038216612fca5760405163538ba4f960e01b815260040160405180910390fd5b6001600160a01b038381165f9081526001602052604090206004015481841691160361300957604051634c683a1560e11b815260040160405180910390fd5b6001600160a01b0383165f90815260016020526040902060059061302e908201613b01565b1061304c5760405163e232ecaf60e01b815260040160405180910390fd5b6001600160a01b0383165f9081526001602052604090206130709060050183613d62565b61308d576040516322f10c1d60e11b815260040160405180910390fd5b816001600160a01b0316836001600160a01b03167fb62a858ba4f3ba693ebca539c91a5c78f4514d4c1ea35b9d336ce5ce8f071f7660405160405180910390a3505050565b6130da613eae565b5f7f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b0316633ef150596040518163ffffffff1660e01b8152600401602060405180830381865afa158015613137573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061315b91906153ea565b90505f61316883806150f6565b905090505f5b8181101561344c575f61318185806150f6565b838181106131915761319161507b565b90506020020160208101906131a69190614b96565b90506131b3600682613d35565b6131f0576040516001600160a01b038216907fde5abf6fb8e50b9c44ca7d6b0b47660db6158640044348124a98dbb8c94d6524905f90a250613444565b5f6131fe60408701876150f6565b8481811061320e5761320e61507b565b9050602002013590504281111561326957604080518281524260208201526001600160a01b038416917f11ce21eb8049b15d3cb258f2a3e207e24b6ca2141010b9c063228faa8628b9b5910160405180910390a25050613444565b6001600160a01b0382165f9081526001602052604090206004810154600160a81b900460ff16156132cf576040516001600160a01b038416907ff3e21bd07cf4050faf914678b23f5d9a218123f79904e55a5fb41a41a7d057b0905f90a2505050613444565b60038101546132de8342615055565b1115613348576132f46080880160608901614b96565b6001600160a01b0316836001600160a01b03167f5413368ef0fd371d352762fd42f794381bdd66d2b2c3860549bc61f12f6ab2ba8460405161333891815260200190565b60405180910390a3505050613444565b5f86156133dd5760405163125e2c4f60e31b81523360048201527f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b0316906392f1627890602401602060405180830381865afa9250505080156133cf575060408051601f3d908101601f191682019092526133cc9181019061513b565b60015b6133da57505f6133dd565b90505b61343f6040518060a00160405280866001600160a01b03168152602001835f14151581526020018a806020019061341491906150f6565b898181106134245761342461507b565b90506020020135815260200185815260200183815250613f53565b505050505b60010161316e565b50505050565b8061345c81613ad6565b613466338361299a565b6134835760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0382165f90815260016020526040902060040154600160a81b900460ff16156134c65760405163064dc5f760e11b815260040160405180910390fd5b6001600160a01b0382165f81815260016020526040808220600401805461ffff60a01b191661010160a01b179055517fc2897a3765ea6cd9ed9ce463d3bc9c9cf968f21f8664b62684e1254d3b0f9ee59190a25050565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d41614613566576040516365bc1f8360e11b815260040160405180910390fd5b805f54101561358857604051637f06a4a760e01b815260040160405180910390fd5b805f5f8282546135989190615055565b925050819055505f7f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b03166321f8a7217f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b0316637b7630666040518163ffffffff1660e01b8152600401602060405180830381865afa15801561362b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061364f919061513b565b6040518263ffffffff1660e01b815260040161366d91815260200190565b602060405180830381865afa158015613688573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906136ac9190615152565b90505f6136b7614550565b90506136cd6001600160a01b0383168285613e74565b806001600160a01b03167f43214536812d90daa91186dfb744049f5e3c6f3379765892f0f41b204a87e0ee84604051610c0891815260200190565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d416146137515760405163177c8a7760e21b815260040160405180910390fd5b600e5481151560ff9091161515036137665750565b600e805460ff1916821515908117909155600b54604051918252907fd867c555762bc6e6e6c9f80aa6c8132ccc5909f83266bc4d24489c8aa10e5e8d9060200160405180910390a250565b816137bb81613ad6565b6001600160a01b038381165f908152600160205260409020600401541633146137f75760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383165f9081526001602052604090206003015482106138315760405163178c07bb60e11b815260040160405180910390fd5b5f61383f6203f48042615068565b6001600160a01b0385165f81815260016020908152604091829020600f8101889055601001849055815187815290810184905292935033927ffaabc7622c15965d088243dee3943358c4b09cacae32f9b23d8c19014711ea9b9101611372565b806138a981613ad6565b6001600160a01b038281165f908152600160205260409020600401541633146138e55760405163177c8a7760e21b815260040160405180910390fd5b8351835181146139085760405163be26e77f60e01b815260040160405180910390fd5b5f5b81811015613995578481815181106139245761392461507b565b602002602001015160015f866001600160a01b03166001600160a01b031681526020019081526020015f20600e015f8884815181106139655761396561507b565b60209081029190910181015182528101919091526040015f20805460ff191691151591909117905560010161390a565b50826001600160a01b03167f671df22165975c9055ff482eedb4963e75f35ac9f3040346699bd97228a8c79086866040516139d1929190615405565b60405180910390a25050505050565b816139ea81613ad6565b336001600160a01b037f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d41614613a335760405163177c8a7760e21b815260040160405180910390fd5b6001600160a01b0383165f9081526001602052604090206004015460ff600160b01b90910416151582151514613ad1576001600160a01b0383165f8181526001602052604090819020600481018054861515600160b01b0260ff60b01b19909116179055600a015490519091907f927440ad59e90a7b01af1effd4191955ea9e6ef812fc415e7f8e71eadbc5bc3590611a1790861515815260200190565b505050565b613ae1600682613d35565b613afe57604051638a525cdd60e01b815260040160405180910390fd5b50565b5f6111aa825490565b5f613b37613b178361465d565b8015613b3257505f8480613b2d57613b2d61545c565b868809115b151590565b613b42868686614689565b613b4c9190615068565b95945050505050565b60605f61165a83614739565b5f61165a836001600160a01b038416614792565b5f61165a8383614875565b5f8315613ba757825180613b9657506020830151155b15613ba257505f613cc2565b613bc5565b826040015180613bb957506060830151155b15613bc557505f613cc2565b5f613bd18a8a8a61489b565b90508415613c6d576001600160a01b0383165f908152600160209081526040808320848452600e0190915290205460ff1615613c10575f915050613cc2565b5f869003613c25576020840151149050613cc2565b613c658787808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152505050506020860151836148ed565b915050613cc2565b5f869003613c82576060840151149050613cc2565b613c658787808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152505050506060860151836148ed565b98975050505050505050565b6040516001600160a01b03848116602483015283811660448301526064820183905261344c9186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050614902565b6001600160a01b0381165f908152600183016020526040812054151561165a565b5f61165a83835f614973565b5f61165a836001600160a01b038416614a08565b5f5f826001600160a01b03163b5f03613da2576040516333bb44cb60e21b815260040160405180910390fd5b60408051600481526024810182526020810180516001600160e01b031663313ce56760e01b17905290515f9182916001600160a01b03871691613de491615470565b5f60405180830381855afa9150503d805f8114613e1c576040519150601f19603f3d011682016040523d82523d5f602084013e613e21565b606091505b5091509150818015613e3557506020815110155b15613e68575f81806020019051810190613e4f919061513b565b905060ff8111613e66576001969095509350505050565b505b505f9485945092505050565b6040516001600160a01b03838116602483015260448201839052613ad191859182169063a9059cbb90606401613d03565b613afe81614a54565b604051637eb787e160e11b81523360048201527f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b03169063fd6f0fc290602401602060405180830381865afa158015613f10573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613f3491906153ea565b613f515760405163d4ab126760e01b815260040160405180910390fd5b565b80516001600160a01b039081165f9081526001602081905260408083206002015485519094168352909120015460608301518110613fb8576040517f2e78c6ab27a29f5c1471d797dd0809527031b55c91aa70143ffa36ceecfab75f905f90a1505050565b82516001600160a01b03165f908152600160205260409020601101548015801590613fe7575080846060015111155b15614035578351606085015160408051918252602082018490526001600160a01b03909216917f253dc90db1f408af7684ca176fd3b27f8e0d92b39817e87c63a8d2cd312d8bce91016118fc565b828285606001516140469190615055565b101561407a576040517f53f7a6ee85aab0f20869755601a6424676aa3807e9ef249627fa11a39e9a0e0c905f90a150505050565b83516001600160a01b039081165f9081526001602052604080822054875190931682528120600d01549091905f19148015906140b557505f81115b80156140e4575085516001600160a01b03165f90815260016020526040902060040154600160a01b900460ff16155b156141c6575f818760400151116141095760408701516141049083615055565b614119565b8187604001516141199190615055565b90505f61412f82670de0b6b3a764000085614689565b88516001600160a01b03165f908152600160205260409020600d01549091508111156141c35760019350875f01516001600160a01b03167fe364669b114d8ecba48ed1742526a04c78dd506cd3af6beb910f6622a928cbdb6040516141ba906020808252601290820152712424a3a42fa82829afa222ab24a0aa24a7a760711b604082015260600190565b60405180910390a25b50505b85516001600160a01b03165f908152600260209081526040909120549087015161438f5786608001518110156142ef5786516001600160a01b039081165f908152600160205260408082206004908101805460ff60a81b1916600160a81b1790558a518416835281832001805460ff60a01b1916600160a01b179055895190519216917fc2897a3765ea6cd9ed9ce463d3bc9c9cf968f21f8664b62684e1254d3b0f9ee59190a286516040516001600160a01b03909116907f80796c8d1dc90790262881896e3680e577f032ca370f16b8dccd1cd4cef254f1905f90a2865160808801516040516001600160a01b039092169182917f9f17e0625b7890f465cad6295db4c1abad9d5afeea38a1d7d64f390aef73a770916124d391868252602082015260400190565b608087015187516001600160a01b03165f908152600260205260408120805490919061431c908490615055565b909155505060808701515f80548190614336908490615068565b9091555050865160808801515f5460408051928352602083018590528201526001600160a01b03909116907f70485ec1c1ebd5a2176ff9969ef90917bbeca387cb89fee9a5fac5840d7253d99060600160405180910390a25b828061439d57506040870151155b156144545786516001600160a01b039081165f908152600160205260408082206004908101805460ff60a81b1916600160a81b1790558a518416835281832001805460ff60a01b1916600160a01b179055895190519216917fc2897a3765ea6cd9ed9ce463d3bc9c9cf968f21f8664b62684e1254d3b0f9ee59190a286516040516001600160a01b03909116907f80796c8d1dc90790262881896e3680e577f032ca370f16b8dccd1cd4cef254f1905f90a2614547565b60408781015188516001600160a01b039081165f908152600160208190528482209390935560608b01518b51831682528482209093019290925589511681522060040154600160a01b900460ff16156144fc5786516001600160a01b039081165f90815260016020526040808220600401805460ff60a01b19169055895190519216917f87afbf71df10b23be48535f5f7689b97e949bf5b76a67c76e79fd0f10db005d89190a25b865160408089015160608a0151825191825260208201526001600160a01b03909216917fdacecd2f542e0332ebba232014aeb2099cc06386862bc8d88d8446b826382ece9101610eeb565b50505050505050565b5f7f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b03166321f8a7217f000000000000000000000000b5396ef2bf8ca360ceb4166b77afb2bed20e74d46001600160a01b0316636f2140c16040518163ffffffff1660e01b8152600401602060405180830381865afa1580156145dc573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190614600919061513b565b6040518263ffffffff1660e01b815260040161461e91815260200190565b602060405180830381865afa158015614639573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c209190615152565b5f600282600381111561467257614672615486565b61467c919061549a565b60ff166001149050919050565b5f5f5f6146968686614aad565b91509150815f036146ba578381816146b0576146b061545c565b049250505061165a565b8184116146d1576146d16003851502601118614ac9565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010185841190960395909502919093039390930492909217029150509392505050565b6060815f0180548060200260200160405190810160405280929190818152602001828054801561478657602002820191905f5260205f20905b815481526020019060010190808311614772575b50505050509050919050565b5f818152600183016020526040812054801561486c575f6147b4600183615055565b85549091505f906147c790600190615055565b9050808214614826575f865f0182815481106147e5576147e561507b565b905f5260205f200154905080875f0184815481106148055761480561507b565b5f918252602080832090910192909255918252600188019052604090208390555b8554869080614837576148376154c7565b600190038181905f5260205f20015f90559055856001015f8681526020019081526020015f205f9055600193505050506111aa565b5f9150506111aa565b5f825f01828154811061488a5761488a61507b565b905f5260205f200154905092915050565b5f8383836040516020016148b1939291906154db565b60408051601f19818403018152828252805160209182012090830152016040516020818303038152906040528051906020012090509392505050565b5f826148f98584614ada565b14949350505050565b5f5f60205f8451602086015f885af180614921576040513d5f823e3d81fd5b50505f513d91508115614938578060011415614945565b6001600160a01b0384163b155b1561344c57604051635274afe760e01b81526001600160a01b03851660048201526024015b60405180910390fd5b5f8147101561499e5760405163cf47918160e01b81524760048201526024810183905260440161496a565b763d602d80600a3d3981f3363d3d373d3d3d363d730000008460601b60e81c175f526e5af43d82803e903d91602b57fd5bf38460781b17602052826037600984f590506001600160a01b03811661165a5760405163b06ebf3d60e01b815260040160405180910390fd5b5f818152600183016020526040812054614a4d57508154600181810184555f8481526020808220909301849055845484825282860190935260409020919091556111aa565b505f6111aa565b5f614a5d825490565b90505f5b81811015614aa657826001015f845f018381548110614a8257614a8261507b565b905f5260205f20015481526020019081526020015f205f9055806001019050614a61565b50505f9055565b5f805f1983850993909202808410938190039390930393915050565b634e487b715f52806020526024601cfd5b5f81815b8451811015614b1457614b0a82868381518110614afd57614afd61507b565b6020026020010151614b1c565b9150600101614ade565b509392505050565b5f818310614b36575f82815260208490526040902061165a565b5f83815260208390526040902061165a565b6001600160a01b0381168114613afe575f5ffd5b8035614b6781614b48565b919050565b5f5f60408385031215614b7d575f5ffd5b8235614b8881614b48565b946020939093013593505050565b5f60208284031215614ba6575f5ffd5b813561165a81614b48565b5f5f60408385031215614bc2575f5ffd5b50508035926020909101359150565b602080825282518282018190525f918401906040840190835b81811015614c115783516001600160a01b0316835260209384019390920191600101614bea565b509095945050505050565b5f60208284031215614c2c575f5ffd5b5035919050565b5f5f60408385031215614c44575f5ffd5b8235614c4f81614b48565b91506020830135614c5f81614b48565b809150509250929050565b5f5f5f60408486031215614c7c575f5ffd5b8335614c8781614b48565b925060208401356001600160401b03811115614ca1575f5ffd5b8401601f81018613614cb1575f5ffd5b80356001600160401b03811115614cc6575f5ffd5b8660208260051b8401011115614cda575f5ffd5b939660209190910195509293505050565b5f8151808452602084019350602083015f5b82811015614d1d5781511515865260209586019590910190600101614cfd565b5093949350505050565b602081525f61165a6020830184614ceb565b5f60208284031215614d49575f5ffd5b81356001600160401b03811115614d5e575f5ffd5b8201610140818503121561165a575f5ffd5b5f60808284031215614d80575f5ffd5b50919050565b5f5f60408385031215614d97575f5ffd5b8235614da281614b48565b915060208301356001600160401b03811115614dbc575f5ffd5b614dc885828601614d70565b9150509250929050565b5f5f5f60608486031215614de4575f5ffd5b8335614def81614b48565b92506020840135614dff81614b48565b91506040840135614e0f81614b48565b809150509250925092565b5f60208284031215614e2a575f5ffd5b81356001600160401b03811115614e3f575f5ffd5b61299284828501614d70565b8015158114613afe575f5ffd5b5f60208284031215614e68575f5ffd5b813561165a81614e4b565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f191681016001600160401b0381118282101715614eaf57614eaf614e73565b604052919050565b5f6001600160401b03821115614ecf57614ecf614e73565b5060051b60200190565b5f82601f830112614ee8575f5ffd5b8135614efb614ef682614eb7565b614e87565b8082825260208201915060208360051b860101925085831115614f1c575f5ffd5b602085015b83811015614f42578035614f3481614e4b565b835260209283019201614f21565b5095945050505050565b5f5f5f60608486031215614f5e575f5ffd5b83356001600160401b03811115614f73575f5ffd5b8401601f81018613614f83575f5ffd5b8035614f91614ef682614eb7565b8082825260208201915060208360051b850101925088831115614fb2575f5ffd5b6020840193505b82841015614fd4578335825260209384019390910190614fb9565b955050505060208401356001600160401b03811115614ff1575f5ffd5b614ffd86828701614ed9565b92505061500c60408501614b5c565b90509250925092565b5f5f60408385031215615026575f5ffd5b823561503181614b48565b91506020830135614c5f81614e4b565b634e487b7160e01b5f52601160045260245ffd5b818103818111156111aa576111aa615041565b808201808211156111aa576111aa615041565b634e487b7160e01b5f52603260045260245ffd5b5f8235607e198336030181126150a3575f5ffd5b9190910192915050565b5f5f8335601e198436030181126150c2575f5ffd5b8301803591506001600160401b038211156150db575f5ffd5b6020019150368190038213156150ef575f5ffd5b9250929050565b5f5f8335601e1984360301811261510b575f5ffd5b8301803591506001600160401b03821115615124575f5ffd5b6020019150600581901b36038213156150ef575f5ffd5b5f6020828403121561514b575f5ffd5b5051919050565b5f60208284031215615162575f5ffd5b815161165a81614b48565b5f6001820161517e5761517e615041565b5060010190565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b0388811682528716602082015260a0604082018190525f906151d99083018789615185565b82810360608401526151ec818688615185565b91505082608083015298975050505050505050565b6001600160a01b038816815260a0602082018190525f90615225908301888a615185565b8281036040840152615238818789615185565b6001600160a01b03958616606085015293909416608090920191909152509695505050505050565b6001600160a01b03831681528135602080830191909152820135604080830191909152608082019083013561529481614b48565b6001600160a01b03166060929092019190915292915050565b6001815b60018411156152e8578085048111156152cc576152cc615041565b60018416156152da57908102905b60019390931c9280026152b1565b935093915050565b5f826152fe575060016111aa565b8161530a57505f6111aa565b8160018114615320576002811461532a57615346565b60019150506111aa565b60ff84111561533b5761533b615041565b50506001821b6111aa565b5060208310610133831016604e8410600b8410161715615369575081810a6111aa565b6153755f1984846152ad565b805f190482111561538857615388615041565b029392505050565b5f61165a60ff8416836152f0565b6001600160a01b038781168252861660208201526080604082018190525f906153ca9083018688615185565b82810360608401526153dd818587615185565b9998505050505050505050565b5f602082840312156153fa575f5ffd5b815161165a81614e4b565b604080825283519082018190525f9060208501906060840190835b8181101561543e578351835260209384019390920191600101615420565b505083810360208501526154528186614ceb565b9695505050505050565b634e487b7160e01b5f52601260045260245ffd5b5f82518060208501845e5f920191825250919050565b634e487b7160e01b5f52602160045260245ffd5b5f60ff8316806154b857634e487b7160e01b5f52601260045260245ffd5b8060ff84160691505092915050565b634e487b7160e01b5f52603160045260245ffd5b6001600160a01b03841681526040602082018190525f90613b4c908301848661518556fea164736f6c634300081e000a
Verified Source Code Partial Match
Compiler: v0.8.30+commit.73712a01
EVM: prague
Optimization: Yes (200 runs)
SuperVaultAggregator.sol 1413 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
// External
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
// Superform
import { SuperVault } from "./SuperVault.sol";
import { SuperVaultStrategy } from "./SuperVaultStrategy.sol";
import { ISuperVaultStrategy } from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
import { SuperVaultEscrow } from "./SuperVaultEscrow.sol";
import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
// Libraries
import { AssetMetadataLib } from "../libraries/AssetMetadataLib.sol";
/// @title SuperVaultAggregator
/// @author Superform Labs
/// @notice Registry and PPS oracle for all SuperVaults
/// @dev Creates new SuperVault trios and manages PPS updates
contract SuperVaultAggregator is ISuperVaultAggregator {
using AssetMetadataLib for address;
using Clones for address;
using SafeERC20 for IERC20;
using Math for uint256;
using EnumerableSet for EnumerableSet.AddressSet;
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
// Vault implementation contracts
address public immutable VAULT_IMPLEMENTATION;
address public immutable STRATEGY_IMPLEMENTATION;
address public immutable ESCROW_IMPLEMENTATION;
// Governance
ISuperGovernor public immutable SUPER_GOVERNOR;
// Claimable upkeep
uint256 public claimableUpkeep;
// Strategy data storage
mapping(address strategy => StrategyData) private _strategyData;
// Upkeep balances (per strategy)
mapping(address strategy => uint256 upkeep) private _strategyUpkeepBalance;
// Two-step upkeep withdrawal system
mapping(address strategy => UpkeepWithdrawalRequest) public pendingUpkeepWithdrawals;
// Registry of created vaults
EnumerableSet.AddressSet private _superVaults;
EnumerableSet.AddressSet private _superVaultStrategies;
EnumerableSet.AddressSet private _superVaultEscrows;
// Constant for basis points precision (100% = 10,000 bps)
uint256 private constant BPS_PRECISION = 10_000;
// Maximum performance fee allowed (51%)
uint256 private constant MAX_PERFORMANCE_FEE = 5100;
// Maximum number of secondary managers per strategy to prevent governance DoS on manager replacement
uint256 public constant MAX_SECONDARY_MANAGERS = 5;
// Default deviation threshold for new strategies (50% in 1e18 scale)
uint256 private constant DEFAULT_DEVIATION_THRESHOLD = 5e17;
// Timelock for upkeep withdrawal (24 hours)
uint256 public constant UPKEEP_WITHDRAWAL_TIMELOCK = 24 hours;
// Timelock for manager changes and Merkle root updates
uint256 private constant _MANAGER_CHANGE_TIMELOCK = 7 days;
uint256 private _hooksRootUpdateTimelock = 15 minutes;
// Timelock for parameter changes (3 days)
uint256 private constant _PARAMETER_CHANGE_TIMELOCK = 3 days;
// Global hooks Merkle root data
bytes32 private _globalHooksRoot;
bytes32 private _proposedGlobalHooksRoot;
uint256 private _globalHooksRootEffectiveTime;
bool private _globalHooksRootVetoed;
// Nonce for vault creation tracking
uint256 private _vaultCreationNonce;
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/// @notice Validates that msg.sender is the active PPS Oracle
modifier onlyPPSOracle() {
_onlyPPSOracle();
_;
}
function _onlyPPSOracle() internal view {
if (!SUPER_GOVERNOR.isActivePPSOracle(msg.sender)) {
revert UNAUTHORIZED_PPS_ORACLE();
}
}
/// @notice Validates that a strategy exists (has been created by this aggregator)
modifier validStrategy(address strategy) {
_validStrategy(strategy);
_;
}
function _validStrategy(address strategy) internal view {
if (!_superVaultStrategies.contains(strategy)) revert UNKNOWN_STRATEGY();
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @notice Initializes the SuperVaultAggregator
/// @param superGovernor_ Address of the SuperGovernor contract
/// @param vaultImpl_ Address of the pre-deployed SuperVault implementation
/// @param strategyImpl_ Address of the pre-deployed SuperVaultStrategy implementation
/// @param escrowImpl_ Address of the pre-deployed SuperVaultEscrow implementation
constructor(address superGovernor_, address vaultImpl_, address strategyImpl_, address escrowImpl_) {
if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
if (vaultImpl_ == address(0)) revert ZERO_ADDRESS();
if (strategyImpl_ == address(0)) revert ZERO_ADDRESS();
if (escrowImpl_ == address(0)) revert ZERO_ADDRESS();
SUPER_GOVERNOR = ISuperGovernor(superGovernor_);
VAULT_IMPLEMENTATION = vaultImpl_;
STRATEGY_IMPLEMENTATION = strategyImpl_;
ESCROW_IMPLEMENTATION = escrowImpl_;
}
/*//////////////////////////////////////////////////////////////
VAULT CREATION
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function createVault(VaultCreationParams calldata params)
external
returns (address superVault, address strategy, address escrow)
{
// Input validation
if (params.asset == address(0) || params.mainManager == address(0) || params.feeConfig.recipient == address(0))
{
revert ZERO_ADDRESS();
}
/// @dev Check that name and symbol are not empty
/// We don't check for anything else and
/// it's up to the creator to ensure that the vault
/// is created with valid parameters
if (bytes(params.name).length == 0 || bytes(params.symbol).length == 0) {
revert INVALID_VAULT_PARAMS();
}
// Initialize local variables struct to avoid stack too deep
VaultCreationLocalVars memory vars;
vars.currentNonce = _vaultCreationNonce++;
vars.salt = keccak256(abi.encode(msg.sender, params.asset, params.name, params.symbol, vars.currentNonce));
// Create minimal proxies
superVault = VAULT_IMPLEMENTATION.cloneDeterministic(vars.salt);
escrow = ESCROW_IMPLEMENTATION.cloneDeterministic(vars.salt);
strategy = STRATEGY_IMPLEMENTATION.cloneDeterministic(vars.salt);
// Initialize superVault
SuperVault(superVault).initialize(params.asset, params.name, params.symbol, strategy, escrow);
// Initialize escrow
SuperVaultEscrow(escrow).initialize(superVault);
// Initialize strategy
SuperVaultStrategy(payable(strategy)).initialize(superVault, params.feeConfig);
// Store vault trio in registry
_superVaults.add(superVault);
_superVaultStrategies.add(strategy);
_superVaultEscrows.add(escrow);
// Get asset decimals
(bool success, uint8 assetDecimals) = params.asset.tryGetAssetDecimals();
if (!success) revert INVALID_ASSET();
// Initial PPS is always 1.0 (scaled by asset decimals) for new vaults
// This means 1 vault share = 1 unit of underlying asset at inception
vars.initialPPS = 10 ** assetDecimals;
// Validate maxStaleness against minimum required staleness
if (params.maxStaleness < SUPER_GOVERNOR.getMinStaleness()) {
revert MAX_STALENESS_TOO_LOW();
}
// Validate minUpdateInterval against minimum required staleness
if (params.minUpdateInterval >= params.maxStaleness) {
revert INVALID_VAULT_PARAMS();
}
// Initialize StrategyData individually to avoid mapping assignment issues
_strategyData[strategy].pps = vars.initialPPS;
_strategyData[strategy].lastUpdateTimestamp = block.timestamp;
_strategyData[strategy].minUpdateInterval = params.minUpdateInterval;
_strategyData[strategy].maxStaleness = params.maxStaleness;
_strategyData[strategy].isPaused = false;
_strategyData[strategy].mainManager = params.mainManager;
uint256 secondaryLen = params.secondaryManagers.length;
if (secondaryLen > MAX_SECONDARY_MANAGERS) revert TOO_MANY_SECONDARY_MANAGERS();
for (uint256 i; i < secondaryLen; ++i) {
address _secondaryManager = params.secondaryManagers[i];
// Check if manager is a zero address
if (_secondaryManager == address(0)) revert ZERO_ADDRESS();
// Check if manager is already the primary manager
if (_strategyData[strategy].mainManager == _secondaryManager) revert SECONDARY_MANAGER_CANNOT_BE_PRIMARY();
// Add secondary manager and revert if it already exists
if (!_strategyData[strategy].secondaryManagers.add(_secondaryManager)) {
revert MANAGER_ALREADY_EXISTS();
}
}
_strategyData[strategy].deviationThreshold = DEFAULT_DEVIATION_THRESHOLD;
emit VaultDeployed(superVault, strategy, escrow, params.asset, params.name, params.symbol, vars.currentNonce);
emit PPSUpdated(strategy, vars.initialPPS, _strategyData[strategy].lastUpdateTimestamp);
return (superVault, strategy, escrow);
}
/*//////////////////////////////////////////////////////////////
PPS UPDATE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function forwardPPS(ForwardPPSArgs calldata args) external onlyPPSOracle {
bool paymentsEnabled = SUPER_GOVERNOR.isUpkeepPaymentsEnabled();
uint256 strategiesLength = args.strategies.length;
for (uint256 i; i < strategiesLength; ++i) {
address strategy = args.strategies[i];
// Skip invalid strategy
if (!_superVaultStrategies.contains(strategy)) {
emit UnknownStrategy(strategy);
continue;
}
// Skip invalid timestamp
uint256 ts = args.timestamps[i];
// [Property 4: Future Timestamp Rejection]
// Reject updates with timestamps in the future. This prevents validators from
// creating signatures with future timestamps that could be used later.
if (ts > block.timestamp) {
emit ProvidedTimestampExceedsBlockTimestamp(strategy, ts, block.timestamp);
continue;
}
StrategyData storage data = _strategyData[strategy];
// [Property 5: Pause Rejection]
// Always skip paused strategies, regardless of payment settings.
// Paused strategies should not accept any PPS updates until explicitly unpaused.
// This check happens early to avoid unnecessary processing and gas costs.
if (data.isPaused) {
emit PPSUpdateRejectedStrategyPaused(strategy);
continue; // Skip processing paused strategies
}
// [Property 6: Staleness Enforcement (Absolute Time)]
// Always enforce staleness check, regardless of payment status.
// This prevents attackers from submitting stale signatures to manipulate PPS.
// The check must occur before payment calculation to protect all strategies.
if (block.timestamp - ts > data.maxStaleness) {
emit StaleUpdate(strategy, args.updateAuthority, ts);
continue; // Skip processing stale updates
}
uint256 upkeepCost = 0;
if (paymentsEnabled) {
// Query cost directly per entry
// Everyone pays the upkeep cost
try SUPER_GOVERNOR.getUpkeepCostPerSingleUpdate(msg.sender) returns (uint256 cost) {
upkeepCost = cost;
} catch {
// If upkeep cost computation fails (e.g., oracle misconfiguration),
// allow PPS update to proceed without charging upkeep.
upkeepCost = 0;
}
}
_forwardPPS(
PPSUpdateData({
strategy: strategy,
// isExempt when upkeepCost is 0 (covers both paymentsDisabled and oracle failures)
isExempt: upkeepCost == 0,
pps: args.ppss[i],
timestamp: ts,
upkeepCost: upkeepCost
})
);
}
}
/// @inheritdoc ISuperVaultAggregator
function updatePPSAfterSkim(uint256 newPPS, uint256 feeAmount) external validStrategy(msg.sender) {
// msg.sender must be a registered strategy (validated by modifier)
address strategy = msg.sender;
StrategyData storage data = _strategyData[strategy];
// Disallow PPS updates after skim when strategy is paused or PPS is stale
if (data.isPaused) revert STRATEGY_PAUSED();
if (data.ppsStale) revert PPS_STALE();
uint256 oldPPS = data.pps;
// VALIDATION 1: PPS must decrease after fee skim
if (newPPS >= oldPPS) revert PPS_MUST_DECREASE_AFTER_SKIM();
// VALIDATION 2: PPS must be positive
if (newPPS == 0) revert INVALID_ASSET();
// VALIDATION 3: Range check - deduction must be within max fee bounds
// Use MAX_PERFORMANCE_FEE to avoid external call to strategy
// Max possible PPS after skim: oldPPS * (1 - MAX_PERFORMANCE_FEE)
// Use Ceil rounding to ensure strict enforcement of MAX_PERFORMANCE_FEE (51%) limit
uint256 minAllowedPPS = oldPPS.mulDiv(BPS_PRECISION - MAX_PERFORMANCE_FEE, BPS_PRECISION, Math.Rounding.Ceil);
if (newPPS < minAllowedPPS) revert PPS_DEDUCTION_TOO_LARGE();
// VALIDATION 4: Fee amount must be non-zero when PPS decreases
// This ensures consistent reporting between PPS change and claimed fee amount
if (feeAmount == 0) revert INVALID_ASSET();
// UPDATE: Store new PPS
data.pps = newPPS;
// UPDATE TIMESTAMP
// Update timestamp to reflect when this PPS change occurred
// NOTE: This may interact with oracle submissions - to be discussed
data.lastUpdateTimestamp = block.timestamp;
// NOTE: We do NOT reset ppsStale flag here
// The skim function can only be called if _validateStrategyState doesn't revert
// So if we reach here, the strategy state is valid
emit PPSUpdatedAfterSkim(strategy, oldPPS, newPPS, feeAmount, block.timestamp);
}
/*//////////////////////////////////////////////////////////////
UPKEEP MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function depositUpkeep(address strategy, uint256 amount) external validStrategy(strategy) {
if (amount == 0) revert ZERO_AMOUNT();
// Get the UPKEEP_TOKEN address from SUPER_GOVERNOR
address upkeepToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UPKEEP_TOKEN());
// Transfer UPKEEP_TOKEN from msg.sender to this contract
IERC20(upkeepToken).safeTransferFrom(msg.sender, address(this), amount);
// Update upkeep balance for this strategy
_strategyUpkeepBalance[strategy] += amount;
emit UpkeepDeposited(strategy, msg.sender, amount);
}
/// @inheritdoc ISuperVaultAggregator
function claimUpkeep(uint256 amount) external {
// Only SUPER_GOVERNOR can claim upkeep
if (msg.sender != address(SUPER_GOVERNOR)) {
revert CALLER_NOT_AUTHORIZED();
}
if (claimableUpkeep < amount) revert INSUFFICIENT_UPKEEP();
claimableUpkeep -= amount;
// Get the UPKEEP_TOKEN address from SUPER_GOVERNOR
address upkeepToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UPKEEP_TOKEN());
// Transfer UPKEEP_TOKEN to `SuperBank`
address _superBank = _getSuperBank();
IERC20(upkeepToken).safeTransfer(_superBank, amount);
emit UpkeepClaimed(_superBank, amount);
}
/// @inheritdoc ISuperVaultAggregator
function proposeWithdrawUpkeep(address strategy) external validStrategy(strategy) {
// Only mainManager can propose upkeep withdrawal from a strategy
if (msg.sender != _strategyData[strategy].mainManager) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Get current strategy upkeep balance (full balance withdrawal)
uint256 currentBalance = _strategyUpkeepBalance[strategy];
if (currentBalance == 0) revert ZERO_AMOUNT();
// Create withdrawal request
pendingUpkeepWithdrawals[strategy] = UpkeepWithdrawalRequest({
amount: currentBalance, effectiveTime: block.timestamp + UPKEEP_WITHDRAWAL_TIMELOCK
});
emit UpkeepWithdrawalProposed(
strategy, msg.sender, currentBalance, block.timestamp + UPKEEP_WITHDRAWAL_TIMELOCK
);
}
/// @inheritdoc ISuperVaultAggregator
function executeWithdrawUpkeep(address strategy) external validStrategy(strategy) {
UpkeepWithdrawalRequest memory request = pendingUpkeepWithdrawals[strategy];
// Check that a request exists
if (request.effectiveTime == 0) revert UPKEEP_WITHDRAWAL_NOT_FOUND();
// Check that timelock has passed
if (block.timestamp < request.effectiveTime) revert UPKEEP_WITHDRAWAL_NOT_READY();
// Calculate actual withdrawal amount (use current balance, may be less if upkeep was spent)
uint256 currentBalance = _strategyUpkeepBalance[strategy];
uint256 withdrawalAmount = currentBalance < request.amount ? currentBalance : request.amount;
if (withdrawalAmount == 0) revert ZERO_AMOUNT();
// Clear the pending request
delete pendingUpkeepWithdrawals[strategy];
// Get the UPKEEP_TOKEN address from SUPER_GOVERNOR
address upkeepToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UPKEEP_TOKEN());
// Update upkeep balance
unchecked {
_strategyUpkeepBalance[strategy] -= withdrawalAmount;
}
// Transfer UPKEEP_TOKEN to the original main manager (not msg.sender)
address mainManager = _strategyData[strategy].mainManager;
IERC20(upkeepToken).safeTransfer(mainManager, withdrawalAmount);
emit UpkeepWithdrawn(strategy, mainManager, withdrawalAmount);
}
/*//////////////////////////////////////////////////////////////
PAUSE MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Manually pauses a strategy
/// @param strategy Address of the strategy to pause
/// @dev Only the main or secondary manager of the strategy can pause it
function pauseStrategy(address strategy) external validStrategy(strategy) {
// Either primary or secondary manager can pause
if (!isAnyManager(msg.sender, strategy)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Check if strategy is already paused
if (_strategyData[strategy].isPaused) {
revert STRATEGY_ALREADY_PAUSED();
}
// Pause the strategy
_strategyData[strategy].isPaused = true;
_strategyData[strategy].ppsStale = true;
emit StrategyPaused(strategy);
}
/// @notice Manually unpauses a strategy
/// @param strategy Address of the strategy to unpause
/// @dev unpausing marks PPS stale until a fresh oracle update
function unpauseStrategy(address strategy) external validStrategy(strategy) {
if (!isAnyManager(msg.sender, strategy)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Check if strategy is currently paused
if (!_strategyData[strategy].isPaused) {
revert STRATEGY_NOT_PAUSED();
}
// Unpause the strategy and track unpause timestamp
_strategyData[strategy].isPaused = false;
_strategyData[strategy].lastUnpauseTimestamp = block.timestamp; // Track for skim timelock
// ppsStale already true from pause - no need to set again (gas savings)
emit StrategyUnpaused(strategy);
}
/*//////////////////////////////////////////////////////////////
MANAGER MANAGEMENT FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function addSecondaryManager(address strategy, address manager) external validStrategy(strategy) {
// Only the primary manager can add secondary managers
if (msg.sender != _strategyData[strategy].mainManager) revert UNAUTHORIZED_UPDATE_AUTHORITY();
if (manager == address(0)) revert ZERO_ADDRESS();
// Check if manager is already the primary manager
if (_strategyData[strategy].mainManager == manager) revert SECONDARY_MANAGER_CANNOT_BE_PRIMARY();
// Enforce a cap on secondary managers to prevent governance DoS on changePrimaryManager
if (_strategyData[strategy].secondaryManagers.length() >= MAX_SECONDARY_MANAGERS) {
revert TOO_MANY_SECONDARY_MANAGERS();
}
// Add as secondary manager using EnumerableSet
if (!_strategyData[strategy].secondaryManagers.add(manager)) revert MANAGER_ALREADY_EXISTS();
emit SecondaryManagerAdded(strategy, manager);
}
/// @inheritdoc ISuperVaultAggregator
function removeSecondaryManager(address strategy, address manager) external validStrategy(strategy) {
// Only the primary manager can remove secondary managers
if (msg.sender != _strategyData[strategy].mainManager) revert UNAUTHORIZED_UPDATE_AUTHORITY();
// Remove the manager using EnumerableSet
if (!_strategyData[strategy].secondaryManagers.remove(manager)) revert MANAGER_NOT_FOUND();
emit SecondaryManagerRemoved(strategy, manager);
}
/// @inheritdoc ISuperVaultAggregator
function updateDeviationThreshold(address strategy, uint256 deviationThreshold_) external validStrategy(strategy) {
// Since this is a risky call, we only allow main managers as callers
if (msg.sender != _strategyData[strategy].mainManager) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Update the threshold
_strategyData[strategy].deviationThreshold = deviationThreshold_;
// Emit the event
emit DeviationThresholdUpdated(strategy, deviationThreshold_);
}
/// @inheritdoc ISuperVaultAggregator
function changeGlobalLeavesStatus(
bytes32[] memory leaves,
bool[] memory statuses,
address strategy
)
external
validStrategy(strategy)
{
// Only the primary manager can change global leaves status
if (msg.sender != _strategyData[strategy].mainManager) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
uint256 leavesLen = leaves.length;
// Check array lengths match
if (leavesLen != statuses.length) {
revert MISMATCHED_ARRAY_LENGTHS();
}
// Update banned status for each leaf
for (uint256 i; i < leavesLen; i++) {
_strategyData[strategy].bannedLeaves[leaves[i]] = statuses[i];
}
// Emit event
emit GlobalLeavesStatusChanged(strategy, leaves, statuses);
}
/// @inheritdoc ISuperVaultAggregator
/// @dev SECURITY: This is the emergency governance override function
/// @dev Clears ALL pending proposals and secondary managers to prevent malicious manager attacks:
/// - Pending manager change proposals
/// - Pending hooks root proposals
/// - Pending minUpdateInterval proposals
/// - ALL secondary managers (they may be controlled by malicious manager)
/// @dev This ensures clean slate for new manager without inherited vulnerabilities
/// @dev This function is only callable by SUPER_GOVERNOR
function changePrimaryManager(
address strategy,
address newManager,
address feeRecipient
)
external
validStrategy(strategy)
{
// Only SuperGovernor can call this
if (msg.sender != address(SUPER_GOVERNOR)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
if (newManager == address(0) || feeRecipient == address(0)) revert ZERO_ADDRESS();
// Check if new manager is already the primary manager to prevent malicious feeRecipient update
if (newManager == _strategyData[strategy].mainManager) revert MANAGER_ALREADY_EXISTS();
address oldManager = _strategyData[strategy].mainManager;
// SECURITY: Clear any pending manager proposals to prevent malicious re-takeover
_strategyData[strategy].proposedManager = address(0);
_strategyData[strategy].managerChangeEffectiveTime = 0;
// SECURITY: Clear any pending fee recipient proposals to prevent malicious change
_strategyData[strategy].proposedFeeRecipient = address(0);
// SECURITY: Clear any pending hooks root proposals to prevent malicious hook updates
_strategyData[strategy].proposedHooksRoot = bytes32(0);
_strategyData[strategy].hooksRootEffectiveTime = 0;
// SECURITY: Clear any pending minUpdateInterval proposals
_strategyData[strategy].proposedMinUpdateInterval = 0;
_strategyData[strategy].minUpdateIntervalEffectiveTime = 0;
// SECURITY: Clear all secondary managers as they may be controlled by malicious manager
// Get all secondary managers first to emit proper events
address[] memory clearedSecondaryManagers = _strategyData[strategy].secondaryManagers.values();
// Clear the entire secondary managers set
for (uint256 i = 0; i < clearedSecondaryManagers.length; i++) {
_strategyData[strategy].secondaryManagers.remove(clearedSecondaryManagers[i]);
emit SecondaryManagerRemoved(strategy, clearedSecondaryManagers[i]);
}
// SECURITY: Cancel any pending upkeep withdrawal to prevent old manager from withdrawing
if (pendingUpkeepWithdrawals[strategy].effectiveTime != 0) {
delete pendingUpkeepWithdrawals[strategy];
emit UpkeepWithdrawalCancelled(strategy);
}
// Set the new primary manager
_strategyData[strategy].mainManager = newManager;
// Set the new fee recipient
ISuperVaultStrategy(strategy).changeFeeRecipient(feeRecipient);
emit PrimaryManagerChanged(strategy, oldManager, newManager, feeRecipient);
}
/// @inheritdoc ISuperVaultAggregator
function proposeChangePrimaryManager(
address strategy,
address newManager,
address feeRecipient
)
external
validStrategy(strategy)
{
// Only secondary managers can propose changes to the primary manager
if (!_strategyData[strategy].secondaryManagers.contains(msg.sender)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
if (newManager == address(0) || feeRecipient == address(0)) revert ZERO_ADDRESS();
// Check if new manager is already the primary manager to prevent malicious feeRecipient update
if (newManager == _strategyData[strategy].mainManager) revert MANAGER_ALREADY_EXISTS();
// Set up the proposal with 7-day timelock
uint256 effectiveTime = block.timestamp + _MANAGER_CHANGE_TIMELOCK;
// Store proposal in the strategy data
_strategyData[strategy].proposedManager = newManager;
_strategyData[strategy].proposedFeeRecipient = feeRecipient;
_strategyData[strategy].managerChangeEffectiveTime = effectiveTime;
emit PrimaryManagerChangeProposed(strategy, msg.sender, newManager, feeRecipient, effectiveTime);
}
/// @inheritdoc ISuperVaultAggregator
function cancelChangePrimaryManager(address strategy) external validStrategy(strategy) {
// Only the current main manager can cancel the proposal
if (_strategyData[strategy].mainManager != msg.sender) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Check if there is a pending proposal
if (_strategyData[strategy].proposedManager == address(0)) {
revert NO_PENDING_MANAGER_CHANGE();
}
address cancelledManager = _strategyData[strategy].proposedManager;
// Clear the proposal
_strategyData[strategy].proposedManager = address(0);
_strategyData[strategy].proposedFeeRecipient = address(0);
_strategyData[strategy].managerChangeEffectiveTime = 0;
emit PrimaryManagerChangeCancelled(strategy, cancelledManager);
}
/// @inheritdoc ISuperVaultAggregator
function executeChangePrimaryManager(address strategy) external validStrategy(strategy) {
// Check if there is a pending proposal
if (_strategyData[strategy].proposedManager == address(0)) revert NO_PENDING_MANAGER_CHANGE();
// Check if the timelock period has passed
if (block.timestamp < _strategyData[strategy].managerChangeEffectiveTime) revert TIMELOCK_NOT_EXPIRED();
address newManager = _strategyData[strategy].proposedManager;
address feeRecipient = _strategyData[strategy].proposedFeeRecipient;
// Validate proposed values are not zero addresses (defense in depth)
if (newManager == address(0) || feeRecipient == address(0)) revert ZERO_ADDRESS();
address oldManager = _strategyData[strategy].mainManager;
// SECURITY: Clear all secondary managers to prevent privilege retntion
_strategyData[strategy].secondaryManagers.clear();
// Cancel any pending upkeep withdrawal to ensure clean transition
if (pendingUpkeepWithdrawals[strategy].effectiveTime != 0) {
delete pendingUpkeepWithdrawals[strategy];
emit UpkeepWithdrawalCancelled(strategy);
}
_strategyData[strategy].proposedHooksRoot = bytes32(0);
_strategyData[strategy].hooksRootEffectiveTime = 0;
_strategyData[strategy].proposedMinUpdateInterval = 0;
_strategyData[strategy].minUpdateIntervalEffectiveTime = 0;
// Set the new primary manager
_strategyData[strategy].mainManager = newManager;
// Set the new fee recipient
ISuperVaultStrategy(strategy).changeFeeRecipient(feeRecipient);
// Clear the proposal
_strategyData[strategy].proposedManager = address(0);
_strategyData[strategy].proposedFeeRecipient = address(0);
_strategyData[strategy].managerChangeEffectiveTime = 0;
emit PrimaryManagerChanged(strategy, oldManager, newManager, feeRecipient);
}
/// @inheritdoc ISuperVaultAggregator
/// @dev SECURITY: This function is intended to be used by governance to onboard a new manager without penalizing
/// them for the previous manager's performance.
/// @dev If a manager is replaced while the strategy is below its
/// previous HWM, the new manager would otherwise inherit a "loss" state and be unable to earn performance fees
/// until the fee config are updated after the week timelock.
/// @dev Calling this function resets the HWM to the current PPS, allowing a newly appointed manager to start from a
/// neutral baseline. @dev This function is only callable by SUPER_GOVERNOR
function resetHighWaterMark(address strategy) external validStrategy(strategy) {
// Only SuperGovernor can call this
if (msg.sender != address(SUPER_GOVERNOR)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
uint256 newHwmPps = _strategyData[strategy].pps;
// Reset the High Water Mark to the current PPS
ISuperVaultStrategy(strategy).resetHighWaterMark(newHwmPps);
emit HighWaterMarkReset(strategy, newHwmPps);
}
/*//////////////////////////////////////////////////////////////
HOOK VALIDATION FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function setHooksRootUpdateTimelock(uint256 newTimelock) external {
// Only SUPER_GOVERNOR can update the timelock
if (msg.sender != address(SUPER_GOVERNOR)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Update the timelock
_hooksRootUpdateTimelock = newTimelock;
emit HooksRootUpdateTimelockChanged(newTimelock);
}
/// @inheritdoc ISuperVaultAggregator
function proposeGlobalHooksRoot(bytes32 newRoot) external {
// Only SUPER_GOVERNOR can update the global hooks root
if (msg.sender != address(SUPER_GOVERNOR)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Set new root with timelock
_proposedGlobalHooksRoot = newRoot;
uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock;
_globalHooksRootEffectiveTime = effectiveTime;
emit GlobalHooksRootUpdateProposed(newRoot, effectiveTime);
}
/// @inheritdoc ISuperVaultAggregator
function executeGlobalHooksRootUpdate() external {
bytes32 proposedRoot = _proposedGlobalHooksRoot;
// Ensure there is a pending proposal
if (proposedRoot == bytes32(0)) {
revert NO_PENDING_GLOBAL_ROOT_CHANGE();
}
// Check if timelock period has elapsed
if (block.timestamp < _globalHooksRootEffectiveTime) {
revert ROOT_UPDATE_NOT_READY();
}
// Update the global hooks root
bytes32 oldRoot = _globalHooksRoot;
_globalHooksRoot = proposedRoot;
_globalHooksRootEffectiveTime = 0;
_proposedGlobalHooksRoot = bytes32(0);
emit GlobalHooksRootUpdated(oldRoot, proposedRoot);
}
/// @inheritdoc ISuperVaultAggregator
function setGlobalHooksRootVetoStatus(bool vetoed) external {
// Only SuperGovernor can call this
if (msg.sender != address(SUPER_GOVERNOR)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Don't emit event if status doesn't change
if (_globalHooksRootVetoed == vetoed) {
return;
}
// Update veto status
_globalHooksRootVetoed = vetoed;
emit GlobalHooksRootVetoStatusChanged(vetoed, _globalHooksRoot);
}
/// @inheritdoc ISuperVaultAggregator
function proposeStrategyHooksRoot(address strategy, bytes32 newRoot) external validStrategy(strategy) {
// Only the main manager can propose strategy-specific hooks root
if (_strategyData[strategy].mainManager != msg.sender) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Set proposed root with timelock
_strategyData[strategy].proposedHooksRoot = newRoot;
uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock;
_strategyData[strategy].hooksRootEffectiveTime = effectiveTime;
emit StrategyHooksRootUpdateProposed(strategy, msg.sender, newRoot, effectiveTime);
}
/// @inheritdoc ISuperVaultAggregator
function executeStrategyHooksRootUpdate(address strategy) external validStrategy(strategy) {
bytes32 proposedRoot = _strategyData[strategy].proposedHooksRoot;
// Ensure there is a pending proposal
if (proposedRoot == bytes32(0)) {
revert NO_PENDING_MANAGER_CHANGE(); // Reusing error for simplicity
}
// Check if timelock period has elapsed
if (block.timestamp < _strategyData[strategy].hooksRootEffectiveTime) {
revert ROOT_UPDATE_NOT_READY();
}
// Update the strategy's hooks root
bytes32 oldRoot = _strategyData[strategy].managerHooksRoot;
_strategyData[strategy].managerHooksRoot = proposedRoot;
// Reset proposal state
_strategyData[strategy].proposedHooksRoot = bytes32(0);
_strategyData[strategy].hooksRootEffectiveTime = 0;
emit StrategyHooksRootUpdated(strategy, oldRoot, proposedRoot);
}
/// @inheritdoc ISuperVaultAggregator
function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external validStrategy(strategy) {
// Only SuperGovernor can call this
if (msg.sender != address(SUPER_GOVERNOR)) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Don't emit event if status doesn't change
if (_strategyData[strategy].hooksRootVetoed == vetoed) {
return;
}
// Update veto status
_strategyData[strategy].hooksRootVetoed = vetoed;
emit StrategyHooksRootVetoStatusChanged(strategy, vetoed, _strategyData[strategy].managerHooksRoot);
}
/*//////////////////////////////////////////////////////////////
MIN UPDATE INTERVAL MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function proposeMinUpdateIntervalChange(
address strategy,
uint256 newMinUpdateInterval
)
external
validStrategy(strategy)
{
// Only the main manager can propose changes
if (_strategyData[strategy].mainManager != msg.sender) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Validate: newMinUpdateInterval must be less than maxStaleness
// This ensures updates can occur before data becomes stale
if (newMinUpdateInterval >= _strategyData[strategy].maxStaleness) {
revert MIN_UPDATE_INTERVAL_TOO_HIGH();
}
// Set proposed interval with timelock
uint256 effectiveTime = block.timestamp + _PARAMETER_CHANGE_TIMELOCK;
_strategyData[strategy].proposedMinUpdateInterval = newMinUpdateInterval;
_strategyData[strategy].minUpdateIntervalEffectiveTime = effectiveTime;
emit MinUpdateIntervalChangeProposed(strategy, msg.sender, newMinUpdateInterval, effectiveTime);
}
/// @inheritdoc ISuperVaultAggregator
function executeMinUpdateIntervalChange(address strategy) external validStrategy(strategy) {
// Check if there is a pending proposal
if (_strategyData[strategy].minUpdateIntervalEffectiveTime == 0) {
revert NO_PENDING_MIN_UPDATE_INTERVAL_CHANGE();
}
// Check if the timelock period has passed
if (block.timestamp < _strategyData[strategy].minUpdateIntervalEffectiveTime) {
revert TIMELOCK_NOT_EXPIRED();
}
uint256 newInterval = _strategyData[strategy].proposedMinUpdateInterval;
uint256 oldInterval = _strategyData[strategy].minUpdateInterval;
// Clear the proposal first
_strategyData[strategy].proposedMinUpdateInterval = 0;
_strategyData[strategy].minUpdateIntervalEffectiveTime = 0;
// Update the minUpdateInterval
_strategyData[strategy].minUpdateInterval = newInterval;
emit MinUpdateIntervalChanged(strategy, oldInterval, newInterval);
}
/// @inheritdoc ISuperVaultAggregator
function cancelMinUpdateIntervalChange(address strategy) external validStrategy(strategy) {
// Only the main manager can cancel
if (_strategyData[strategy].mainManager != msg.sender) {
revert UNAUTHORIZED_UPDATE_AUTHORITY();
}
// Check if there is a pending proposal
if (_strategyData[strategy].minUpdateIntervalEffectiveTime == 0) {
revert NO_PENDING_MIN_UPDATE_INTERVAL_CHANGE();
}
uint256 cancelledInterval = _strategyData[strategy].proposedMinUpdateInterval;
// Clear the proposal
_strategyData[strategy].proposedMinUpdateInterval = 0;
_strategyData[strategy].minUpdateIntervalEffectiveTime = 0;
emit MinUpdateIntervalChangeCancelled(strategy, cancelledInterval);
}
/// @inheritdoc ISuperVaultAggregator
function getProposedMinUpdateInterval(address strategy)
external
view
returns (uint256 proposedInterval, uint256 effectiveTime)
{
return (
_strategyData[strategy].proposedMinUpdateInterval, _strategyData[strategy].minUpdateIntervalEffectiveTime
);
}
/// @inheritdoc ISuperVaultAggregator
function isGlobalHooksRootVetoed() external view returns (bool vetoed) {
return _globalHooksRootVetoed;
}
/// @inheritdoc ISuperVaultAggregator
function isStrategyHooksRootVetoed(address strategy) external view returns (bool vetoed) {
return _strategyData[strategy].hooksRootVetoed;
}
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultAggregator
function getSuperVaultsCount() external view returns (uint256) {
return _superVaults.length();
}
/// @inheritdoc ISuperVaultAggregator
function getSuperVaultStrategiesCount() external view returns (uint256) {
return _superVaultStrategies.length();
}
/// @inheritdoc ISuperVaultAggregator
function getSuperVaultEscrowsCount() external view returns (uint256) {
return _superVaultEscrows.length();
}
/// @inheritdoc ISuperVaultAggregator
function getCurrentNonce() external view returns (uint256) {
return _vaultCreationNonce;
}
/// @inheritdoc ISuperVaultAggregator
function getHooksRootUpdateTimelock() external view returns (uint256) {
return _hooksRootUpdateTimelock;
}
/// @inheritdoc ISuperVaultAggregator
function getPPS(address strategy) external view validStrategy(strategy) returns (uint256 pps) {
return _strategyData[strategy].pps;
}
/// @inheritdoc ISuperVaultAggregator
function getLastUpdateTimestamp(address strategy) external view returns (uint256 timestamp) {
return _strategyData[strategy].lastUpdateTimestamp;
}
/// @inheritdoc ISuperVaultAggregator
function getMinUpdateInterval(address strategy) external view returns (uint256 interval) {
return _strategyData[strategy].minUpdateInterval;
}
/// @inheritdoc ISuperVaultAggregator
function getMaxStaleness(address strategy) external view returns (uint256 staleness) {
return _strategyData[strategy].maxStaleness;
}
/// @inheritdoc ISuperVaultAggregator
function getDeviationThreshold(address strategy)
external
view
validStrategy(strategy)
returns (uint256 deviationThreshold)
{
return _strategyData[strategy].deviationThreshold;
}
/// @inheritdoc ISuperVaultAggregator
function isStrategyPaused(address strategy) external view returns (bool isPaused) {
return _strategyData[strategy].isPaused;
}
/// @inheritdoc ISuperVaultAggregator
function isPPSStale(address strategy) external view returns (bool isStale) {
return _strategyData[strategy].ppsStale;
}
/// @inheritdoc ISuperVaultAggregator
function getLastUnpauseTimestamp(address strategy) external view returns (uint256 timestamp) {
return _strategyData[strategy].lastUnpauseTimestamp;
}
/// @inheritdoc ISuperVaultAggregator
function getUpkeepBalance(address strategy) external view returns (uint256 balance) {
return _strategyUpkeepBalance[strategy];
}
/// @inheritdoc ISuperVaultAggregator
function getMainManager(address strategy) external view returns (address manager) {
return _strategyData[strategy].mainManager;
}
/// @inheritdoc ISuperVaultAggregator
function getPendingManagerChange(address strategy)
external
view
returns (address proposedManager, uint256 effectiveTime)
{
return (_strategyData[strategy].proposedManager, _strategyData[strategy].managerChangeEffectiveTime);
}
/// @inheritdoc ISuperVaultAggregator
function isMainManager(address manager, address strategy) public view returns (bool) {
return _strategyData[strategy].mainManager == manager;
}
/// @inheritdoc ISuperVaultAggregator
function getSecondaryManagers(address strategy) external view returns (address[] memory) {
return _strategyData[strategy].secondaryManagers.values();
}
/// @inheritdoc ISuperVaultAggregator
function isSecondaryManager(address manager, address strategy) external view returns (bool) {
return _strategyData[strategy].secondaryManagers.contains(manager);
}
/// @inheritdoc ISuperVaultAggregator
function isAnyManager(address manager, address strategy) public view returns (bool) {
// Single storage pointer read instead of multiple
StrategyData storage data = _strategyData[strategy];
return (data.mainManager == manager) || data.secondaryManagers.contains(manager);
}
/// @inheritdoc ISuperVaultAggregator
function getAllSuperVaults() external view returns (address[] memory) {
return _superVaults.values();
}
/// @inheritdoc ISuperVaultAggregator
function superVaults(uint256 index) external view returns (address) {
if (index >= _superVaults.length()) revert INDEX_OUT_OF_BOUNDS();
return _superVaults.at(index);
}
/// @inheritdoc ISuperVaultAggregator
function getAllSuperVaultStrategies() external view returns (address[] memory) {
return _superVaultStrategies.values();
}
/// @inheritdoc ISuperVaultAggregator
function superVaultStrategies(uint256 index) external view returns (address) {
if (index >= _superVaultStrategies.length()) revert INDEX_OUT_OF_BOUNDS();
return _superVaultStrategies.at(index);
}
/// @inheritdoc ISuperVaultAggregator
function getAllSuperVaultEscrows() external view returns (address[] memory) {
return _superVaultEscrows.values();
}
/// @inheritdoc ISuperVaultAggregator
function superVaultEscrows(uint256 index) external view returns (address) {
if (index >= _superVaultEscrows.length()) revert INDEX_OUT_OF_BOUNDS();
return _superVaultEscrows.at(index);
}
/// @inheritdoc ISuperVaultAggregator
function validateHook(address strategy, ValidateHookArgs calldata args) external view returns (bool isValid) {
// Cache all state variables in struct
HookValidationCache memory cache = HookValidationCache({
globalHooksRootVetoed: _globalHooksRootVetoed,
globalHooksRoot: _globalHooksRoot,
strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed,
strategyRoot: _strategyData[strategy].managerHooksRoot
});
// Early return false if either global or strategy hooks root is vetoed
if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) {
return false;
}
// Try to validate against global root first
if (_validateSingleHook(args.hookAddress, args.hookArgs, args.globalProof, true, cache, strategy)) {
return true;
}
// If global validation fails, try strategy root
return _validateSingleHook(args.hookAddress, args.hookArgs, args.strategyProof, false, cache, strategy);
}
/// @inheritdoc ISuperVaultAggregator
function validateHooks(
address strategy,
ValidateHookArgs[] calldata argsArray
)
external
view
returns (bool[] memory validHooks)
{
uint256 length = argsArray.length;
// Cache all state variables in struct
HookValidationCache memory cache = HookValidationCache({
globalHooksRootVetoed: _globalHooksRootVetoed,
globalHooksRoot: _globalHooksRoot,
strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed,
strategyRoot: _strategyData[strategy].managerHooksRoot
});
// Early return all false if either global or strategy hooks root is vetoed
if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) {
return new bool[](length); // Array initialized with all false values
}
// Validate each hook
validHooks = new bool[](length);
for (uint256 i; i < length; i++) {
// Try global root first
if (_validateSingleHook(
argsArray[i].hookAddress, argsArray[i].hookArgs, argsArray[i].globalProof, true, cache, strategy
)) {
validHooks[i] = true;
} else {
// Try strategy root
validHooks[i] = _validateSingleHook(
argsArray[i].hookAddress, argsArray[i].hookArgs, argsArray[i].strategyProof, false, cache, strategy
);
}
// If both conditions fail, validHooks[i] remains false (default value)
}
return validHooks;
}
/// @inheritdoc ISuperVaultAggregator
function getGl...
// [truncated — 60576 bytes total]
Math.sol 749 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit addition of two uint256.
*
* The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
*/
function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
assembly ("memory-safe") {
low := add(a, b)
high := lt(low, a)
}
}
/**
* @dev Return the 512-bit multiplication of two uint256.
*
* The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
*/
function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
// 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = high * 2²⁵⁶ + low.
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
low := mul(a, b)
high := sub(sub(mm, low), lt(mm, low))
}
}
/**
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
success = c >= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a - b;
success = c <= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a * b;
assembly ("memory-safe") {
// Only true when the multiplication doesn't overflow
// (c / a == b) || (a == 0)
success := or(eq(div(c, a), b), iszero(a))
}
// equivalent to: success ? c : 0
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `DIV` opcode returns zero when the denominator is 0.
result := div(a, b)
}
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `MOD` opcode returns zero when the denominator is 0.
result := mod(a, b)
}
}
}
/**
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryAdd(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
*/
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
(, uint256 result) = trySub(a, b);
return result;
}
/**
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryMul(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
// Handle non-overflow cases, 256 by 256 division.
if (high == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return low / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= high) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [high low].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
high := sub(high, gt(remainder, low))
low := sub(low, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly ("memory-safe") {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [high low] by twos.
low := div(low, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from high into low.
low |= high * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
// is no longer required.
result = low * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
*/
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
if (high >= 1 << n) {
Panic.panic(Panic.UNDER_OVERFLOW);
}
return (high << (256 - n)) | (low >> n);
}
}
/**
* @dev Calculates x * y >> n with full precision, following the selected rounding direction.
*/
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
for (uint256 i = 0; i < byteArray.length; ++i) {
if (byteArray[i] != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
// to the msb function.
uint256 aa = a;
uint256 xn = 1;
if (aa >= (1 << 128)) {
aa >>= 128;
xn <<= 64;
}
if (aa >= (1 << 64)) {
aa >>= 64;
xn <<= 32;
}
if (aa >= (1 << 32)) {
aa >>= 32;
xn <<= 16;
}
if (aa >= (1 << 16)) {
aa >>= 16;
xn <<= 8;
}
if (aa >= (1 << 8)) {
aa >>= 8;
xn <<= 4;
}
if (aa >= (1 << 4)) {
aa >>= 4;
xn <<= 2;
}
if (aa >= (1 << 2)) {
xn <<= 1;
}
// We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
//
// We can refine our estimation by noticing that the middle of that interval minimizes the error.
// If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
// This is going to be our x_0 (and ε_0)
xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
// From here, Newton's method give us:
// x_{n+1} = (x_n + a / x_n) / 2
//
// One should note that:
// x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
// = ((x_n² + a) / (2 * x_n))² - a
// = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
// = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
// = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
// = (x_n² - a)² / (2 * x_n)²
// = ((x_n² - a) / (2 * x_n))²
// ≥ 0
// Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
//
// This gives us the proof of quadratic convergence of the sequence:
// ε_{n+1} = | x_{n+1} - sqrt(a) |
// = | (x_n + a / x_n) / 2 - sqrt(a) |
// = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
// = | (x_n - sqrt(a))² / (2 * x_n) |
// = | ε_n² / (2 * x_n) |
// = ε_n² / | (2 * x_n) |
//
// For the first iteration, we have a special case where x_0 is known:
// ε_1 = ε_0² / | (2 * x_0) |
// ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
// ≤ 2**(2*e-4) / (3 * 2**(e-1))
// ≤ 2**(e-3) / 3
// ≤ 2**(e-3-log2(3))
// ≤ 2**(e-4.5)
//
// For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
// ε_{n+1} = ε_n² / | (2 * x_n) |
// ≤ (2**(e-k))² / (2 * 2**(e-1))
// ≤ 2**(2*e-2*k) / 2**e
// ≤ 2**(e-2*k)
xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above
xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5
xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9
xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18
xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36
xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72
// Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
// ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
// sqrt(a) or sqrt(a) + 1.
return xn - SafeCast.toUint(xn > a / xn);
}
}
/**
* @dev Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// If upper 8 bits of 16-bit half set, add 8 to result
r |= SafeCast.toUint((x >> r) > 0xff) << 3;
// If upper 4 bits of 8-bit half set, add 4 to result
r |= SafeCast.toUint((x >> r) > 0xf) << 2;
// Shifts value right by the current result and use it as an index into this lookup table:
//
// | x (4 bits) | index | table[index] = MSB position |
// |------------|---------|-----------------------------|
// | 0000 | 0 | table[0] = 0 |
// | 0001 | 1 | table[1] = 0 |
// | 0010 | 2 | table[2] = 1 |
// | 0011 | 3 | table[3] = 1 |
// | 0100 | 4 | table[4] = 2 |
// | 0101 | 5 | table[5] = 2 |
// | 0110 | 6 | table[6] = 2 |
// | 0111 | 7 | table[7] = 2 |
// | 1000 | 8 | table[8] = 3 |
// | 1001 | 9 | table[9] = 3 |
// | 1010 | 10 | table[10] = 3 |
// | 1011 | 11 | table[11] = 3 |
// | 1100 | 12 | table[12] = 3 |
// | 1101 | 13 | table[13] = 3 |
// | 1110 | 14 | table[14] = 3 |
// | 1111 | 15 | table[15] = 3 |
//
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
assembly ("memory-safe") {
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
}
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
SafeERC20.sol 212 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
Clones.sol 262 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}
EnumerableSet.sol 792 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol";
import {Math} from "../math/Math.sol";
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Set can be cleared (all elements removed) in O(n).
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* The following types are supported:
*
* - `bytes32` (`Bytes32Set`) since v3.3.0
* - `address` (`AddressSet`) since v3.3.0
* - `uint256` (`UintSet`) since v3.3.0
* - `string` (`StringSet`) since v5.4.0
* - `bytes` (`BytesSet`) since v5.4.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that
* using it may render the function uncallable if the set grows to the point where clearing it consumes too much
* gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) {
unchecked {
end = Math.min(end, _length(set));
start = Math.min(start, end);
uint256 len = end - start;
bytes32[] memory result = new bytes32[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(Bytes32Set storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set, uint256 start, uint256 end) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(AddressSet storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(UintSet storage set) internal {
_clear(set._inner);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
struct StringSet {
// Storage of set values
string[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(string value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(StringSet storage set, string memory value) internal returns (bool) {
if (!contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(StringSet storage set, string memory value) internal returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
string memory lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(StringSet storage set) internal {
uint256 len = length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(StringSet storage set, string memory value) internal view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(StringSet storage set) internal view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(StringSet storage set, uint256 index) internal view returns (string memory) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(StringSet storage set) internal view returns (string[] memory) {
return set._values;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) {
unchecked {
end = Math.min(end, length(set));
start = Math.min(start, end);
uint256 len = end - start;
string[] memory result = new string[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
struct BytesSet {
// Storage of set values
bytes[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(BytesSet storage set, bytes memory value) internal returns (bool) {
if (!contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes memory lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(BytesSet storage set) internal {
uint256 len = length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(BytesSet storage set) internal view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(BytesSet storage set) internal view returns (bytes[] memory) {
return set._values;
}
/**
* @dev Return a slice of the set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) {
unchecked {
end = Math.min(end, length(set));
start = Math.min(start, end);
uint256 len = end - start;
bytes[] memory result = new bytes[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
}
MerkleProof.sol 514 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: This library supports proof verification for merkle trees built using
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
* leaf inclusion in trees built using non-commutative hashing functions requires
* additional logic that is not supported by this library.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}
SuperVault.sol 633 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
// External
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
// OpenZeppelin Upgradeable
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";
import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
// Interfaces
import { ISuperVault } from "../interfaces/SuperVault/ISuperVault.sol";
import { ISuperVaultStrategy } from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
import { IERC7540Operator, IERC7540Redeem, IERC7540CancelRedeem } from "../vendor/standards/ERC7540/IERC7540Vault.sol";
import { IERC7741 } from "../vendor/standards/ERC7741/IERC7741.sol";
import { IERC7575 } from "../vendor/standards/ERC7575/IERC7575.sol";
import { ISuperVaultEscrow } from "../interfaces/SuperVault/ISuperVaultEscrow.sol";
// Libraries
import { AssetMetadataLib } from "../libraries/AssetMetadataLib.sol";
/// @title SuperVault
/// @author Superform Labs
/// @notice SuperVault vault contract implementing ERC4626 with synchronous deposits and asynchronous redeems via
/// ERC7540
contract SuperVault is Initializable, ERC20Upgradeable, ISuperVault, ReentrancyGuardUpgradeable, EIP712Upgradeable {
using AssetMetadataLib for address;
using SafeERC20 for IERC20;
using Math for uint256;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
uint256 private constant REQUEST_ID = 0;
uint256 private constant BPS_PRECISION = 10_000;
// EIP712 TypeHash
/// @notice EIP-712 typehash for operator authorization signatures
/// @dev Used to construct the digest for EIP-712 signature validation in authorizeOperator()
/// Format: "AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256
// deadline)" / - controller: The address authorizing the operator
/// - operator: The address being authorized/deauthorized
/// - approved: True to authorize, false to revoke
/// - nonce: Unique nonce for replay protection (one-time use)
/// - deadline: Timestamp after which signature expires
/// @dev This typehash MUST remain constant. Any changes invalidate all existing signatures.
/// @dev Off-chain signers must use this exact structure when creating signatures for authorizeOperator()
bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH = keccak256(
"AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)"
);
/*//////////////////////////////////////////////////////////////
STATE
//////////////////////////////////////////////////////////////*/
address public share;
IERC20 private _asset;
uint8 private _underlyingDecimals;
ISuperVaultStrategy public strategy;
address public escrow;
uint256 public PRECISION;
// Core contracts
ISuperGovernor public immutable SUPER_GOVERNOR;
/// @inheritdoc IERC7540Operator
mapping(address owner => mapping(address operator => bool)) public isOperator;
// Authorization tracking
mapping(address controller => mapping(bytes32 nonce => bool used)) private _authorizations;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address superGovernor_) {
if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
SUPER_GOVERNOR = ISuperGovernor(superGovernor_);
emit SuperGovernorSet(superGovernor_);
_disableInitializers();
}
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
/// @notice Initialize the vault with required parameters
/// @dev This function can only be called once due to initializer modifier
/// @dev SECURITY: asset, strategy, and escrow are pre-validated in SuperVaultAggregator.createVault()
/// to prevent initialization with invalid addresses. No additional validation needed here.
/// @dev PRECISION is set to 10^decimals for consistent share/asset conversions
/// @dev EIP-712 domain separator is initialized with vault name and version "1" for signature validation
/// @param asset_ The underlying asset token address (pre-validated by aggregator)
/// @param name_ The name of the vault token (used for ERC20 and EIP-712 domain)
/// @param symbol_ The symbol of the vault token
/// @param strategy_ The strategy contract address (pre-validated by aggregator)
/// @param escrow_ The escrow contract address (pre-validated by aggregator)
function initialize(
address asset_,
string memory name_,
string memory symbol_,
address strategy_,
address escrow_
)
external
initializer
{
/// @dev asset, strategy, and escrow already validated in SuperVaultAggregator during vault creation
// Initialize parent contracts
__ERC20_init(name_, symbol_);
__ReentrancyGuard_init();
__EIP712_init(name_, "1");
// Set asset and precision
_asset = IERC20(asset_);
(bool success, uint8 assetDecimals) = asset_.tryGetAssetDecimals();
if (!success) revert INVALID_ASSET();
_underlyingDecimals = assetDecimals;
PRECISION = 10 ** _underlyingDecimals;
share = address(this);
strategy = ISuperVaultStrategy(strategy_);
escrow = escrow_;
emit Initialized(asset_, strategy_, escrow_);
}
/*//////////////////////////////////////////////////////////////
ERC20 OVERRIDES
//////////////////////////////////////////////////////////////*/
/*//////////////////////////////////////////////////////////////
USER EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IERC4626
function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) {
if (receiver == address(0)) revert ZERO_ADDRESS();
if (assets == 0) revert ZERO_AMOUNT();
// Forward assets from msg.sender to strategy
_asset.safeTransferFrom(msg.sender, address(strategy), assets);
// Single executor call: strategy skims entry fee, accounts on NET, returns net shares
// Note: handleOperations4626Deposit already validates and reverts if shares == 0
shares = strategy.handleOperations4626Deposit(receiver, assets);
// Mint the net shares
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/// @inheritdoc IERC4626
function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets) {
if (receiver == address(0)) revert ZERO_ADDRESS();
if (shares == 0) revert ZERO_AMOUNT();
uint256 assetsNet;
(assets, assetsNet) = strategy.quoteMintAssetsGross(shares);
// Forward quoted gross assets from msg.sender to strategy
_asset.safeTransferFrom(msg.sender, address(strategy), assets);
// Single executor call: strategy handles fees and accounts on NET
strategy.handleOperations4626Mint(receiver, shares, assets, assetsNet);
// Mint the exact shares asked
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/// @inheritdoc IERC7540Redeem
/// @notice Once owner has authorized an operator, controller must be the owner
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256) {
if (shares == 0) revert ZERO_AMOUNT();
if (owner == address(0) || controller == address(0)) revert ZERO_ADDRESS();
_validateController(owner);
if (balanceOf(owner) < shares) revert INVALID_AMOUNT();
if (strategy.pendingCancelRedeemRequest(owner)) revert CANCELLATION_REDEEM_REQUEST_PENDING();
// Enforce auditor's invariant for current accounting model
if (controller != owner) revert CONTROLLER_MUST_EQUAL_OWNER();
// Transfer shares to escrow for temporary locking
_approve(owner, escrow, shares);
ISuperVaultEscrow(escrow).escrowShares(owner, shares);
// Forward to strategy (7540 path)
strategy.handleOperations7540(ISuperVaultStrategy.Operation.RedeemRequest, controller, address(0), shares);
emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares);
return REQUEST_ID;
}
/// @inheritdoc IERC7540CancelRedeem
function cancelRedeemRequest(
uint256,
/*requestId*/
address controller
)
external
{
_validateController(controller);
// Forward to strategy (7540 path)
strategy.handleOperations7540(ISuperVaultStrategy.Operation.CancelRedeemRequest, controller, address(0), 0);
emit CancelRedeemRequest(controller, REQUEST_ID, msg.sender);
}
/// @inheritdoc IERC7540CancelRedeem
function claimCancelRedeemRequest(
uint256, /*requestId*/
address receiver,
address controller
)
external
returns (uint256 shares)
{
if (receiver == address(0) || controller == address(0)) revert ZERO_ADDRESS();
_validateControllerAndReceiver(controller, receiver);
shares = strategy.claimableCancelRedeemRequest(controller);
// Forward to strategy (7540 path)
strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimCancelRedeem, controller, address(0), 0);
// Return shares to controller
ISuperVaultEscrow(escrow).returnShares(receiver, shares);
emit CancelRedeemClaim(receiver, controller, REQUEST_ID, msg.sender, shares);
}
/// @inheritdoc IERC7540Operator
function setOperator(address operator, bool approved) external returns (bool success) {
if (msg.sender == operator) revert UNAUTHORIZED();
isOperator[msg.sender][operator] = approved;
emit OperatorSet(msg.sender, operator, approved);
return true;
}
/// @inheritdoc IERC7741
function authorizeOperator(
address controller,
address operator,
bool approved,
bytes32 nonce,
uint256 deadline,
bytes memory signature
)
external
returns (bool)
{
if (controller == operator) revert UNAUTHORIZED();
if (block.timestamp > deadline) revert DEADLINE_PASSED();
if (_authorizations[controller][nonce]) revert UNAUTHORIZED();
_authorizations[controller][nonce] = true;
bytes32 structHash =
keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline));
bytes32 digest = _hashTypedDataV4(structHash);
if (!_isValidSignature(controller, digest, signature)) revert INVALID_SIGNATURE();
isOperator[controller][operator] = approved;
emit OperatorSet(controller, operator, approved);
return true;
}
/*//////////////////////////////////////////////////////////////
USER EXTERNAL VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVault
function getEscrowedAssets() external view returns (uint256) {
return _asset.balanceOf(escrow);
}
//--ERC7540--
/// @inheritdoc IERC7540Redeem
function pendingRedeemRequest(
uint256, /*requestId*/
address controller
)
external
view
returns (uint256 pendingShares)
{
return strategy.pendingRedeemRequest(controller);
}
/// @inheritdoc IERC7540Redeem
function claimableRedeemRequest(
uint256, /*requestId*/
address controller
)
external
view
returns (uint256 claimableShares)
{
return maxRedeem(controller);
}
/// @inheritdoc IERC7540CancelRedeem
function pendingCancelRedeemRequest(
uint256,
/*requestId*/
address controller
)
external
view
returns (bool isPending)
{
isPending = strategy.pendingCancelRedeemRequest(controller);
}
/// @inheritdoc IERC7540CancelRedeem
function claimableCancelRedeemRequest(
uint256, /*requestId*/
address controller
)
external
view
returns (uint256 claimableShares)
{
return strategy.claimableCancelRedeemRequest(controller);
}
//--Operator Management--
/// @inheritdoc IERC7741
function authorizations(address controller, bytes32 nonce) external view returns (bool used) {
return _authorizations[controller][nonce];
}
/// @inheritdoc IERC7741
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return _domainSeparatorV4();
}
/// @inheritdoc IERC7741
function invalidateNonce(bytes32 nonce) external {
if (_authorizations[msg.sender][nonce]) revert INVALID_NONCE();
_authorizations[msg.sender][nonce] = true;
emit NonceInvalidated(msg.sender, nonce);
}
/*//////////////////////////////////////////////////////////////
ERC4626 IMPLEMENTATION
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IERC20Metadata
function decimals() public view virtual override(ERC20Upgradeable, IERC20Metadata) returns (uint8) {
return _underlyingDecimals;
}
/// @inheritdoc IERC4626
function asset() public view virtual override returns (address) {
return address(_asset);
}
/// @inheritdoc IERC4626
function totalAssets() external view override returns (uint256) {
uint256 supply = totalSupply();
if (supply == 0) return 0;
uint256 currentPPS = _getStoredPPS();
return Math.mulDiv(supply, currentPPS, PRECISION, Math.Rounding.Floor);
}
/// @inheritdoc IERC4626
function convertToShares(uint256 assets) public view override returns (uint256) {
uint256 pps = _getStoredPPS();
return pps == 0 ? 0 : Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor);
}
/// @inheritdoc IERC4626
function convertToAssets(uint256 shares) public view override returns (uint256) {
uint256 currentPPS = _getStoredPPS();
return currentPPS == 0 ? 0 : Math.mulDiv(shares, currentPPS, PRECISION, Math.Rounding.Floor);
}
/// @inheritdoc IERC4626
function maxDeposit(address) public view override returns (uint256) {
if (!_canAcceptDeposits()) return 0;
return type(uint256).max;
}
/// @inheritdoc IERC4626
function maxMint(address) external view override returns (uint256) {
if (!_canAcceptDeposits()) return 0;
return type(uint256).max;
}
/// @inheritdoc IERC4626
function maxWithdraw(address owner) public view override returns (uint256) {
return strategy.claimableWithdraw(owner);
}
/// @inheritdoc IERC4626
function maxRedeem(address owner) public view override returns (uint256) {
uint256 withdrawPrice = strategy.getAverageWithdrawPrice(owner);
if (withdrawPrice == 0) return 0;
return maxWithdraw(owner).mulDiv(PRECISION, withdrawPrice, Math.Rounding.Floor);
}
/// @inheritdoc IERC4626
function previewDeposit(uint256 assets) public view override returns (uint256) {
uint256 pps = _getStoredPPS();
if (pps == 0) return 0;
(uint256 feeBps,) = _getManagementFeeConfig();
if (feeBps == 0) return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor);
// fee-on-gross: fee = ceil(gross * feeBps / BPS)
uint256 fee = Math.mulDiv(assets, feeBps, BPS_PRECISION, Math.Rounding.Ceil);
uint256 assetsNet = assets - fee;
return Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor);
}
/// @inheritdoc IERC4626
/// @dev Returns gross assets required to mint exact shares after management fees
/// @dev Formula: gross = net * BPS_PRECISION / (BPS_PRECISION - feeBps)
/// @dev Edge case: If feeBps >= 100% (10000), returns 0 (impossible to mint with 100%+ fees)
/// This prevents division by zero and represents mathematical impossibility.
function previewMint(uint256 shares) public view override returns (uint256) {
uint256 pps = _getStoredPPS();
if (pps == 0) return 0;
uint256 assetsGross = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil);
(uint256 feeBps,) = _getManagementFeeConfig();
if (feeBps == 0) return assetsGross;
if (feeBps >= BPS_PRECISION) return 0; // impossible to mint (would require infinite gross)
return Math.mulDiv(assetsGross, BPS_PRECISION, (BPS_PRECISION - feeBps), Math.Rounding.Ceil);
}
/// @inheritdoc IERC4626
function previewWithdraw(
uint256 /* assets*/
)
public
pure
override
returns (uint256)
{
revert NOT_IMPLEMENTED();
}
/// @inheritdoc IERC4626
function previewRedeem(
uint256 /* shares*/
)
public
pure
override
returns (uint256)
{
revert NOT_IMPLEMENTED();
}
/// @inheritdoc IERC4626
function withdraw(
uint256 assets,
address receiver,
address controller
)
public
override
nonReentrant
returns (uint256 shares)
{
if (receiver == address(0) || controller == address(0)) revert ZERO_ADDRESS();
_validateControllerAndReceiver(controller, receiver);
uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller);
if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE();
uint256 maxWithdrawAmount = maxWithdraw(controller);
if (assets > maxWithdrawAmount) revert INVALID_AMOUNT();
// Calculate shares based on assets and average withdraw price
shares = assets.mulDiv(PRECISION, averageWithdrawPrice, Math.Rounding.Ceil);
uint256 escrowBalance = _asset.balanceOf(escrow);
if (assets > escrowBalance) revert NOT_ENOUGH_ASSETS();
// Update strategy state (7540 path)
strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets);
// Transfer assets from escrow to receiver
ISuperVaultEscrow(escrow).returnAssets(receiver, assets);
emit Withdraw(msg.sender, receiver, controller, assets, shares);
}
/// @inheritdoc IERC4626
function redeem(
uint256 shares,
address receiver,
address controller
)
public
override
nonReentrant
returns (uint256 assets)
{
if (receiver == address(0) || controller == address(0)) revert ZERO_ADDRESS();
_validateControllerAndReceiver(controller, receiver);
uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller);
if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE();
// Calculate assets based on shares and average withdraw price
assets = shares.mulDiv(averageWithdrawPrice, PRECISION, Math.Rounding.Floor);
uint256 maxWithdrawAmount = maxWithdraw(controller);
if (assets > maxWithdrawAmount) revert INVALID_AMOUNT();
uint256 escrowBalance = _asset.balanceOf(escrow);
if (assets > escrowBalance) revert NOT_ENOUGH_ASSETS();
// Update strategy state (7540 path)
strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets);
// Transfer assets from escrow to receiver
ISuperVaultEscrow(escrow).returnAssets(receiver, assets);
emit Withdraw(msg.sender, receiver, controller, assets, shares);
}
/// @inheritdoc ISuperVault
function burnShares(uint256 amount) external {
if (msg.sender != address(strategy)) revert UNAUTHORIZED();
_burn(escrow, amount);
}
/*//////////////////////////////////////////////////////////////
ERC165 INTERFACE
//////////////////////////////////////////////////////////////*/
/// @notice Checks if contract supports a given interface
/// @dev Implements ERC165 for ERC7540, ERC7741, ERC4626, ERC7575 support detection
/// @param interfaceId The interface identifier to check
/// @return True if the interface is supported, false otherwise
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
return interfaceId == type(IERC7540Redeem).interfaceId || interfaceId == type(IERC165).interfaceId
|| interfaceId == type(IERC7741).interfaceId || interfaceId == type(IERC4626).interfaceId
|| interfaceId == type(IERC7575).interfaceId || interfaceId == type(IERC7540Operator).interfaceId;
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Validates that the caller is authorized to act on behalf of the controller
/// @dev Enforces ERC7540Operator pattern: either direct call from controller or authorized operator
/// @dev Operators must be authorized via setOperator() or authorizeOperator() (EIP-712 signature)
/// @dev Used in redemption flows to prevent unauthorized claims
/// @param controller The controller address to validate authorization for
/// @dev Reverts with INVALID_CONTROLLER if:
/// - caller is not the controller AND
/// - caller is not an authorized operator for the controller
function _validateController(address controller) internal view {
if (controller != msg.sender && !_isOperator(controller, msg.sender)) revert INVALID_CONTROLLER();
}
/// @notice Validates controller authorization and enforces operator receiver restrictions
/// @dev Controllers can set any receiver; operators must set receiver == controller
/// @param controller The controller address to validate authorization for
/// @param receiver The receiver address to validate against operator restrictions
function _validateControllerAndReceiver(address controller, address receiver) internal view {
// If caller is controller, all good
if (controller == msg.sender) return;
// Caller is not controller, must be operator
if (!_isOperator(controller, msg.sender)) revert INVALID_CONTROLLER();
// Caller is operator, enforce receiver == controller
if (receiver != controller) revert RECEIVER_MUST_EQUAL_CONTROLLER();
}
function _isOperator(address controller, address operator) internal view returns (bool) {
return isOperator[controller][operator];
}
/// @notice Verify an EIP712 signature using OpenZeppelin's ECDSA library
/// @param signer The signer to verify
/// @param digest The digest to verify
/// @param signature The signature to verify
function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal pure returns (bool) {
address recoveredSigner = ECDSA.recover(digest, signature);
return recoveredSigner == signer;
}
function _getStoredPPS() internal view returns (uint256) {
return strategy.getStoredPPS();
}
/// @notice Combined check for deposits acceptance
/// @dev Reduces external calls by fetching aggregator address once
/// @dev Previously: 4 external calls (2x getAddress + 2x aggregator checks)
/// @dev Now: 3 external calls (1x getAddress + 2x aggregator checks)
/// @return True if deposits can be accepted (not paused and PPS not stale)
function _canAcceptDeposits() internal view returns (bool) {
address aggregatorAddress = _getAggregatorAddress();
ISuperVaultAggregator aggregator = ISuperVaultAggregator(aggregatorAddress);
return !aggregator.isStrategyPaused(address(strategy)) && !aggregator.isPPSStale(address(strategy));
}
/// @notice Helper to get aggregator address once
/// @return Address of the SuperVaultAggregator contract
function _getAggregatorAddress() internal view returns (address) {
return SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_VAULT_AGGREGATOR());
}
/// @dev Read management fee config (view-only for previews)
function _getManagementFeeConfig() internal view returns (uint256 feeBps, address recipient) {
ISuperVaultStrategy.FeeConfig memory cfg = strategy.getConfigInfo();
return (cfg.managementFeeBps, cfg.recipient);
}
}
SuperVaultStrategy.sol 1135 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
// External
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { LibSort } from "solady/utils/LibSort.sol";
// Core Interfaces
import {
ISuperHook,
ISuperHookResult,
ISuperHookContextAware,
ISuperHookInspector
} from "@superform-v2-core/src/interfaces/ISuperHook.sol";
// Periphery Interfaces
import { ISuperVault } from "../interfaces/SuperVault/ISuperVault.sol";
import { HookDataDecoder } from "@superform-v2-core/src/libraries/HookDataDecoder.sol";
import { ISuperVaultStrategy } from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
import { ISuperGovernor, FeeType } from "../interfaces/ISuperGovernor.sol";
import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
import { SuperVaultAccountingLib } from "../libraries/SuperVaultAccountingLib.sol";
import { AssetMetadataLib } from "../libraries/AssetMetadataLib.sol";
/// @title SuperVaultStrategy
/// @author Superform Labs
/// @notice Strategy implementation for SuperVault that executes strategies
contract SuperVaultStrategy is ISuperVaultStrategy, Initializable, ReentrancyGuardUpgradeable {
using LibSort for address[];
using EnumerableSet for EnumerableSet.AddressSet;
using SafeERC20 for IERC20;
using Math for uint256;
using AssetMetadataLib for address;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
uint256 private constant BPS_PRECISION = 10_000;
uint256 private constant MAX_PERFORMANCE_FEE = 5100; // 51% max performance fee
/// @dev Default redeem slippage tolerance when user hasn't set their own (0.5%)
uint16 public constant DEFAULT_REDEEM_SLIPPAGE_BPS = 50;
/// @dev Minimum allowed staleness threshold for PPS updates (prevents too-frequent validation)
uint256 private constant MIN_PPS_EXPIRATION_THRESHOLD = 1 minutes;
/// @dev Maximum allowed staleness threshold for PPS updates (prevents indefinite stale data usage)
uint256 private constant MAX_PPS_EXPIRATION_THRESHOLD = 1 weeks;
/// @dev Timelock period after unpause during which performance fee skimming is disabled (rug prevention)
uint256 private constant POST_UNPAUSE_SKIM_TIMELOCK = 12 hours;
/// @dev Timelock duration for fee config and PPS expiration threshold updates
uint256 private constant PROPOSAL_TIMELOCK = 1 weeks;
uint256 public PRECISION; // Slot 0: 32 bytes
/*//////////////////////////////////////////////////////////////
STATE
//////////////////////////////////////////////////////////////*/
// Packed slot 1: saves 1 storage slot
address private _vault; // 20 bytes
uint8 private _vaultDecimals; // 1 byte
uint88 private __gap1; // 11 bytes padding
// Packed slot 2
IERC20 private _asset; // 20 bytes (address)
uint96 private __gap2; // 12 bytes padding
// Global configuration
// Fee configuration
FeeConfig private feeConfig; // Slots 3-5 (96 bytes: 2 uint256 + 1 address)
FeeConfig private proposedFeeConfig;
uint256 private feeConfigEffectiveTime;
// Core contracts
ISuperGovernor public immutable SUPER_GOVERNOR;
// PPS expiry threshold
uint256 public proposedPPSExpiryThreshold;
uint256 public ppsExpiryThresholdEffectiveTime;
uint256 public ppsExpiration;
// Yield source configuration - simplified mapping from source to oracle
mapping(address source => address oracle) private yieldSources;
EnumerableSet.AddressSet private yieldSourcesList;
// --- Global Vault High-Water Mark (PPS-based) ---
/// @notice High-water mark price-per-share for performance fee calculation
/// @dev Represents the PPS at which performance fees were last collected
/// Scaled by PRECISION (e.g., 1e6 for USDC vaults, 1e18 for 18-decimal vaults)
/// Updated during skimPerformanceFee() when fees are taken, and in executeVaultFeeConfigUpdate()
uint256 public vaultHwmPps;
// --- Redeem Request State ---
mapping(address controller => SuperVaultState state) private superVaultState;
constructor(address superGovernor_) {
if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
SUPER_GOVERNOR = ISuperGovernor(superGovernor_);
emit SuperGovernorSet(superGovernor_);
_disableInitializers();
}
/// @notice Allows the contract to receive native ETH
/// @dev Required for hooks that may send ETH back to the strategy
receive() external payable { }
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
function initialize(address vaultAddress, FeeConfig memory feeConfigData) external initializer {
if (vaultAddress == address(0)) revert INVALID_VAULT();
// if either fee is configured, check if recipient is address (0), if it is revert with ZERO ADDRESS
// if both fees are 0, no need check address (it just passes the if). Recipient can be configured later
if (
(feeConfigData.performanceFeeBps > 0 || feeConfigData.managementFeeBps > 0)
&& feeConfigData.recipient == address(0)
) revert ZERO_ADDRESS();
if (feeConfigData.performanceFeeBps > MAX_PERFORMANCE_FEE) revert INVALID_PERFORMANCE_FEE_BPS();
if (feeConfigData.managementFeeBps > BPS_PRECISION) revert INVALID_PERFORMANCE_FEE_BPS();
__ReentrancyGuard_init();
_vault = vaultAddress;
_asset = IERC20(IERC4626(vaultAddress).asset());
_vaultDecimals = IERC20Metadata(vaultAddress).decimals();
PRECISION = 10 ** _vaultDecimals;
feeConfig = feeConfigData;
ppsExpiration = 1 days;
// Initialize HWM to 1.0 using asset decimals (same as aggregator)
// Get asset decimals the same way aggregator does
(bool success, uint8 assetDecimals) = address(_asset).tryGetAssetDecimals();
if (!success) revert INVALID_ASSET();
vaultHwmPps = 10 ** assetDecimals; // 1.0 as initial PPS (matches aggregator)
emit Initialized(_vault);
}
/*//////////////////////////////////////////////////////////////
CORE STRATEGY OPERATIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultStrategy
function handleOperations4626Deposit(address controller, uint256 assetsGross) external returns (uint256 sharesNet) {
_requireVault();
if (assetsGross == 0) revert INVALID_AMOUNT();
if (controller == address(0)) revert ZERO_ADDRESS();
ISuperVaultAggregator aggregator = _getSuperVaultAggregator();
if (aggregator.isGlobalHooksRootVetoed()) {
revert OPERATIONS_BLOCKED_BY_VETO();
}
_validateStrategyState(aggregator);
// Fee skim in ASSETS (asset-side entry fee)
uint256 feeBps = feeConfig.managementFeeBps;
uint256 feeAssets = feeBps == 0 ? 0 : Math.mulDiv(assetsGross, feeBps, BPS_PRECISION, Math.Rounding.Ceil);
uint256 assetsNet = assetsGross - feeAssets;
if (assetsNet == 0) revert INVALID_AMOUNT();
if (feeAssets != 0) {
address recipient = feeConfig.recipient;
if (recipient == address(0)) revert ZERO_ADDRESS();
_safeTokenTransfer(address(_asset), recipient, feeAssets);
emit ManagementFeePaid(controller, recipient, feeAssets, feeBps);
}
// Compute shares on NET using current PPS
uint256 pps = getStoredPPS();
if (pps == 0) revert INVALID_PPS();
sharesNet = Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor);
if (sharesNet == 0) revert INVALID_AMOUNT();
// No HWM update needed - deposits are PPS-neutral by design
emit DepositHandled(controller, assetsNet, sharesNet);
return sharesNet;
}
/// @inheritdoc ISuperVaultStrategy
function handleOperations4626Mint(
address controller,
uint256 sharesNet,
uint256 assetsGross,
uint256 assetsNet
)
external
{
_requireVault();
if (sharesNet == 0) revert INVALID_AMOUNT();
if (controller == address(0)) revert ZERO_ADDRESS();
ISuperVaultAggregator aggregator = _getSuperVaultAggregator();
if (aggregator.isGlobalHooksRootVetoed()) {
revert OPERATIONS_BLOCKED_BY_VETO();
}
_validateStrategyState(aggregator);
uint256 feeBps = feeConfig.managementFeeBps;
// Transfer fee if needed
if (feeBps != 0) {
uint256 feeAssets = assetsGross - assetsNet;
if (feeAssets != 0) {
address recipient = feeConfig.recipient;
if (recipient == address(0)) revert ZERO_ADDRESS();
_safeTokenTransfer(address(_asset), recipient, feeAssets);
emit ManagementFeePaid(controller, recipient, feeAssets, feeBps);
}
}
// No HWM update needed - mints are PPS-neutral by design
emit DepositHandled(controller, assetsNet, sharesNet);
}
/// @inheritdoc ISuperVaultStrategy
function quoteMintAssetsGross(uint256 shares) external view returns (uint256 assetsGross, uint256 assetsNet) {
uint256 pps = getStoredPPS();
if (pps == 0) revert INVALID_PPS();
assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil);
if (assetsNet == 0) revert INVALID_AMOUNT();
uint256 feeBps = feeConfig.managementFeeBps;
if (feeBps == 0) return (assetsNet, assetsNet);
if (feeBps >= BPS_PRECISION) revert INVALID_AMOUNT(); // prevents div-by-zero (100% fee)
assetsGross = Math.mulDiv(assetsNet, BPS_PRECISION, (BPS_PRECISION - feeBps), Math.Rounding.Ceil);
return (assetsGross, assetsNet);
}
/// @inheritdoc ISuperVaultStrategy
function handleOperations7540(Operation operation, address controller, address receiver, uint256 amount) external {
_requireVault();
ISuperVaultAggregator aggregator = _getSuperVaultAggregator();
if (operation == Operation.RedeemRequest) {
_validateStrategyState(aggregator);
_handleRequestRedeem(controller, amount); // amount = shares
} else if (operation == Operation.ClaimCancelRedeem) {
_handleClaimCancelRedeem(controller);
} else if (operation == Operation.ClaimRedeem) {
_handleClaimRedeem(controller, receiver, amount); // amount = assets
} else if (operation == Operation.CancelRedeemRequest) {
_handleCancelRedeemRequest(controller);
} else {
revert ACTION_TYPE_DISALLOWED();
}
}
/*//////////////////////////////////////////////////////////////
MANAGER EXTERNAL ACCESS FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultStrategy
function executeHooks(ExecuteArgs calldata args) external payable nonReentrant {
_isManager(msg.sender);
uint256 hooksLength = args.hooks.length;
if (hooksLength == 0) revert ZERO_LENGTH();
if (args.hookCalldata.length != hooksLength) revert INVALID_ARRAY_LENGTH();
if (args.expectedAssetsOrSharesOut.length != hooksLength) revert INVALID_ARRAY_LENGTH();
if (args.globalProofs.length != hooksLength) revert INVALID_ARRAY_LENGTH();
if (args.strategyProofs.length != hooksLength) revert INVALID_ARRAY_LENGTH();
address prevHook;
for (uint256 i; i < hooksLength; ++i) {
address hook = args.hooks[i];
if (!_isRegisteredHook(hook)) revert INVALID_HOOK();
// Check if the hook was validated
if (!_validateHook(hook, args.hookCalldata[i], args.globalProofs[i], args.strategyProofs[i])) {
revert HOOK_VALIDATION_FAILED();
}
prevHook =
_processSingleHookExecution(hook, prevHook, args.hookCalldata[i], args.expectedAssetsOrSharesOut[i]);
}
emit HooksExecuted(args.hooks);
}
/// @inheritdoc ISuperVaultStrategy
function fulfillCancelRedeemRequests(address[] memory controllers) external nonReentrant {
_isManager(msg.sender);
uint256 controllersLength = controllers.length;
if (controllersLength == 0) revert ZERO_LENGTH();
for (uint256 i; i < controllersLength; ++i) {
SuperVaultState storage state = superVaultState[controllers[i]];
if (state.pendingCancelRedeemRequest) {
state.claimableCancelRedeemRequest += state.pendingRedeemRequest;
state.pendingRedeemRequest = 0;
state.averageRequestPPS = 0;
emit RedeemCancelRequestFulfilled(controllers[i], state.claimableCancelRedeemRequest);
}
}
}
/// @inheritdoc ISuperVaultStrategy
function fulfillRedeemRequests(
address[] calldata controllers,
uint256[] calldata totalAssetsOut
)
external
nonReentrant
{
_isManager(msg.sender);
_validateStrategyState(_getSuperVaultAggregator());
uint256 len = controllers.length;
if (len == 0 || totalAssetsOut.length != len) revert INVALID_ARRAY_LENGTH();
FulfillRedeemVars memory vars;
vars.currentPPS = getStoredPPS();
if (vars.currentPPS == 0) revert INVALID_PPS();
// Process each controller with all validations in one loop
for (uint256 i; i < len; ++i) {
// Validate controllers are sorted and unique
if (i > 0 && controllers[i] <= controllers[i - 1]) revert CONTROLLERS_NOT_SORTED_UNIQUE();
// Load pending shares into memory and accumulate total
uint256 pendingShares = superVaultState[controllers[i]].pendingRedeemRequest;
vars.totalRequestedShares += pendingShares;
// Disallow fulfillment for controllers with zero pending shares
if (pendingShares == 0) revert ZERO_SHARE_FULFILLMENT_DISALLOWED();
// Process fulfillment and accumulate assets
_processExactFulfillmentBatch(controllers[i], totalAssetsOut[i], vars.currentPPS, pendingShares);
vars.totalNetAssetsOut += totalAssetsOut[i];
}
// Balance check (no fees expected)
vars.strategyBalance = _getTokenBalance(address(_asset), address(this));
if (vars.strategyBalance < vars.totalNetAssetsOut) {
revert INSUFFICIENT_LIQUIDITY();
}
// Burn shares
ISuperVault(_vault).burnShares(vars.totalRequestedShares);
// Transfer net assets to escrow
if (vars.totalNetAssetsOut > 0) {
_asset.safeTransfer(ISuperVault(_vault).escrow(), vars.totalNetAssetsOut);
}
emit RedeemRequestsFulfilled(controllers, vars.totalRequestedShares, vars.currentPPS);
}
/// @notice Skim performance fees based on per-share High Water Mark
/// @dev Can be called by any manager when vault PPS has grown above HWM
/// @dev Uses PPS-based HWM which eliminates redemption-related vulnerabilities
function skimPerformanceFee() external nonReentrant {
_isManager(msg.sender);
ISuperVaultAggregator aggregator = _getSuperVaultAggregator();
_validateStrategyState(aggregator);
// Prevent skim for 12 hours after unpause
// This timelock gives a detection window for potential abuse of fee skimming
// post unpausing with an abnormal PPS update
uint256 lastUnpause = aggregator.getLastUnpauseTimestamp(address(this));
if (block.timestamp < lastUnpause + POST_UNPAUSE_SKIM_TIMELOCK) {
revert SKIM_TIMELOCK_ACTIVE();
}
IERC4626 vault = IERC4626(_vault);
uint256 totalSupplyLocal = vault.totalSupply();
// Early return if no supply - cannot calculate PPS or collect fees
if (totalSupplyLocal == 0) return;
// Get current PPS from aggregator
uint256 currentPPS = aggregator.getPPS(address(this));
if (currentPPS == 0) revert INVALID_PPS();
// Get the high-water mark PPS (baseline for fee calculation)
uint256 hwmPps = vaultHwmPps;
// Check if there's any per-share growth above HWM
if (currentPPS <= hwmPps) {
// No growth above HWM, no fee to collect
return;
}
// Calculate PPS growth above HWM
uint256 ppsGrowth = currentPPS - hwmPps;
// Calculate total profit: (PPS growth) * (total shares) / PRECISION
// This represents the total assets gained above the high-water mark
uint256 profit = Math.mulDiv(ppsGrowth, totalSupplyLocal, PRECISION, Math.Rounding.Floor);
// Safety check: profit must be non-zero to collect fees
if (profit == 0) return;
// Calculate fee as percentage of profit
uint256 fee = Math.mulDiv(profit, feeConfig.performanceFeeBps, BPS_PRECISION, Math.Rounding.Ceil);
// Edge case: profit exists but fee rounds to zero
if (fee == 0) return;
// Split fee between Superform treasury and strategy recipient
uint256 sfFee =
Math.mulDiv(fee, SUPER_GOVERNOR.getFee(FeeType.PERFORMANCE_FEE_SHARE), BPS_PRECISION, Math.Rounding.Floor);
uint256 recipientFee = fee - sfFee;
// Check if strategy has sufficient liquid assets for fee transfer
if (_getTokenBalance(address(_asset), address(this)) < fee) revert NOT_ENOUGH_FREE_ASSETS_FEE_SKIM();
// Transfer fees to recipients
_safeTokenTransfer(address(_asset), SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.TREASURY()), sfFee);
_safeTokenTransfer(address(_asset), feeConfig.recipient, recipientFee);
emit PerformanceFeeSkimmed(fee, sfFee);
// Calculate the new PPS after fee extraction
// Fee extraction reduces vault assets while shares stay constant, lowering PPS
uint256 ppsReduction = Math.mulDiv(fee, PRECISION, totalSupplyLocal, Math.Rounding.Floor);
// Safety check: ensure reduction doesn't crash PPS to zero
if (ppsReduction >= currentPPS) revert INVALID_PPS();
uint256 newPPS = currentPPS - ppsReduction;
// Safety check: new PPS must be positive
if (newPPS == 0) revert INVALID_PPS();
// Update HWM to the new post-fee PPS
// This becomes the new baseline for future fee calculations
vaultHwmPps = newPPS;
emit HWMPPSUpdated(newPPS, currentPPS, profit, fee);
// Update PPS in aggregator to reflect fee extraction
aggregator.updatePPSAfterSkim(newPPS, fee);
}
/*//////////////////////////////////////////////////////////////
YIELD SOURCE MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultStrategy
function manageYieldSource(address source, address oracle, YieldSourceAction actionType) external {
_isPrimaryManager(msg.sender);
_manageYieldSource(source, oracle, actionType);
}
/// @inheritdoc ISuperVaultStrategy
function manageYieldSources(
address[] calldata sources,
address[] calldata oracles,
YieldSourceAction[] calldata actionTypes
)
external
{
_isPrimaryManager(msg.sender);
uint256 length = sources.length;
if (length == 0) revert ZERO_LENGTH();
if (oracles.length != length) revert INVALID_ARRAY_LENGTH();
if (actionTypes.length != length) revert INVALID_ARRAY_LENGTH();
for (uint256 i; i < length; ++i) {
_manageYieldSource(sources[i], oracles[i], actionTypes[i]);
}
}
/// @inheritdoc ISuperVaultStrategy
function changeFeeRecipient(address newRecipient) external {
if (msg.sender != address(_getSuperVaultAggregator())) revert ACCESS_DENIED();
feeConfig.recipient = newRecipient;
emit FeeRecipientChanged(newRecipient);
}
/// @inheritdoc ISuperVaultStrategy
function proposeVaultFeeConfigUpdate(
uint256 performanceFeeBps,
uint256 managementFeeBps,
address recipient
)
external
{
_isPrimaryManager(msg.sender);
if (performanceFeeBps > MAX_PERFORMANCE_FEE) revert INVALID_PERFORMANCE_FEE_BPS();
if (managementFeeBps > BPS_PRECISION) revert INVALID_PERFORMANCE_FEE_BPS();
if (recipient == address(0)) revert ZERO_ADDRESS();
proposedFeeConfig = FeeConfig({
performanceFeeBps: performanceFeeBps, managementFeeBps: managementFeeBps, recipient: recipient
});
feeConfigEffectiveTime = block.timestamp + PROPOSAL_TIMELOCK;
emit VaultFeeConfigProposed(performanceFeeBps, managementFeeBps, recipient, feeConfigEffectiveTime);
}
/// @inheritdoc ISuperVaultStrategy
function executeVaultFeeConfigUpdate() external {
_isPrimaryManager(msg.sender);
if (block.timestamp < feeConfigEffectiveTime) revert INVALID_TIMESTAMP();
if (proposedFeeConfig.recipient == address(0)) revert ZERO_ADDRESS();
// Get current PPS before updating fee config
uint256 currentPPS = getStoredPPS();
uint256 oldHwmPps = vaultHwmPps;
// Update fee config
feeConfig = proposedFeeConfig;
delete proposedFeeConfig;
feeConfigEffectiveTime = 0;
// Reset HWM PPS to current PPS to avoid incorrect fee calculations with new fee structure
vaultHwmPps = currentPPS;
emit VaultFeeConfigUpdated(feeConfig.performanceFeeBps, feeConfig.managementFeeBps, feeConfig.recipient);
emit HWMPPSUpdated(currentPPS, oldHwmPps, 0, 0);
}
/// @inheritdoc ISuperVaultStrategy
function resetHighWaterMark(uint256 newHwmPps) external {
if (msg.sender != address(_getSuperVaultAggregator())) revert ACCESS_DENIED();
if (newHwmPps == 0) revert INVALID_PPS();
vaultHwmPps = newHwmPps;
emit HighWaterMarkReset(newHwmPps);
}
/// @inheritdoc ISuperVaultStrategy
function managePPSExpiration(PPSExpirationAction action, uint256 staleness_) external {
if (action == PPSExpirationAction.Propose) {
_proposePPSExpiration(staleness_);
} else if (action == PPSExpirationAction.Execute) {
_updatePPSExpiration();
} else if (action == PPSExpirationAction.Cancel) {
_cancelPPSExpirationProposalUpdate();
}
}
/*//////////////////////////////////////////////////////////////
USER OPERATIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultStrategy
function setRedeemSlippage(uint16 slippageBps) external {
if (slippageBps > BPS_PRECISION) revert INVALID_REDEEM_SLIPPAGE_BPS();
superVaultState[msg.sender].redeemSlippageBps = slippageBps;
emit RedeemSlippageSet(msg.sender, slippageBps);
}
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultStrategy
function getVaultInfo() external view returns (address vault, address asset, uint8 vaultDecimals) {
vault = _vault;
asset = address(_asset);
vaultDecimals = _vaultDecimals;
}
/// @inheritdoc ISuperVaultStrategy
function getConfigInfo() external view returns (FeeConfig memory feeConfig_) {
feeConfig_ = feeConfig;
}
/// @inheritdoc ISuperVaultStrategy
function getStoredPPS() public view returns (uint256) {
return _getSuperVaultAggregator().getPPS(address(this));
}
/// @inheritdoc ISuperVaultStrategy
function getSuperVaultState(address controller) external view returns (SuperVaultState memory state) {
return superVaultState[controller];
}
/// @inheritdoc ISuperVaultStrategy
function getYieldSource(address source) external view returns (YieldSource memory) {
return YieldSource({ oracle: yieldSources[source] });
}
/// @inheritdoc ISuperVaultStrategy
function getYieldSourcesList() external view returns (YieldSourceInfo[] memory) {
uint256 length = yieldSourcesList.length();
YieldSourceInfo[] memory sourcesInfo = new YieldSourceInfo[](length);
for (uint256 i; i < length; ++i) {
address sourceAddress = yieldSourcesList.at(i);
address oracle = yieldSources[sourceAddress];
sourcesInfo[i] = YieldSourceInfo({ sourceAddress: sourceAddress, oracle: oracle });
}
return sourcesInfo;
}
/// @inheritdoc ISuperVaultStrategy
function getYieldSources() external view returns (address[] memory) {
return yieldSourcesList.values();
}
/// @inheritdoc ISuperVaultStrategy
function getYieldSourcesCount() external view returns (uint256) {
return yieldSourcesList.length();
}
/// @notice Get the current unrealized profit above the High Water Mark
/// @return profit Current profit above High Water Mark (in assets), 0 if no profit
/// @dev Calculates based on PPS growth: (currentPPS - hwmPPS) * totalSupply / PRECISION
function vaultUnrealizedProfit() external view returns (uint256) {
IERC4626 vault = IERC4626(_vault);
uint256 totalSupplyLocal = vault.totalSupply();
// No profit if no shares exist
if (totalSupplyLocal == 0) return 0;
uint256 currentPPS = _getSuperVaultAggregator().getPPS(address(this));
// No profit if current PPS is at or below HWM
if (currentPPS <= vaultHwmPps) return 0;
// Calculate profit as: (PPS growth) * (shares) / PRECISION
uint256 ppsGrowth = currentPPS - vaultHwmPps;
return Math.mulDiv(ppsGrowth, totalSupplyLocal, PRECISION, Math.Rounding.Floor);
}
/// @inheritdoc ISuperVaultStrategy
function containsYieldSource(address source) external view returns (bool) {
return yieldSourcesList.contains(source);
}
/// @inheritdoc ISuperVaultStrategy
function pendingRedeemRequest(address controller) external view returns (uint256 pendingShares) {
return superVaultState[controller].pendingRedeemRequest;
}
/// @inheritdoc ISuperVaultStrategy
function claimableWithdraw(address controller) external view returns (uint256 claimableAssets) {
return superVaultState[controller].maxWithdraw;
}
/// @inheritdoc ISuperVaultStrategy
function pendingCancelRedeemRequest(address controller) external view returns (bool) {
return superVaultState[controller].pendingCancelRedeemRequest;
}
/// @inheritdoc ISuperVaultStrategy
function claimableCancelRedeemRequest(address controller) external view returns (uint256 claimableShares) {
if (!superVaultState[controller].pendingCancelRedeemRequest) return 0;
return superVaultState[controller].claimableCancelRedeemRequest;
}
/// @inheritdoc ISuperVaultStrategy
function getAverageWithdrawPrice(address controller) external view returns (uint256 averageWithdrawPrice) {
return superVaultState[controller].averageWithdrawPrice;
}
/// @inheritdoc ISuperVaultStrategy
function previewExactRedeem(address controller)
external
view
returns (uint256 shares, uint256 theoreticalAssets, uint256 minAssets)
{
SuperVaultState memory state = superVaultState[controller];
shares = state.pendingRedeemRequest;
if (shares == 0) return (0, 0, 0);
uint256 pps = getStoredPPS();
theoreticalAssets = shares.mulDiv(pps, PRECISION, Math.Rounding.Floor);
uint16 slippageBps = state.redeemSlippageBps > 0 ? state.redeemSlippageBps : DEFAULT_REDEEM_SLIPPAGE_BPS;
minAssets = SuperVaultAccountingLib.computeMinNetOut(shares, state.averageRequestPPS, slippageBps, PRECISION);
return (shares, theoreticalAssets, minAssets);
}
/// @inheritdoc ISuperVaultStrategy
function previewExactRedeemBatch(address[] calldata controllers)
external
view
returns (uint256 totalTheoAssets, uint256[] memory individualAssets)
{
if (controllers.length == 0) revert ZERO_LENGTH();
individualAssets = new uint256[](controllers.length);
totalTheoAssets = 0;
for (uint256 i = 0; i < controllers.length; i++) {
// Get theoretical assets for this controller
(, uint256 theoreticalAssets,) = this.previewExactRedeem(controllers[i]);
individualAssets[i] = theoreticalAssets;
totalTheoAssets += theoreticalAssets;
}
return (totalTheoAssets, individualAssets);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Process a single hook execution
/// @param hook Hook address
/// @param prevHook Previous hook address
/// @param hookCalldata Hook calldata
/// @param expectedAssetsOrSharesOut Expected assets or shares output
/// @return processedHook Processed hook address
function _processSingleHookExecution(
address hook,
address prevHook,
bytes memory hookCalldata,
uint256 expectedAssetsOrSharesOut
)
internal
returns (address)
{
ExecutionVars memory vars;
vars.hookContract = ISuperHook(hook);
vars.targetedYieldSource = HookDataDecoder.extractYieldSource(hookCalldata);
// Bool flagging if the hook uses the previous hook's outAmount
// No slippage checks performed here as they have already been performed in the previous hook execution
bool usePrevHookAmount = _decodeHookUsePrevHookAmount(hook, hookCalldata);
ISuperHook(address(vars.hookContract)).setExecutionContext(address(this));
vars.executions = vars.hookContract.build(prevHook, address(this), hookCalldata);
for (uint256 j; j < vars.executions.length; ++j) {
// Block hooks from calling the SuperVaultAggregator directly
address aggregatorAddr = address(_getSuperVaultAggregator());
if (vars.executions[j].target == aggregatorAddr) revert OPERATION_FAILED();
(vars.success,) =
vars.executions[j].target.call{ value: vars.executions[j].value }(vars.executions[j].callData);
if (!vars.success) revert OPERATION_FAILED();
}
ISuperHook(address(vars.hookContract)).resetExecutionState(address(this));
uint256 actualOutput = ISuperHookResult(hook).getOutAmount(address(this));
// this is not to protect the user but rather a honest manager from doing a mistake
if (actualOutput < expectedAssetsOrSharesOut) {
revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET();
}
emit HookExecuted(hook, prevHook, vars.targetedYieldSource, usePrevHookAmount, hookCalldata);
return hook;
}
/*//////////////////////////////////////////////////////////////
INTERNAL REDEMPTION PROCESSING
//////////////////////////////////////////////////////////////*/
/// @notice Process exact fulfillment for batch processing
/// @dev Handles all accounting updates for fulfilled redemption:
/// 1. Validates slippage bounds (minAssets <= actual <= theoretical)
/// 2. Updates weighted average withdraw price across multiple fulfillments
/// 3. Clears pending state and makes assets claimable
/// 4. Resets cancellation flags
/// @dev SECURITY: Bounds validation ensures manager cannot underfill/overfill
/// @dev ACCOUNTING: Average withdraw price uses weighted formula to track historical execution prices
/// @param controller Controller address
/// @param totalAssetsOut Total assets available for this controller (from executeHooks)
/// @param currentPPS Current price per share
/// @param pendingShares Pending shares for this controller (passed to avoid re-reading from storage)
function _processExactFulfillmentBatch(
address controller,
uint256 totalAssetsOut,
uint256 currentPPS,
uint256 pendingShares
)
internal
{
SuperVaultState storage state = superVaultState[controller];
// Slippage validation
uint16 slippageBps = state.redeemSlippageBps > 0 ? state.redeemSlippageBps : DEFAULT_REDEEM_SLIPPAGE_BPS;
uint256 theoreticalAssets = pendingShares.mulDiv(currentPPS, PRECISION, Math.Rounding.Floor);
uint256 minAssetsOut =
SuperVaultAccountingLib.computeMinNetOut(pendingShares, state.averageRequestPPS, slippageBps, PRECISION);
// Bounds check: totalAssetsOut must be between minAssetsOut and theoreticalAssets
if (totalAssetsOut < minAssetsOut || totalAssetsOut > theoreticalAssets) {
revert BOUNDS_EXCEEDED(minAssetsOut, theoreticalAssets, totalAssetsOut);
}
// Update average withdraw price (use actual assets received)
state.averageWithdrawPrice = SuperVaultAccountingLib.calculateAverageWithdrawPrice(
state.maxWithdraw, state.averageWithdrawPrice, pendingShares, totalAssetsOut, PRECISION
);
// Reset state
state.pendingRedeemRequest = 0;
state.maxWithdraw += totalAssetsOut;
state.averageRequestPPS = 0;
state.pendingCancelRedeemRequest = false;
state.claimableCancelRedeemRequest = 0;
emit RedeemClaimable(controller, totalAssetsOut, pendingShares, state.averageWithdrawPrice);
}
/*//////////////////////////////////////////////////////////////
INTERNAL HELPER FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Internal function to get the SuperVaultAggregator
/// @return The SuperVaultAggregator
function _getSuperVaultAggregator() internal view returns (ISuperVaultAggregator) {
address aggregatorAddress = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_VAULT_AGGREGATOR());
return ISuperVaultAggregator(aggregatorAddress);
}
/// @notice Internal function to check if a manager is authorized
/// @param manager_ The manager to check
function _isManager(address manager_) internal view {
if (!_getSuperVaultAggregator().isAnyManager(manager_, address(this))) {
revert MANAGER_NOT_AUTHORIZED();
}
}
/// @notice Internal function to check if a manager is the primary manager
/// @param manager_ The manager to check
function _isPrimaryManager(address manager_) internal view {
if (!_getSuperVaultAggregator().isMainManager(manager_, address(this))) {
revert MANAGER_NOT_AUTHORIZED();
}
}
/// @notice Internal function to manage a yield source
/// @param source Address of the yield source
/// @param oracle Address of the oracle
/// @param actionType Type of action (see YieldSourceAction enum)
function _manageYieldSource(address source, address oracle, YieldSourceAction actionType) internal {
if (actionType == YieldSourceAction.Add) {
_addYieldSource(source, oracle);
} else if (actionType == YieldSourceAction.UpdateOracle) {
_updateYieldSourceOracle(source, oracle);
} else if (actionType == YieldSourceAction.Remove) {
_removeYieldSource(source);
}
}
/// @notice Internal function to add a yield source
/// @param source Address of the yield source
/// @param oracle Address of the oracle
function _addYieldSource(address source, address oracle) internal {
if (source == address(0) || oracle == address(0)) revert ZERO_ADDRESS();
if (yieldSources[source] != address(0)) revert YIELD_SOURCE_ALREADY_EXISTS();
yieldSources[source] = oracle;
if (!yieldSourcesList.add(source)) revert YIELD_SOURCE_ALREADY_EXISTS();
emit YieldSourceAdded(source, oracle);
}
/// @notice Internal function to update a yield source's oracle
/// @param source Address of the yield source
/// @param oracle Address of the oracle
function _updateYieldSourceOracle(address source, address oracle) internal {
if (oracle == address(0)) revert ZERO_ADDRESS();
address oldOracle = yieldSources[source];
if (oldOracle == address(0)) revert YIELD_SOURCE_NOT_FOUND();
yieldSources[source] = oracle;
emit YieldSourceOracleUpdated(source, oldOracle, oracle);
}
/// @notice Internal function to remove a yield source
/// @param source Address of the yield source
function _removeYieldSource(address source) internal {
if (yieldSources[source] == address(0)) revert YIELD_SOURCE_NOT_FOUND();
// Remove from mapping
delete yieldSources[source];
// Remove from EnumerableSet
if (!yieldSourcesList.remove(source)) revert YIELD_SOURCE_NOT_FOUND();
emit YieldSourceRemoved(source);
}
/// @notice Internal function to propose a PPS expiry threshold
/// @param _threshold The new PPS expiry threshold
function _proposePPSExpiration(uint256 _threshold) internal {
_isPrimaryManager(msg.sender);
if (_threshold < MIN_PPS_EXPIRATION_THRESHOLD || _threshold > MAX_PPS_EXPIRATION_THRESHOLD) {
revert INVALID_PPS_EXPIRY_THRESHOLD();
}
uint256 currentProposedThreshold = proposedPPSExpiryThreshold;
proposedPPSExpiryThreshold = _threshold;
ppsExpiryThresholdEffectiveTime = block.timestamp + PROPOSAL_TIMELOCK;
emit PPSExpirationProposed(currentProposedThreshold, _threshold, ppsExpiryThresholdEffectiveTime);
}
/// @notice Internal function to perform a PPS expiry threshold
function _updatePPSExpiration() internal {
_isPrimaryManager(msg.sender);
// Must have a valid proposal
if (block.timestamp < ppsExpiryThresholdEffectiveTime) revert INVALID_TIMESTAMP();
if (proposedPPSExpiryThreshold == 0) revert INVALID_PPS_EXPIRY_THRESHOLD();
uint256 _proposed = proposedPPSExpiryThreshold;
ppsExpiration = _proposed;
ppsExpiryThresholdEffectiveTime = 0;
proposedPPSExpiryThreshold = 0;
emit PPSExpiryThresholdUpdated(_proposed);
}
/// @notice Internal function to cancel a PPS expiry threshold proposal
function _cancelPPSExpirationProposalUpdate() internal {
_isPrimaryManager(msg.sender);
if (ppsExpiryThresholdEffectiveTime == 0) revert NO_PROPOSAL();
proposedPPSExpiryThreshold = 0;
ppsExpiryThresholdEffectiveTime = 0;
emit PPSExpiryThresholdProposalCanceled();
}
/// @notice Internal function to check if a hook is registered
/// @param hook Address of the hook
/// @return True if the hook is registered, false otherwise
function _isRegisteredHook(address hook) private view returns (bool) {
return SUPER_GOVERNOR.isHookRegistered(hook);
}
/// @notice Internal function to decode a hook's use previous hook amount
/// @param hook Address of the hook
/// @param hookCalldata Call data for the hook
/// @return True if the hook should use the previous hook amount, false otherwise
function _decodeHookUsePrevHookAmount(address hook, bytes memory hookCalldata) private pure returns (bool) {
try ISuperHookContextAware(hook).decodeUsePrevHookAmount(hookCalldata) returns (bool usePrevHookAmount) {
return usePrevHookAmount;
} catch {
return false;
}
}
/// @notice Internal function to handle a redeem
/// @param controller Address of the controller
/// @param shares Amount of shares
function _handleRequestRedeem(address controller, uint256 shares) private {
if (shares == 0) revert INVALID_AMOUNT();
if (controller == address(0)) revert ZERO_ADDRESS();
SuperVaultState storage state = superVaultState[controller];
// Get current PPS from aggregator to use as baseline for slippage protection
uint256 currentPPS = getStoredPPS();
if (currentPPS == 0) revert INVALID_PPS();
// Calculate weighted average of PPS if there's an existing request
if (state.pendingRedeemRequest > 0) {
// Incremental request: Calculate weighted average PPS
// This protects users from PPS manipulation between multiple requests
// Formula: avgPPS = (oldShares * oldPPS + newShares * newPPS) / totalShares
uint256 existingSharesInRequest = state.pendingRedeemRequest;
uint256 newTotalSharesInRequest = existingSharesInRequest + shares;
// Weighted average ensures fair pricing across multiple request timestamps
state.averageRequestPPS =
((existingSharesInRequest * state.averageRequestPPS) + (shares * currentPPS)) / newTotalSharesInRequest;
state.pendingRedeemRequest = newTotalSharesInRequest;
} else {
// First request: Initialize with current PPS as baseline for slippage protection
state.pendingRedeemRequest = shares;
state.averageRequestPPS = currentPPS;
}
emit RedeemRequestPlaced(controller, controller, shares);
}
/// @notice Internal function to handle a redeem cancellation request
/// @param controller Address of the controller
function _handleCancelRedeemRequest(address controller) private {
if (controller == address(0)) revert ZERO_ADDRESS();
SuperVaultState storage state = superVaultState[controller];
if (state.pendingRedeemRequest == 0) revert REQUEST_NOT_FOUND();
if (state.pendingCancelRedeemRequest) revert CANCELLATION_REDEEM_REQUEST_PENDING();
state.pendingCancelRedeemRequest = true;
emit RedeemCancelRequestPlaced(controller);
}
/// @notice Internal function to handle a claim redeem cancellation
/// @param controller Address of the controller
function _handleClaimCancelRedeem(address controller) private {
if (controller == address(0)) revert ZERO_ADDRESS();
SuperVaultState storage state = superVaultState[controller];
uint256 pendingShares = state.claimableCancelRedeemRequest;
if (pendingShares == 0) revert REQUEST_NOT_FOUND();
if (!state.pendingCancelRedeemRequest) revert CANCELLATION_REDEEM_REQUEST_PENDING();
// Clear pending request metadata
state.pendingCancelRedeemRequest = false;
state.claimableCancelRedeemRequest = 0;
emit RedeemRequestCanceled(controller, pendingShares);
}
/// @notice Internal function to handle a redeem claim
/// @dev Only updates state. Vault is responsible for calling Escrow.returnAssets() after this returns.
/// Callers (SuperVault.withdraw/redeem) already validate assetsToClaim <= state.maxWithdraw.
/// @param controller Address of the controller
/// @param receiver Address of the receiver (used for event only)
/// @param assetsToClaim Amount of assets to claim
function _handleClaimRedeem(address controller, address receiver, uint256 assetsToClaim) private {
if (assetsToClaim == 0) revert INVALID_AMOUNT();
if (controller == address(0)) revert ZERO_ADDRESS();
SuperVaultState storage state = superVaultState[controller];
state.maxWithdraw -= assetsToClaim;
emit RedeemRequestClaimed(receiver, controller, assetsToClaim, 0);
}
/// @notice Internal function to safely transfer tokens
/// @param token Address of the token
/// @param recipient Address to receive the tokens
/// @param amount Amount of tokens to transfer
function _safeTokenTransfer(address token, address recipient, uint256 amount) private {
if (amount > 0) IERC20(token).safeTransfer(recipient, amount);
}
/// @notice Internal function to get the token balance of an account
/// @param token Address of the token
/// @param account Address of the account
/// @return Token balance of the account
function _getTokenBalance(address token, address account) private view returns (uint256) {
return IERC20(token).balanceOf(account);
}
/// @notice Internal function to check if the caller is the vault
/// @dev This is used to prevent unauthorized access to certain functions
function _requireVault() internal view {
if (msg.sender != _vault) revert ACCESS_DENIED();
}
/// @notice Checks if the strategy is currently paused
/// @dev This calls SuperVaultAggregator.isStrategyPaused to determine pause status
/// @return True if the strategy is paused, false otherwise
function _isPaused(ISuperVaultAggregator aggregator) internal view returns (bool) {
return aggregator.isStrategyPaused(address(this));
}
/// @notice Checks if the PPS is stale
/// @dev This calls SuperVaultAggregator.isPPSStale to determine stale status
/// @return True if the PPS is stale, false otherwise
function _isPPSStale(ISuperVaultAggregator aggregator) internal view returns (bool) {
return aggregator.isPPSStale(address(this));
}
/// @notice Checks if the PPS is not updated
/// @dev This checks if the PPS has not been updated since the `ppsExpiration` time
/// @param aggregator The SuperVaultAggregator contract
/// @return True if the PPS is not updated, false otherwise
function _isPPSNotUpdated(ISuperVaultAggregator aggregator) internal view returns (bool) {
// The `ppsExpiration` serves a different purpose:
// if the oracle network stops pushing updates for some reasons (e.g. quite some nodes go down and the
// quorum is never reached)
// then the onchain PPS gets never updated and eventually it should not be used anymore, which is what the
// `ppsExpiration` logic controls
uint256 lastPPSUpdateTimestamp = aggregator.getLastUpdateTimestamp(address(this));
return block.timestamp - lastPPSUpdateTimestamp > ppsExpiration;
}
/// @notice Validates full pps state by checking pause, stale, and PPS update status
/// @dev Used for operations that require current PPS for calculations:
/// - handleOperations4626Deposit: Needs PPS to calculate shares from assets
/// - handleOperations4626Mint: Needs PPS to validate asset requirements
/// - fulfillRedeemRequests: Needs current PPS to calculate assets from shares
/// @param aggregator The SuperVaultAggregator contract
function _validateStrategyState(ISuperVaultAggregator aggregator) internal view {
if (_isPaused(aggregator)) revert STRATEGY_PAUSED();
if (_isPPSStale(aggregator)) revert STALE_PPS();
if (_isPPSNotUpdated(aggregator)) revert PPS_EXPIRED();
}
/// @notice Validates a hook using the Merkle root system
/// @param hook Address of the hook to validate
/// @param hookCalldata Calldata to be passed to the hook
/// @param globalProof Merkle proof for the global root
/// @param strategyProof Merkle proof for the strategy-specific root
/// @return isValid True if the hook is valid, false otherwise
function _validateHook(
address hook,
bytes memory hookCalldata,
bytes32[] memory globalProof,
bytes32[] memory strategyProof
)
internal
view
returns (bool)
{
return _getSuperVaultAggregator()
.validateHook(
address(this),
ISuperVaultAggregator.ValidateHookArgs({
hookAddress: hook,
hookArgs: ISuperHookInspector(hook).inspect(hookCalldata),
globalProof: globalProof,
strategyProof: strategyProof
})
);
}
}
ISuperVaultStrategy.sol 418 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { ISuperHook, Execution } from "@superform-v2-core/src/interfaces/ISuperHook.sol";
/// @title ISuperVaultStrategy
/// @author Superform Labs
/// @notice Interface for SuperVault strategy implementation that manages yield sources and executes strategies
interface ISuperVaultStrategy {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error ZERO_LENGTH();
error INVALID_HOOK();
error ZERO_ADDRESS();
error ACCESS_DENIED();
error INVALID_AMOUNT();
error OPERATION_FAILED();
error INVALID_TIMESTAMP();
error REQUEST_NOT_FOUND();
error INVALID_ARRAY_LENGTH();
error ACTION_TYPE_DISALLOWED();
error YIELD_SOURCE_NOT_FOUND();
error YIELD_SOURCE_ALREADY_EXISTS();
error INVALID_PERFORMANCE_FEE_BPS();
error MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET();
error MANAGER_NOT_AUTHORIZED();
error INVALID_PPS();
error INVALID_VAULT();
error INVALID_ASSET();
error OPERATIONS_BLOCKED_BY_VETO();
error HOOK_VALIDATION_FAILED();
error STRATEGY_PAUSED();
error NO_PROPOSAL();
error INVALID_REDEEM_SLIPPAGE_BPS();
error CANCELLATION_REDEEM_REQUEST_PENDING();
error STALE_PPS();
error PPS_EXPIRED();
error INVALID_PPS_EXPIRY_THRESHOLD();
error BOUNDS_EXCEEDED(uint256 minAllowed, uint256 maxAllowed, uint256 actual);
error INSUFFICIENT_LIQUIDITY();
error CONTROLLERS_NOT_SORTED_UNIQUE();
error ZERO_SHARE_FULFILLMENT_DISALLOWED();
error NOT_ENOUGH_FREE_ASSETS_FEE_SKIM();
error SKIM_TIMELOCK_ACTIVE();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event SuperGovernorSet(address indexed superGovernor);
event Initialized(address indexed vault);
event YieldSourceAdded(address indexed source, address indexed oracle);
event YieldSourceOracleUpdated(address indexed source, address indexed oldOracle, address indexed newOracle);
event YieldSourceRemoved(address indexed source);
event VaultFeeConfigUpdated(uint256 performanceFeeBps, uint256 managementFeeBps, address indexed recipient);
event VaultFeeConfigProposed(
uint256 performanceFeeBps, uint256 managementFeeBps, address indexed recipient, uint256 effectiveTime
);
event HooksExecuted(address[] hooks);
event RedeemRequestPlaced(address indexed controller, address indexed owner, uint256 shares);
event RedeemRequestClaimed(address indexed controller, address indexed receiver, uint256 assets, uint256 shares);
event RedeemRequestsFulfilled(address[] controllers, uint256 processedShares, uint256 currentPPS);
event RedeemRequestCanceled(address indexed controller, uint256 shares);
event RedeemCancelRequestPlaced(address indexed controller);
event RedeemCancelRequestFulfilled(address indexed controller, uint256 shares);
event HookExecuted(
address indexed hook,
address indexed prevHook,
address indexed targetedYieldSource,
bool usePrevHookAmount,
bytes hookCalldata
);
event PPSUpdated(uint256 newPPS, uint256 calculationBlock);
event FeeRecipientChanged(address indexed newRecipient);
event ManagementFeePaid(address indexed controller, address indexed recipient, uint256 feeAssets, uint256 feeBps);
event DepositHandled(address indexed controller, uint256 assets, uint256 shares);
event RedeemClaimable(
address indexed controller, uint256 assetsFulfilled, uint256 sharesFulfilled, uint256 averageWithdrawPrice
);
event RedeemSlippageSet(address indexed controller, uint16 slippageBps);
event PPSExpirationProposed(uint256 currentProposedThreshold, uint256 ppsExpiration, uint256 effectiveTime);
event PPSExpiryThresholdUpdated(uint256 ppsExpiration);
event PPSExpiryThresholdProposalCanceled();
/// @notice Emitted when the high-water mark PPS is updated after fee collection
/// @param newHwmPps The new high-water mark PPS (post-fee)
/// @param previousPps The PPS before fee collection
/// @param profit The total profit above HWM (in assets)
/// @param feeCollected The total fee collected (in assets)
event HWMPPSUpdated(uint256 newHwmPps, uint256 previousPps, uint256 profit, uint256 feeCollected);
/// @notice Emitted when the high-water mark PPS is reset
/// @param newHwmPps The new high-water mark PPS (post-fee)
event HighWaterMarkReset(uint256 newHwmPps);
/// @notice Emitted when performance fees are skimmed
/// @param totalFee The total fee collected (in assets)
/// @param superformFee The fee collected for Superform (in assets)
event PerformanceFeeSkimmed(uint256 totalFee, uint256 superformFee);
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
struct FeeConfig {
uint256 performanceFeeBps; // On profit at fulfill time
uint256 managementFeeBps; // Entry fee on deposit/mint (asset-side)
address recipient; // Fee sink (entry + performance)
}
/// @notice Structure for hook execution arguments
struct ExecuteArgs {
/// @notice Array of hooks to execute
address[] hooks;
/// @notice Calldata for each hook (must match hooks array length)
bytes[] hookCalldata;
/// @notice Expected output amounts or output shares
uint256[] expectedAssetsOrSharesOut;
/// @notice Global Merkle proofs for hook validation (must match hooks array length)
bytes32[][] globalProofs;
/// @notice Strategy-specific Merkle proofs for hook validation (must match hooks array length)
bytes32[][] strategyProofs;
}
struct YieldSource {
address oracle; // Associated yield source oracle address
}
/// @notice Comprehensive information about a yield source including its address and configuration
struct YieldSourceInfo {
address sourceAddress; // Address of the yield source
address oracle; // Associated yield source oracle address
}
/// @notice State specific to asynchronous redeem requests
struct SuperVaultState {
// Cancellation
bool pendingCancelRedeemRequest;
uint256 claimableCancelRedeemRequest;
// Redeems
uint256 pendingRedeemRequest; // Shares requested
uint256 maxWithdraw; // Assets claimable after fulfillment
uint256 averageRequestPPS; // Average PPS at the time of redeem request
uint256 averageWithdrawPrice; // Average price for claimable assets
uint16 redeemSlippageBps; // User-defined slippage tolerance in BPS for redeem fulfillment
}
struct ExecutionVars {
bool success;
address targetedYieldSource;
uint256 outAmount;
ISuperHook hookContract;
Execution[] executions;
}
struct FulfillRedeemVars {
uint256 totalRequestedShares;
uint256 totalNetAssetsOut;
uint256 currentPPS;
uint256 strategyBalance;
}
/*//////////////////////////////////////////////////////////////
ENUMS
//////////////////////////////////////////////////////////////*/
enum Operation {
RedeemRequest,
CancelRedeemRequest,
ClaimCancelRedeem,
ClaimRedeem
}
/// @notice Action types for yield source management
enum YieldSourceAction {
Add, // 0: Add a new yield source
UpdateOracle, // 1: Update an existing yield source's oracle
Remove // 2: Remove a yield source
}
/// @notice Action types for PPS expiration threshold management
enum PPSExpirationAction {
Propose, // 0: Propose a new PPS expiration threshold
Execute, // 1: Execute the proposed threshold update
Cancel // 2: Cancel the pending threshold proposal
}
/*//////////////////////////////////////////////////////////////
CORE STRATEGY OPERATIONS
//////////////////////////////////////////////////////////////*/
/// @notice Initializes the strategy with required parameters
/// @param vaultAddress Address of the associated SuperVault
/// @param feeConfigData Fee configuration
function initialize(address vaultAddress, FeeConfig memory feeConfigData) external;
/// @notice Execute a 4626 deposit by processing assets.
/// @param controller The controller address
/// @param assetsGross The amount of gross assets user has to deposit
/// @return sharesNet The amount of net shares to mint
function handleOperations4626Deposit(address controller, uint256 assetsGross) external returns (uint256 sharesNet);
/// @notice Execute a 4626 mint by processing shares.
/// @param controller The controller address
/// @param sharesNet The amount of shares to mint
/// @param assetsGross The amount of gross assets user has to deposit
/// @param assetsNet The amount of net assets that strategy will receive
function handleOperations4626Mint(
address controller,
uint256 sharesNet,
uint256 assetsGross,
uint256 assetsNet
)
external;
/// @notice Quotes the amount of assets that will be received for a given amount of shares.
/// @param shares The amount of shares to mint
/// @return assetsGross The amount of gross assets that will be received
/// @return assetsNet The amount of net assets that will be received
function quoteMintAssetsGross(uint256 shares) external view returns (uint256 assetsGross, uint256 assetsNet);
/// @notice Execute async redeem requests (redeem, cancel, claim).
/// @param op The operation type (RedeemRequest, CancelRedeem, ClaimRedeem)
/// @param controller The controller address
/// @param receiver The receiver address
/// @param amount The amount of assets or shares
function handleOperations7540(Operation op, address controller, address receiver, uint256 amount) external;
/*//////////////////////////////////////////////////////////////
MANAGER EXTERNAL ACCESS FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Execute hooks for general strategy management (rebalancing, etc.).
/// @param args Execution arguments containing hooks, calldata, proofs, expectations.
function executeHooks(ExecuteArgs calldata args) external payable;
/// @notice Fulfills pending cancel redeem requests by making shares claimable
/// @dev Processes all controllers with pending cancellation flags
/// @dev Can only be called by authorized managers
/// @param controllers Array of controller addresses with pending cancel requests
function fulfillCancelRedeemRequests(address[] memory controllers) external;
/// @notice Fulfills pending redeem requests with exact total assets per controller (pre-fee).
/// @dev PRE: Off-chain sort/unique controllers. Call executeHooks(sum(totalAssetsOut)) first.
/// @dev Social: totalAssetsOut[i] = theoreticalGross[i] (full). Selective: totalAssetsOut[i] < theoreticalGross[i].
/// @dev NOTE: totalAssetsOut includes fees - actual net amount received is calculated internally after fee
/// deduction. @param controllers Ordered/unique controllers with pending requests.
/// @param totalAssetsOut Total PRE-FEE assets available for each controller[i] (from executeHooks).
function fulfillRedeemRequests(address[] calldata controllers, uint256[] calldata totalAssetsOut) external;
/// @notice Skim performance fees based on per-share High Water Mark (PPS-based)
/// @dev Can be called by any manager when vault PPS has grown above HWM PPS
/// @dev Uses PPS growth to calculate profit: (currentPPS - hwmPPS) * totalSupply / PRECISION
/// @dev HWM is only updated during this function, not during deposits/redemptions
function skimPerformanceFee() external;
/*//////////////////////////////////////////////////////////////
YIELD SOURCE MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Manage a single yield source: add, update oracle, or remove
/// @param source Address of the yield source
/// @param oracle Address of the oracle (used for adding/updating, ignored for removal)
/// @param actionType Type of action (see YieldSourceAction enum)
function manageYieldSource(address source, address oracle, YieldSourceAction actionType) external;
/// @notice Batch manage multiple yield sources in a single transaction
/// @param sources Array of yield source addresses
/// @param oracles Array of oracle addresses (used for adding/updating, ignored for removal)
/// @param actionTypes Array of action types (see YieldSourceAction enum)
function manageYieldSources(
address[] calldata sources,
address[] calldata oracles,
YieldSourceAction[] calldata actionTypes
)
external;
/// @notice Change the fee recipient when the primary manager is changed
/// @param newRecipient New fee recipient
function changeFeeRecipient(address newRecipient) external;
/// @notice Propose or execute a hook root update
/// @notice Propose changes to vault-specific fee configuration
/// @param performanceFeeBps New performance fee in basis points
/// @param managementFeeBps New management fee in basis points
/// @param recipient New fee recipient
/// @dev IMPORTANT: Before executing the proposed update (via executeVaultFeeConfigUpdate),
/// manager should call skimPerformanceFee() to collect performance fees on existing profits
/// under the current fee structure to avoid losing profit or incorrect fee calculations.
function proposeVaultFeeConfigUpdate(
uint256 performanceFeeBps,
uint256 managementFeeBps,
address recipient
)
external;
/// @notice Execute the proposed vault fee configuration update after timelock
/// @dev IMPORTANT: Manager should call skimPerformanceFee() before executing this update
/// to collect performance fees on existing profits under the current fee structure.
/// Otherwise, profit earned under the old fee percentage will be lost or incorrectly calculated.
/// @dev This function will reset the High Water Mark (vaultHwmPps) to the current PPS value
/// to avoid incorrect fee calculations with the new fee structure.
function executeVaultFeeConfigUpdate() external;
/// @notice Reset the high-water mark PPS to the current PPS
/// @dev This function is only callable by Aggregator
/// @dev This function will reset the High Water Mark (vaultHwmPps) to the current PPS value
/// @param newHwmPps The new high-water mark PPS value
function resetHighWaterMark(uint256 newHwmPps) external;
/// @notice Manage PPS expiry threshold
/// @param action Type of action (see PPSExpirationAction enum)
/// @param ppsExpiration The new PPS expiry threshold
function managePPSExpiration(PPSExpirationAction action, uint256 ppsExpiration) external;
/*//////////////////////////////////////////////////////////////
ACCOUNTING MANAGEMENT
//////////////////////////////////////////////////////////////*/
/*//////////////////////////////////////////////////////////////
USER OPERATIONS
//////////////////////////////////////////////////////////////*/
/// @notice Set the slippage tolerance for all future redeem request fulfillments, until reset using this function
/// @param slippageBps Slippage tolerance in basis points (e.g., 50 = 0.5%)
function setRedeemSlippage(uint16 slippageBps) external;
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Get the vault info
function getVaultInfo() external view returns (address vault, address asset, uint8 vaultDecimals);
/// @notice Get the fee configurations
function getConfigInfo() external view returns (FeeConfig memory feeConfig);
/// @notice Returns the currently stored PPS value.
function getStoredPPS() external view returns (uint256);
/// @notice Get a yield source's configuration
function getYieldSource(address source) external view returns (YieldSource memory);
/// @notice Get all yield sources with their information
/// @return Array of YieldSourceInfo structs
function getYieldSourcesList() external view returns (YieldSourceInfo[] memory);
/// @notice Get all yield source addresses
/// @return Array of yield source addresses
function getYieldSources() external view returns (address[] memory);
/// @notice Get the count of yield sources
/// @return Number of yield sources
function getYieldSourcesCount() external view returns (uint256);
/// @notice Check if a yield source exists
/// @param source Address of the yield source
/// @return True if the yield source exists
function containsYieldSource(address source) external view returns (bool);
/// @notice Get the average withdraw price for a controller
/// @param controller The controller address
/// @return averageWithdrawPrice The average withdraw price
function getAverageWithdrawPrice(address controller) external view returns (uint256 averageWithdrawPrice);
/// @notice Get the super vault state for a controller
/// @param controller The controller address
/// @return state The super vault state
function getSuperVaultState(address controller) external view returns (SuperVaultState memory state);
/// @notice Get the pending redeem request amount (shares) for a controller
/// @param controller The controller address
/// @return pendingShares The amount of shares pending redemption
function pendingRedeemRequest(address controller) external view returns (uint256 pendingShares);
/// @notice Get the pending cancellation for a redeem request for a controller
/// @param controller The controller address
/// @return isPending True if the redeem request is pending cancellation
function pendingCancelRedeemRequest(address controller) external view returns (bool isPending);
/// @notice Get the claimable cancel redeem request amount (shares) for a controller
/// @param controller The controller address
/// @return claimableShares The amount of shares claimable
function claimableCancelRedeemRequest(address controller) external view returns (uint256 claimableShares);
/// @notice Get the claimable withdraw amount (assets) for a controller
/// @param controller The controller address
/// @return claimableAssets The amount of assets claimable
function claimableWithdraw(address controller) external view returns (uint256 claimableAssets);
/// @notice Preview exact redeem fulfillment for off-chain calculation
/// @param controller The controller address to preview
/// @return shares Pending redeem shares
/// @return theoreticalAssets Theoretical assets at current PPS
/// @return minAssets Minimum acceptable assets (slippage floor)
function previewExactRedeem(address controller)
external
view
returns (uint256 shares, uint256 theoreticalAssets, uint256 minAssets);
/// @notice Batch preview exact redeem fulfillment for multiple controllers
/// @dev Efficiently batches multiple previewExactRedeem calls to reduce RPC overhead
/// @param controllers Array of controller addresses to preview
/// @return totalTheoAssets Total theoretical assets across all controllers
/// @return individualAssets Array of theoretical assets per controller
function previewExactRedeemBatch(address[] calldata controllers)
external
view
returns (uint256 totalTheoAssets, uint256[] memory individualAssets);
/// @notice Get the current unrealized profit above the High Water Mark
/// @return profit Current profit above High Water Mark (in assets), 0 if no profit
/// @dev Calculates based on PPS growth: (currentPPS - hwmPPS) * totalSupply / PRECISION
/// @dev Returns 0 if totalSupply is 0 or currentPPS <= hwmPPS
function vaultUnrealizedProfit() external view returns (uint256);
}
SuperVaultEscrow.sol 73 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ISuperVaultEscrow } from "../interfaces/SuperVault/ISuperVaultEscrow.sol";
/// @title SuperVaultEscrow
/// @author Superform Labs
/// @notice Escrow contract for SuperVault shares during request/claim process
contract SuperVaultEscrow is ISuperVaultEscrow {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////
STATE
//////////////////////////////////////////////////////////////*/
bool public initialized;
address public vault;
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier onlyVault() {
_onlyVault();
_;
}
function _onlyVault() internal view {
if (msg.sender != vault) revert UNAUTHORIZED();
}
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultEscrow
function initialize(address vaultAddress) external {
if (initialized) revert ALREADY_INITIALIZED();
if (vaultAddress == address(0)) revert ZERO_ADDRESS();
initialized = true;
vault = vaultAddress;
emit Initialized(vaultAddress);
}
/*//////////////////////////////////////////////////////////////
VAULT FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ISuperVaultEscrow
function escrowShares(address from, uint256 amount) external onlyVault {
if (amount == 0) revert ZERO_AMOUNT();
IERC20(vault).safeTransferFrom(from, address(this), amount);
emit SharesEscrowed(from, amount);
}
/// @inheritdoc ISuperVaultEscrow
function returnShares(address to, uint256 amount) external onlyVault {
if (amount == 0) revert ZERO_AMOUNT();
IERC20(vault).safeTransfer(to, amount);
emit SharesReturned(to, amount);
}
/// @inheritdoc ISuperVaultEscrow
function returnAssets(address to, uint256 amount) external onlyVault {
if (amount == 0) revert ZERO_AMOUNT();
if (to == address(0)) revert ZERO_ADDRESS();
IERC20(IERC4626(vault).asset()).safeTransfer(to, amount);
emit AssetsReturned(to, amount);
}
}
ISuperGovernor.sol 542 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
/*//////////////////////////////////////////////////////////////
ENUMS
//////////////////////////////////////////////////////////////*/
/// @notice Enum representing different types of fees that can be managed
enum FeeType {
REVENUE_SHARE,
PERFORMANCE_FEE_SHARE
}
/// @title ISuperGovernor
/// @author Superform Labs
/// @notice Interface for the SuperGovernor contract
/// @dev Central registry for all deployed contracts in the Superform periphery
interface ISuperGovernor is IAccessControl {
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/// @notice Structure containing Merkle root data for a hook
struct HookMerkleRootData {
bytes32 currentRoot; // Current active Merkle root for the hook
bytes32 proposedRoot; // Proposed new Merkle root (zero if no proposal exists)
uint256 effectiveTime; // Timestamp when the proposed root becomes effective
}
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when trying to access a contract that is not registered
error CONTRACT_NOT_FOUND();
/// @notice Thrown when providing an invalid address (typically zero address)
error INVALID_ADDRESS();
/// @notice Thrown when a hook is not approved but expected to be
error HOOK_NOT_APPROVED();
/// @notice Thrown when an invalid fee value is proposed (must be <= BPS_MAX)
error INVALID_FEE_VALUE();
/// @notice Thrown when no proposed fee exists but one is expected
error NO_PROPOSED_FEE(FeeType feeType);
/// @notice Thrown when timelock period has not expired
error TIMELOCK_NOT_EXPIRED();
/// @notice Thrown when a validator is already registered
error VALIDATOR_ALREADY_REGISTERED();
/// @notice Thrown when trying to change active PPS oracle directly
error MUST_USE_TIMELOCK_FOR_CHANGE();
/// @notice Thrown when a SuperBank hook Merkle root is not registered but expected to be
/// @dev This error is defined here for use by other contracts in the system (SuperVaultStrategy,
/// SuperVaultAggregator, ECDSAPPSOracle)
error INVALID_TIMESTAMP();
/// @notice Thrown when attempting to set an invalid quorum value (typically zero)
error INVALID_QUORUM();
/// @notice Thrown when validator and public key array lengths don't match
error ARRAY_LENGTH_MISMATCH();
/// @notice Thrown when trying to set validator config with an empty validator array
error EMPTY_VALIDATOR_ARRAY();
/// @notice Thrown when no active PPS oracle is set but one is required
error NO_ACTIVE_PPS_ORACLE();
/// @notice Thrown when no proposed PPS oracle exists but one is expected
error NO_PROPOSED_PPS_ORACLE();
/// @notice Error thrown when manager takeovers are frozen
error MANAGER_TAKEOVERS_FROZEN();
/// @notice Thrown when no proposed Merkle root exists but one is expected
error NO_PROPOSED_MERKLE_ROOT();
/// @notice Thrown when no proposed Merkle root exists but one is expected
error ZERO_PROPOSED_MERKLE_ROOT();
/// @notice Thrown when no proposed minimum staleness exists but one is expected
error NO_PROPOSED_MIN_STALENESS();
/// @notice Thrown when the provided maxStaleness is less than the minimum required staleness
error MAX_STALENESS_TOO_LOW();
/// @notice Thrown when there's no pending change but one is expected
error NO_PENDING_CHANGE();
/// @notice Thrown when the super oracle is not found
error SUPER_ORACLE_NOT_FOUND();
/// @notice Thrown when the up token is not found
error UP_NOT_FOUND();
/// @notice Thrown when the upkeep token is not found
error UPKEEP_TOKEN_NOT_FOUND();
/// @notice Thrown when the gas info is invalid
error INVALID_GAS_INFO();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when an address is set in the registry
/// @param key The key used to reference the address
/// @param oldValue The old address value
/// @param value The address value
event AddressSet(bytes32 indexed key, address indexed oldValue, address indexed value);
/// @notice Emitted when a hook is approved
/// @param hook The address of the approved hook
event HookApproved(address indexed hook);
/// @notice Emitted when validator configuration is set
/// @param version The version of the configuration
/// @param validators Array of validator addresses
/// @param validatorPublicKeys Array of validator public keys (for signature verification)
/// @param quorum The quorum required for validator consensus
/// @param offchainConfig Offchain configuration data
event ValidatorConfigSet(
uint256 version, address[] validators, bytes[] validatorPublicKeys, uint256 quorum, bytes offchainConfig
);
/// @notice Emitted when a hook is removed
/// @param hook The address of the removed hook
event HookRemoved(address indexed hook);
/// @notice Emitted when a new fee is proposed
/// @param feeType The type of fee being proposed
/// @param value The proposed fee value (in basis points)
/// @param effectiveTime The timestamp when the fee will be effective
event FeeProposed(FeeType indexed feeType, uint256 value, uint256 effectiveTime);
/// @notice Emitted when a fee is updated
/// @param feeType The type of fee being updated
/// @param value The new fee value (in basis points)
event FeeUpdated(FeeType indexed feeType, uint256 value);
/// @notice Emitted when a new SuperBank hook Merkle root is proposed
/// @param hook The hook address for which the Merkle root is being proposed
/// @param newRoot The new Merkle root
/// @param effectiveTime The timestamp when the new root will be effective
event SuperBankHookMerkleRootProposed(address indexed hook, bytes32 newRoot, uint256 effectiveTime);
/// @notice Emitted when the SuperBank hook Merkle root is updated.
/// @param hook The address of the hook for which the Merkle root was updated.
/// @param newRoot The new Merkle root.
event SuperBankHookMerkleRootUpdated(address indexed hook, bytes32 newRoot);
/// @notice Emitted when an active PPS oracle is initially set
/// @param oracle The address of the set oracle
event ActivePPSOracleSet(address indexed oracle);
/// @notice Emitted when a new PPS oracle is proposed
/// @param oracle The address of the proposed oracle
/// @param effectiveTime The timestamp when the proposal will be effective
event ActivePPSOracleProposed(address indexed oracle, uint256 effectiveTime);
/// @notice Emitted when the active PPS oracle is changed
/// @param oldOracle The address of the previous oracle
/// @param newOracle The address of the new oracle
event ActivePPSOracleChanged(address indexed oldOracle, address indexed newOracle);
/// @notice Event emitted when manager takeovers are permanently frozen
event ManagerTakeoversFrozen();
/// @notice Emitted when a change to upkeep payments status is proposed
/// @param enabled The proposed status (enabled/disabled)
/// @param effectiveTime The timestamp when the status change will be effective
event UpkeepPaymentsChangeProposed(bool enabled, uint256 effectiveTime);
/// @notice Emitted when upkeep payments status is changed
/// @param enabled The new status (enabled/disabled)
event UpkeepPaymentsChanged(bool enabled);
/// @notice Emitted when a new minimum staleness is proposed
/// @param newMinStaleness The proposed minimum staleness value
/// @param effectiveTime The timestamp when the new value will be effective
event MinStalenessProposed(uint256 newMinStaleness, uint256 effectiveTime);
/// @notice Emitted when the minimum staleness is changed
/// @param newMinStaleness The new minimum staleness value
event MinStalenessChanged(uint256 newMinStaleness);
/// @notice Emitted when gas info is set
/// @param oracle The address of the oracle
/// @param gasIncreasePerEntryBatch The gas increase per entry for the oracle
event GasInfoSet(address indexed oracle, uint256 gasIncreasePerEntryBatch);
/*//////////////////////////////////////////////////////////////
CONTRACT REGISTRY FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Sets an address in the registry
/// @param key The key to associate with the address
/// @param value The address value
function setAddress(bytes32 key, address value) external;
/*//////////////////////////////////////////////////////////////
PERIPHERY CONFIGURATIONS
//////////////////////////////////////////////////////////////*/
/// @notice Change the primary manager for a strategy
/// @dev Only SuperGovernor can call this function directly
/// @param strategy The strategy address
/// @param newManager The new primary manager address
/// @param feeRecipient The new fee recipient address
function changePrimaryManager(address strategy, address newManager, address feeRecipient) external;
/// @notice Resets the high-water mark PPS to the current PPS
/// @dev Only SuperGovernor can call this function
/// @dev If a manager is replaced while the strategy is below its
/// previous HWM, the new manager would otherwise inherit a "loss" state and be unable to earn performance fees
/// until the fee config are updated after the week timelock.
/// @dev This function will reset the High Water Mark (vaultHwmPps) to the current PPS value for the given strategy
/// @param strategy Address of the strategy to reset the high-water mark for
function resetHighWaterMark(address strategy) external;
/// @notice Permanently freezes all manager takeovers globally
function freezeManagerTakeover() external;
/// @notice Changes the hooks root update timelock duration
/// @param newTimelock New timelock duration in seconds
function changeHooksRootUpdateTimelock(uint256 newTimelock) external;
/// @notice Proposes a new global hooks Merkle root
/// @dev Only GOVERNOR_ROLE can call this function
/// @param newRoot New Merkle root for global hooks validation
function proposeGlobalHooksRoot(bytes32 newRoot) external;
/// @notice Sets veto status for global hooks Merkle root
/// @dev Only GUARDIAN_ROLE can call this function
/// @param vetoed Whether to veto (true) or unveto (false) the global hooks root
function setGlobalHooksRootVetoStatus(bool vetoed) external;
/// @notice Sets veto status for a strategy-specific hooks Merkle root
/// @dev Only GUARDIAN_ROLE can call this function
/// @param strategy Address of the strategy to affect
/// @param vetoed Whether to veto (true) or unveto (false) the strategy hooks root
function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external;
/// @notice Sets the maximum staleness period for all oracle feeds
/// @param newMaxStaleness The new maximum staleness period in seconds
function setOracleMaxStaleness(uint256 newMaxStaleness) external;
/// @notice Sets the maximum staleness period for a specific oracle feed
/// @param feed The address of the feed to set staleness for
/// @param newMaxStaleness The new maximum staleness period in seconds
function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external;
/// @notice Sets the maximum staleness periods for multiple oracle feeds in batch
/// @param feeds The addresses of the feeds to set staleness for
/// @param newMaxStalenessList The new maximum staleness periods in seconds
function setOracleFeedMaxStalenessBatch(address[] calldata feeds, uint256[] calldata newMaxStalenessList) external;
/// @notice Queues an oracle update for execution after timelock period
/// @param bases Base asset addresses
/// @param quotes Quote asset addresses
/// @param providers Provider identifiers
/// @param feeds Feed addresses
function queueOracleUpdate(
address[] calldata bases,
address[] calldata quotes,
bytes32[] calldata providers,
address[] calldata feeds
)
external;
/// @notice Executes a previously queued oracle update after timelock has expired
function executeOracleUpdate() external;
/// @notice Queues a provider removal for execution after timelock period
/// @param providers The providers to remove
function queueOracleProviderRemoval(bytes32[] calldata providers) external;
/// @notice Sets uptime feeds for multiple data oracles in batch (Layer 2 only)
/// @param dataOracles Array of data oracle addresses to set uptime feeds for
/// @param uptimeOracles Array of uptime feed addresses to set
/// @param gracePeriods Array of grace periods in seconds after sequencer restart
function batchSetOracleUptimeFeed(
address[] calldata dataOracles,
address[] calldata uptimeOracles,
uint256[] calldata gracePeriods
)
external;
/*//////////////////////////////////////////////////////////////
HOOK MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Registers a hook for use in SuperVaults
/// @param hook The address of the hook to register
function registerHook(address hook) external;
/// @notice Unregisters a hook from the approved list
/// @param hook The address of the hook to unregister
function unregisterHook(address hook) external;
/*//////////////////////////////////////////////////////////////
VALIDATOR MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Sets the validator configuration for the protocol
/// @dev This function atomically updates all validator configuration including quorum.
/// The entire validator set is replaced (not incrementally updated).
/// Version must be managed externally for cross-chain synchronization.
/// Quorum updates require providing the full validator list.
/// @param version The version number for the configuration (for cross-chain sync)
/// @param validators Array of validator addresses
/// @param validatorPublicKeys Array of validator public keys for signature verification
/// @param quorum The number of validators required for consensus
/// @param offchainConfig Offchain configuration data (emitted but not stored)
function setValidatorConfig(
uint256 version,
address[] calldata validators,
bytes[] calldata validatorPublicKeys,
uint256 quorum,
bytes calldata offchainConfig
)
external;
/*//////////////////////////////////////////////////////////////
PPS ORACLE MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Sets the active PPS oracle (only if there is no active oracle yet)
/// @param oracle Address of the PPS oracle to set as active
function setActivePPSOracle(address oracle) external;
/// @notice Proposes a new active PPS oracle (when there is already an active one)
/// @param oracle Address of the PPS oracle to propose as active
function proposeActivePPSOracle(address oracle) external;
/// @notice Executes a previously proposed PPS oracle change after timelock has expired
function executeActivePPSOracleChange() external;
/*//////////////////////////////////////////////////////////////
REVENUE SHARE MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Proposes a new fee value
/// @param feeType The type of fee to propose
/// @param value The proposed fee value (in basis points)
function proposeFee(FeeType feeType, uint256 value) external;
/// @notice Executes a previously proposed fee update after timelock has expired
/// @param feeType The type of ffee to execute the update for
function executeFeeUpdate(FeeType feeType) external;
/// @notice Executes an upkeep claim on `SuperVaultAggregator`
/// @param amount The amount to claim
function executeUpkeepClaim(uint256 amount) external;
/*//////////////////////////////////////////////////////////////
UPKEEP COST MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Sets gas info for an oracle
/// @param oracle The address of the oracle
/// @param gasIncreasePerEntryBatch The gas increase per entry for the oracle
function setGasInfo(address oracle, uint256 gasIncreasePerEntryBatch) external;
/// @notice Proposes a change to upkeep payments enabled status
/// @param enabled The proposed enabled status
function proposeUpkeepPaymentsChange(bool enabled) external;
/// @notice Executes a previously proposed upkeep payments status change
function executeUpkeepPaymentsChange() external;
/*//////////////////////////////////////////////////////////////
MIN STALENESS MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Proposes a new minimum staleness value to prevent maxStaleness from being set too low
/// @param newMinStaleness The proposed new minimum staleness value in seconds
function proposeMinStaleness(uint256 newMinStaleness) external;
/// @notice Executes a previously proposed minimum staleness change after timelock has expired
function executeMinStalenessChange() external;
/*//////////////////////////////////////////////////////////////
SUPERBANK HOOKS MGMT
//////////////////////////////////////////////////////////////*/
/// @notice Proposes a new Merkle root for a specific hook's allowed targets.
/// @param hook The address of the hook to update the Merkle root for.
/// @param proposedRoot The proposed new Merkle root.
function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external;
/// @notice Executes a previously proposed Merkle root update for a specific hook if the effective time has passed.
/// @param hook The address of the hook to execute the update for.
function executeSuperBankHookMerkleRootUpdate(address hook) external;
/*//////////////////////////////////////////////////////////////
EXTERNAL VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice The identifier of the role that grants access to critical governance functions
function SUPER_GOVERNOR_ROLE() external view returns (bytes32);
/// @notice The identifier of the role that grants access to daily operations like hooks and validators
function GOVERNOR_ROLE() external view returns (bytes32);
/// @notice The identifier of the role that grants access to bank management functions
function BANK_MANAGER_ROLE() external view returns (bytes32);
/// @notice The identifier of the role that grants access to gas management functions
function GAS_MANAGER_ROLE() external view returns (bytes32);
/// @notice The identifier of the role that grants access to oracle management functions
function ORACLE_MANAGER_ROLE() external view returns (bytes32);
/// @notice The identifier of the role that grants access to guardian functions
function GUARDIAN_ROLE() external view returns (bytes32);
/// @notice Gets an address from the registry
/// @param key The key of the address to get
/// @return The address value
function getAddress(bytes32 key) external view returns (address);
/// @notice Checks if manager takeovers are frozen
/// @return True if manager takeovers are frozen, false otherwise
function isManagerTakeoverFrozen() external view returns (bool);
/// @notice Checks if a hook is registered
/// @param hook The address of the hook to check
/// @return True if the hook is registered, false otherwise
function isHookRegistered(address hook) external view returns (bool);
/// @notice Gets all registered hooks
/// @return An array of registered hook addresses
function getRegisteredHooks() external view returns (address[] memory);
/// @notice Checks if an address is an approved validator
/// @param validator The address to check
/// @return True if the address is an approved validator, false otherwise
function isValidator(address validator) external view returns (bool);
/// @notice Checks if an address has the guardian role
/// @param guardian Address to check
/// @return true if the address has the GUARDIAN_ROLE
function isGuardian(address guardian) external view returns (bool);
/// @notice Returns the complete validator configuration
/// @return version The current configuration version number
/// @return validators Array of all registered validator addresses
/// @return validatorPublicKeys Array of validator public keys
/// @return quorum The number of validators required for consensus
function getValidatorConfig()
external
view
returns (uint256 version, address[] memory validators, bytes[] memory validatorPublicKeys, uint256 quorum);
/// @notice Returns all registered validators
/// @return List of validator addresses
function getValidators() external view returns (address[] memory);
/// @notice Returns the number of registered validators (O(1))
function getValidatorsCount() external view returns (uint256);
/// @notice Returns a validator address by index (0 … count-1)
/// @param index The index into the validators set
/// @return validator The validator address at the given index
function getValidatorAt(uint256 index) external view returns (address validator);
/// @notice Gets the proposed active PPS oracle and its effective time
/// @return proposedOracle The proposed oracle address
/// @return effectiveTime The timestamp when the proposed oracle will become effective
function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime);
/// @notice Gets the current quorum requirement for the active PPS Oracle
/// @return The current quorum requirement
function getPPSOracleQuorum() external view returns (uint256);
/// @notice Gets the active PPS oracle
/// @return The active PPS oracle address
function getActivePPSOracle() external view returns (address);
/// @notice Checks if an address is the current active PPS oracle
/// @param oracle The address to check
/// @return True if the address is the active PPS oracle, false otherwise
function isActivePPSOracle(address oracle) external view returns (bool);
/// @notice Gets the current fee value for a specific fee type
/// @param feeType The type of fee to get
/// @return The current fee value (in basis points)
function getFee(FeeType feeType) external view returns (uint256);
/// @notice Gets the current upkeep cost for an entry
function getUpkeepCostPerSingleUpdate(address oracle_) external view returns (uint256);
/// @notice Gets the proposed upkeep cost per update and its effective time
/// @notice Gets the current minimum staleness value
/// @return The current minimum staleness value in seconds
function getMinStaleness() external view returns (uint256);
/// @notice Gets the proposed minimum staleness value and its effective time
/// @return proposedMinStaleness The proposed new minimum staleness value
/// @return effectiveTime The timestamp when the new value will become effective
function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime);
/// @notice Returns the current Merkle root for a specific hook's allowed targets.
/// @param hook The address of the hook to get the Merkle root for.
/// @return The Merkle root for the hook's allowed targets.
function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32);
/// @notice Gets the proposed Merkle root and its effective time for a specific hook.
/// @param hook The address of the hook to get the proposed Merkle root for.
/// @return proposedRoot The proposed Merkle root.
/// @return effectiveTime The timestamp when the proposed root will become effective.
function getProposedSuperBankHookMerkleRoot(address hook)
external
view
returns (bytes32 proposedRoot, uint256 effectiveTime);
/// @notice Checks if upkeep payments are currently enabled
/// @return enabled True if upkeep payments are enabled
function isUpkeepPaymentsEnabled() external view returns (bool);
/// @notice Gets the proposed upkeep payments status and effective time
/// @return enabled The proposed status
/// @return effectiveTime The timestamp when the change becomes effective
function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime);
/// @notice Gets the SUP strategy ID
/// @return The ID of the SUP strategy vault
function SUP_STRATEGY() external view returns (bytes32);
/// @notice Gets the UP ID
/// @return The ID of the UP token
function UP() external view returns (bytes32);
/// @notice Gets the UPKEEP_TOKEN ID
/// @return The ID of the UPKEEP_TOKEN (used for upkeep payments, can be UP on mainnet or WETH/USDC on L2s)
function UPKEEP_TOKEN() external view returns (bytes32);
/// @notice Gets the Treasury ID
/// @return The ID for the Treasury in the registry
function TREASURY() external view returns (bytes32);
/// @notice Gets the SuperOracle ID
/// @return The ID for the SuperOracle in the registry
function SUPER_ORACLE() external view returns (bytes32);
/// @notice Gets the ECDSA PPS Oracle ID
/// @return The ID for the ECDSA PPS Oracle in the registry
function ECDSAPPSORACLE() external view returns (bytes32);
/// @notice Gets the SuperVaultAggregator ID
/// @return The ID for the SuperVaultAggregator in the registry
function SUPER_VAULT_AGGREGATOR() external view returns (bytes32);
/// @notice Gets the SuperBank ID
/// @return The ID for the SuperBank in the registry
function SUPER_BANK() external view returns (bytes32);
/// @notice Gets the gas info for a specific SuperVault PPS Oracle
/// @param oracle_ The address of the oracle to get gas info for
/// @return The gas info for the specified oracle
function getGasInfo(address oracle_) external view returns (uint256);
/// @notice Cancels a previously proposed oracle provider removal
function cancelOracleProviderRemoval() external;
/// @notice Executes a previously proposed oracle provider removal after timelock has expired
function executeOracleProviderRemoval() external;
}
ISuperVaultAggregator.sol 838 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { ISuperVaultStrategy } from "../SuperVault/ISuperVaultStrategy.sol";
/// @title ISuperVaultAggregator
/// @author Superform Labs
/// @notice Interface for the SuperVaultAggregator contract
/// @dev Registry and PPS oracle for all SuperVaults
interface ISuperVaultAggregator {
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/// @notice Arguments for forwarding PPS updates to avoid stack too deep errors
/// @param strategy Address of the strategy being updated
/// @param isExempt Whether the update is exempt from paying upkeep
/// @param pps New price-per-share value
/// @param timestamp Timestamp when the value was generated
/// @param upkeepCost Amount of upkeep tokens to charge if not exempt
struct PPSUpdateData {
address strategy;
bool isExempt;
uint256 pps;
uint256 timestamp;
uint256 upkeepCost;
}
/// @notice Local variables for vault creation to avoid stack too deep
/// @param currentNonce Current vault creation nonce
/// @param salt Salt for deterministic proxy creation
/// @param initialPPS Initial price-per-share value
struct VaultCreationLocalVars {
uint256 currentNonce;
bytes32 salt;
uint256 initialPPS;
}
/// @notice Strategy configuration and state data
/// @param pps Current price-per-share value
/// @param lastUpdateTimestamp Last time PPS was updated
/// @param minUpdateInterval Minimum time interval between PPS updates
/// @param maxStaleness Maximum time allowed between PPS updates before staleness
/// @param isPaused Whether the strategy is paused
/// @param mainManager Address of the primary manager controlling the strategy
/// @param secondaryManagers Set of secondary managers that can manage the strategy
struct StrategyData {
uint256 pps; // Slot 0: 32 bytes
uint256 lastUpdateTimestamp; // Slot 1: 32 bytes
uint256 minUpdateInterval; // Slot 2: 32 bytes
uint256 maxStaleness; // Slot 3: 32 bytes
// Packed slot 4: saves 2 storage slots (~4000 gas per read)
address mainManager; // 20 bytes
bool ppsStale; // 1 byte
bool isPaused; // 1 byte
bool hooksRootVetoed; // 1 byte
uint72 __gap1; // 9 bytes padding
EnumerableSet.AddressSet secondaryManagers;
// Manager change proposal data
address proposedManager;
address proposedFeeRecipient;
uint256 managerChangeEffectiveTime;
// Hook validation data
bytes32 managerHooksRoot;
// Hook root update proposal data
bytes32 proposedHooksRoot;
uint256 hooksRootEffectiveTime;
// PPS Verification thresholds
uint256 deviationThreshold; // Threshold for abs(new - current) / current
// Banned global leaves mapping
mapping(bytes32 => bool) bannedLeaves; // Mapping of leaf hash to banned status
// Min update interval proposal data
uint256 proposedMinUpdateInterval;
uint256 minUpdateIntervalEffectiveTime;
uint256 lastUnpauseTimestamp; // Timestamp of last unpause (for skim timelock)
}
/// @notice Parameters for creating a new SuperVault trio
/// @param asset Address of the underlying asset
/// @param name Name of the vault token
/// @param symbol Symbol of the vault token
/// @param mainManager Address of the vault mainManager
/// @param minUpdateInterval Minimum time interval between PPS updates
/// @param maxStaleness Maximum time allowed between PPS updates before staleness
/// @param feeConfig Fee configuration for the vault
struct VaultCreationParams {
address asset;
string name;
string symbol;
address mainManager;
address[] secondaryManagers;
uint256 minUpdateInterval;
uint256 maxStaleness;
ISuperVaultStrategy.FeeConfig feeConfig;
}
/// @notice Struct to hold cached hook validation state variables to avoid stack too deep
/// @param globalHooksRootVetoed Cached global hooks root veto status
/// @param globalHooksRoot Cached global hooks root
/// @param strategyHooksRootVetoed Cached strategy hooks root veto status
/// @param strategyRoot Cached strategy hooks root
struct HookValidationCache {
bool globalHooksRootVetoed;
bytes32 globalHooksRoot;
bool strategyHooksRootVetoed;
bytes32 strategyRoot;
}
/// @notice Arguments for validating a hook to avoid stack too deep
/// @param hookAddress Address of the hook contract
/// @param hookArgs Encoded arguments for the hook operation
/// @param globalProof Merkle proof for the global root
/// @param strategyProof Merkle proof for the strategy-specific root
struct ValidateHookArgs {
address hookAddress;
bytes hookArgs;
bytes32[] globalProof;
bytes32[] strategyProof;
}
/// @notice Two-step upkeep withdrawal request
/// @param amount Amount to withdraw (full balance at time of request)
/// @param effectiveTime When withdrawal can be executed (timestamp + 24h)
struct UpkeepWithdrawalRequest {
uint256 amount;
uint256 effectiveTime;
}
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when a new vault trio is created
/// @param vault Address of the created SuperVault
/// @param strategy Address of the created SuperVaultStrategy
/// @param escrow Address of the created SuperVaultEscrow
/// @param asset Address of the underlying asset
/// @param name Name of the vault token
/// @param symbol Symbol of the vault token
/// @param nonce The nonce used for vault creation
event VaultDeployed(
address indexed vault,
address indexed strategy,
address escrow,
address asset,
string name,
string symbol,
uint256 indexed nonce
);
/// @notice Emitted when a PPS value is updated
/// @param strategy Address of the strategy
/// @param pps New price-per-share value
/// @param timestamp Timestamp of the update
event PPSUpdated(address indexed strategy, uint256 pps, uint256 timestamp);
/// @notice Emitted when a strategy is paused due to missed updates
/// @param strategy Address of the paused strategy
event StrategyPaused(address indexed strategy);
/// @notice Emitted when a strategy is unpaused
/// @param strategy Address of the unpaused strategy
event StrategyUnpaused(address indexed strategy);
/// @notice Emitted when a strategy validation check fails but execution continues
/// @param strategy Address of the strategy that failed the check
/// @param reason String description of which check failed
event StrategyCheckFailed(address indexed strategy, string reason);
/// @notice Emitted when upkeep tokens are deposited
/// @param strategy Address of the strategy
/// @param depositor Address of the depositor
/// @param amount Amount of upkeep tokens deposited
event UpkeepDeposited(address indexed strategy, address indexed depositor, uint256 amount);
/// @notice Emitted when upkeep tokens are withdrawn
/// @param strategy Address of the strategy
/// @param withdrawer Address of the withdrawer (main manager of the strategy)
/// @param amount Amount of upkeep tokens withdrawn
event UpkeepWithdrawn(address indexed strategy, address indexed withdrawer, uint256 amount);
/// @notice Emitted when an upkeep withdrawal is proposed (start of 24h timelock)
/// @param strategy Address of the strategy
/// @param mainManager Address of the main manager who proposed the withdrawal
/// @param amount Amount of upkeep tokens to withdraw
/// @param effectiveTime Timestamp when withdrawal can be executed
event UpkeepWithdrawalProposed(
address indexed strategy, address indexed mainManager, uint256 amount, uint256 effectiveTime
);
/// @notice Emitted when a pending upkeep withdrawal is cancelled (e.g., during governance takeover)
/// @param strategy Address of the strategy
event UpkeepWithdrawalCancelled(address indexed strategy);
/// @notice Emitted when upkeep tokens are spent for validation
/// @param strategy Address of the strategy
/// @param amount Amount of upkeep tokens spent
/// @param balance Current balance of the strategy
/// @param claimableUpkeep Amount of upkeep tokens claimable
event UpkeepSpent(address indexed strategy, uint256 amount, uint256 balance, uint256 claimableUpkeep);
/// @notice Emitted when a secondary manager is added to a strategy
/// @param strategy Address of the strategy
/// @param manager Address of the manager added
event SecondaryManagerAdded(address indexed strategy, address indexed manager);
/// @notice Emitted when a secondary manager is removed from a strategy
/// @param strategy Address of the strategy
/// @param manager Address of the manager removed
event SecondaryManagerRemoved(address indexed strategy, address indexed manager);
/// @notice Emitted when a primary manager is changed
/// @param strategy Address of the strategy
/// @param oldManager Address of the old primary manager
/// @param newManager Address of the new primary manager
/// @param feeRecipient Address of the new fee recipient
event PrimaryManagerChanged(
address indexed strategy, address indexed oldManager, address indexed newManager, address feeRecipient
);
/// @notice Emitted when a change to primary manager is proposed by a secondary manager
/// @param strategy Address of the strategy
/// @param proposer Address of the secondary manager who made the proposal
/// @param newManager Address of the proposed new primary manager
/// @param effectiveTime Timestamp when the proposal can be executed
event PrimaryManagerChangeProposed(
address indexed strategy,
address indexed proposer,
address indexed newManager,
address feeRecipient,
uint256 effectiveTime
);
/// @notice Emitted when a primary manager change proposal is cancelled
/// @param strategy Address of the strategy
/// @param cancelledManager Address of the manager that was proposed
event PrimaryManagerChangeCancelled(address indexed strategy, address indexed cancelledManager);
/// @notice Emitted when the High Water Mark for a strategy is reset to PPS
/// @param strategy Address of the strategy
/// @param newHWM The new High Water Mark (PPS)
event HighWaterMarkReset(address indexed strategy, uint256 indexed newHWM);
/// @notice Emitted when a PPS update is stale (Validators could get slashed for innactivity)
/// @param strategy Address of the strategy
/// @param updateAuthority Address of the update authority
/// @param timestamp Timestamp of the stale update
event StaleUpdate(address indexed strategy, address indexed updateAuthority, uint256 timestamp);
/// @notice Emitted when the global hooks Merkle root is being updated
/// @param root New root value
/// @param effectiveTime Timestamp when the root becomes effective
event GlobalHooksRootUpdateProposed(bytes32 indexed root, uint256 effectiveTime);
/// @notice Emitted when the global hooks Merkle root is updated
/// @param oldRoot Previous root value
/// @param newRoot New root value
event GlobalHooksRootUpdated(bytes32 indexed oldRoot, bytes32 newRoot);
/// @notice Emitted when a strategy-specific hooks Merkle root is updated
/// @param strategy Address of the strategy
/// @param oldRoot Previous root value (may be zero)
/// @param newRoot New root value
event StrategyHooksRootUpdated(address indexed strategy, bytes32 oldRoot, bytes32 newRoot);
/// @notice Emitted when a strategy-specific hooks Merkle root is proposed
/// @param strategy Address of the strategy
/// @param proposer Address of the account proposing the new root
/// @param root New root value
/// @param effectiveTime Timestamp when the root becomes effective
event StrategyHooksRootUpdateProposed(
address indexed strategy, address indexed proposer, bytes32 root, uint256 effectiveTime
);
/// @notice Emitted when a proposed global hooks root update is vetoed by SuperGovernor
/// @param vetoed Whether the root is being vetoed (true) or unvetoed (false)
/// @param root The root value affected
event GlobalHooksRootVetoStatusChanged(bool vetoed, bytes32 indexed root);
/// @notice Emitted when a strategy's hooks Merkle root veto status changes
/// @param strategy Address of the strategy
/// @param vetoed Whether the root is being vetoed (true) or unvetoed (false)
/// @param root The root value affected
event StrategyHooksRootVetoStatusChanged(address indexed strategy, bool vetoed, bytes32 indexed root);
/// @notice Emitted when a strategy's deviation threshold is updated
/// @param strategy Address of the strategy
/// @param deviationThreshold New deviation threshold (abs diff/current)
event DeviationThresholdUpdated(address indexed strategy, uint256 deviationThreshold);
/// @notice Emitted when the hooks root update timelock is changed
/// @param newTimelock New timelock duration in seconds
event HooksRootUpdateTimelockChanged(uint256 newTimelock);
/// @notice Emitted when global leaves status is changed for a strategy
/// @param strategy Address of the strategy
/// @param leaves Array of leaf hashes that had their status changed
/// @param statuses Array of new banned statuses (true = banned, false = allowed)
event GlobalLeavesStatusChanged(address indexed strategy, bytes32[] leaves, bool[] statuses);
/// @notice Emitted when upkeep is claimed
/// @param superBank Address of the superBank
/// @param amount Amount of upkeep claimed
event UpkeepClaimed(address indexed superBank, uint256 amount);
/// @notice Emitted when PPS update is too frequent (before minUpdateInterval)
event UpdateTooFrequent();
/// @notice Emitted when PPS update timestamp is not monotonically increasing
event TimestampNotMonotonic();
/// @notice Emitted when PPS update is rejected due to stale signature after unpause
event StaleSignatureAfterUnpause(
address indexed strategy, uint256 signatureTimestamp, uint256 lastUnpauseTimestamp
);
/// @notice Emitted when a strategy does not have enough upkeep balance
event InsufficientUpkeep(address indexed strategy, address indexed strategyAddr, uint256 balance, uint256 cost);
/// @notice Emitted when the provided timestamp is too large
event ProvidedTimestampExceedsBlockTimestamp(
address indexed strategy, uint256 argsTimestamp, uint256 blockTimestamp
);
/// @notice Emitted when a strategy is unknown
event UnknownStrategy(address indexed strategy);
/// @notice Emitted when the old primary manager is removed from the strategy
/// @dev This can happen because of reaching the max number of secondary managers
event OldPrimaryManagerRemoved(address indexed strategy, address indexed oldManager);
/// @notice Emitted when a strategy's PPS is stale
event StrategyPPSStale(address indexed strategy);
/// @notice Emitted when a strategy's PPS is reset
event StrategyPPSStaleReset(address indexed strategy);
/// @notice Emitted when PPS is updated after performance fee skimming
/// @param strategy Address of the strategy
/// @param oldPPS Previous price-per-share value
/// @param newPPS New price-per-share value after fee deduction
/// @param feeAmount Amount of fee skimmed that caused the PPS update
/// @param timestamp Timestamp of the update
event PPSUpdatedAfterSkim(
address indexed strategy, uint256 oldPPS, uint256 newPPS, uint256 feeAmount, uint256 timestamp
);
/// @notice Emitted when a change to minUpdateInterval is proposed
/// @param strategy Address of the strategy
/// @param proposer Address of the manager who made the proposal
/// @param newMinUpdateInterval The proposed new minimum update interval
/// @param effectiveTime Timestamp when the proposal can be executed
event MinUpdateIntervalChangeProposed(
address indexed strategy, address indexed proposer, uint256 newMinUpdateInterval, uint256 effectiveTime
);
/// @notice Emitted when a minUpdateInterval change is executed
/// @param strategy Address of the strategy
/// @param oldMinUpdateInterval Previous minimum update interval
/// @param newMinUpdateInterval New minimum update interval
event MinUpdateIntervalChanged(
address indexed strategy, uint256 oldMinUpdateInterval, uint256 newMinUpdateInterval
);
/// @notice Emitted when a minUpdateInterval change proposal is rejected due to validation failure
/// @param strategy Address of the strategy
/// @param proposedInterval The proposed interval that was rejected
/// @param currentMaxStaleness The current maxStaleness value that caused rejection
event MinUpdateIntervalChangeRejected(
address indexed strategy, uint256 proposedInterval, uint256 currentMaxStaleness
);
/// @notice Emitted when a minUpdateInterval change proposal is cancelled
/// @param strategy Address of the strategy
/// @param cancelledInterval The proposed interval that was cancelled
event MinUpdateIntervalChangeCancelled(address indexed strategy, uint256 cancelledInterval);
/// @notice Emitted when a PPS update is rejected because strategy is paused
/// @param strategy Address of the paused strategy
event PPSUpdateRejectedStrategyPaused(address indexed strategy);
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when address provided is zero
error ZERO_ADDRESS();
/// @notice Thrown when amount provided is zero
error ZERO_AMOUNT();
/// @notice Thrown when vault creation parameters are invalid (empty name or symbol)
error INVALID_VAULT_PARAMS();
/// @notice Thrown when array length is zero
error ZERO_ARRAY_LENGTH();
/// @notice Thrown when array length is zero
error ARRAY_LENGTH_MISMATCH();
/// @notice Thrown when asset is invalid
error INVALID_ASSET();
/// @notice Thrown when insufficient upkeep balance for operation
error INSUFFICIENT_UPKEEP();
/// @notice Thrown when caller is not authorized
error CALLER_NOT_AUTHORIZED();
/// @notice Thrown when caller is not an approved PPS oracle
error UNAUTHORIZED_PPS_ORACLE();
/// @notice Thrown when caller is not authorized for update
error UNAUTHORIZED_UPDATE_AUTHORITY();
/// @notice Thrown when strategy address is not a known SuperVault strategy
error UNKNOWN_STRATEGY();
/// @notice Thrown when trying to unpause a strategy that is not paused
error STRATEGY_NOT_PAUSED();
/// @notice Thrown when trying to pause a strategy that is already paused
error STRATEGY_ALREADY_PAUSED();
/// @notice Thrown when array index is out of bounds
error INDEX_OUT_OF_BOUNDS();
/// @notice Thrown when attempting to add a manager that already exists
error MANAGER_ALREADY_EXISTS();
/// @notice Thrown when attempting to add a manager that is the primary manager
error SECONDARY_MANAGER_CANNOT_BE_PRIMARY();
/// @notice Thrown when there is no pending global hooks root change
error NO_PENDING_GLOBAL_ROOT_CHANGE();
/// @notice Thrown when attempting to execute a hooks root change before timelock has elapsed
error ROOT_UPDATE_NOT_READY();
/// @notice Thrown when a provided hook fails Merkle proof validation
error HOOK_VALIDATION_FAILED();
/// @notice Thrown when manager is not found
error MANAGER_NOT_FOUND();
/// @notice Thrown when there is no pending manager change proposal
error NO_PENDING_MANAGER_CHANGE();
/// @notice Thrown when caller is not authorized to update settings
error UNAUTHORIZED_CALLER();
/// @notice Thrown when the timelock for a proposed change has not expired
error TIMELOCK_NOT_EXPIRED();
/// @notice Thrown when an array length is invalid
error INVALID_ARRAY_LENGTH();
/// @notice Thrown when the provided maxStaleness is less than the minimum required staleness
error MAX_STALENESS_TOO_LOW();
/// @notice Thrown when arrays have mismatched lengths
error MISMATCHED_ARRAY_LENGTHS();
/// @notice Thrown when timestamp is invalid
error INVALID_TIMESTAMP(uint256 index);
/// @notice Thrown when too many secondary managers are added
error TOO_MANY_SECONDARY_MANAGERS();
/// @notice Thrown when upkeep withdrawal timelock has not passed yet
error UPKEEP_WITHDRAWAL_NOT_READY();
/// @notice Thrown when no pending upkeep withdrawal request exists
error UPKEEP_WITHDRAWAL_NOT_FOUND();
/// @notice PPS must decrease after skimming fees
error PPS_MUST_DECREASE_AFTER_SKIM();
/// @notice PPS deduction is larger than the maximum allowed fee rate
error PPS_DEDUCTION_TOO_LARGE();
/// @notice Thrown when no minUpdateInterval change proposal is pending
error NO_PENDING_MIN_UPDATE_INTERVAL_CHANGE();
/// @notice Thrown when minUpdateInterval >= maxStaleness
error MIN_UPDATE_INTERVAL_TOO_HIGH();
/// @notice Thrown when trying to update PPS while strategy is paused
error STRATEGY_PAUSED();
/// @notice Thrown when trying to update PPS while PPS is stale
error PPS_STALE();
/*//////////////////////////////////////////////////////////////
VAULT CREATION
//////////////////////////////////////////////////////////////*/
/// @notice Creates a new SuperVault trio (SuperVault, SuperVaultStrategy, SuperVaultEscrow)
/// @param params Parameters for the new vault creation
/// @return superVault Address of the created SuperVault
/// @return strategy Address of the created SuperVaultStrategy
/// @return escrow Address of the created SuperVaultEscrow
function createVault(VaultCreationParams calldata params)
external
returns (address superVault, address strategy, address escrow);
/*//////////////////////////////////////////////////////////////
PPS UPDATE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Arguments for batch forwarding PPS updates
/// @param strategies Array of strategy addresses
/// @param ppss Array of price-per-share values
/// @param timestamps Array of timestamps when values were generated
/// @param updateAuthority Address of the update authority
struct ForwardPPSArgs {
address[] strategies;
uint256[] ppss;
uint256[] timestamps;
address updateAuthority;
}
/// @notice Batch forwards validated PPS updates to multiple strategies
/// @param args Struct containing all batch PPS update parameters
function forwardPPS(ForwardPPSArgs calldata args) external;
/// @notice Updates PPS directly after performance fee skimming
/// @dev Only callable by the strategy contract itself (msg.sender must be a registered strategy)
/// @param newPPS New price-per-share value after fee deduction
/// @param feeAmount Amount of fee that was skimmed (for event logging)
function updatePPSAfterSkim(uint256 newPPS, uint256 feeAmount) external;
/*//////////////////////////////////////////////////////////////
UPKEEP MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Deposits upkeep tokens for strategy upkeep
/// @dev The upkeep token is configurable per chain (UP on mainnet, WETH on L2s, etc.)
/// @param strategy Address of the strategy to deposit for
/// @param amount Amount of upkeep tokens to deposit
function depositUpkeep(address strategy, uint256 amount) external;
/// @notice Proposes withdrawal of upkeep tokens from strategy upkeep balance (starts 24h timelock)
/// @dev Only the main manager can propose. Withdraws full balance at time of proposal.
/// @param strategy Address of the strategy to withdraw from
function proposeWithdrawUpkeep(address strategy) external;
/// @notice Executes a pending upkeep withdrawal after 24h timelock
/// @dev Anyone can execute, but funds go to the main manager of the strategy
/// @param strategy Address of the strategy to withdraw from
function executeWithdrawUpkeep(address strategy) external;
/// @notice Claims upkeep tokens from the contract
/// @param amount Amount of upkeep tokens to claim
function claimUpkeep(uint256 amount) external;
/*//////////////////////////////////////////////////////////////
PAUSE MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Manually pauses a strategy
/// @param strategy Address of the strategy to pause
function pauseStrategy(address strategy) external;
/// @notice Manually unpauses a strategy
/// @param strategy Address of the strategy to unpause
function unpauseStrategy(address strategy) external;
/*//////////////////////////////////////////////////////////////
MANAGER MANAGEMENT FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Adds a secondary manager to a strategy
/// @notice A manager can either be secondary or primary
/// @param strategy Address of the strategy
/// @param manager Address of the manager to add
function addSecondaryManager(address strategy, address manager) external;
/// @notice Removes a secondary manager from a strategy
/// @param strategy Address of the strategy
/// @param manager Address of the manager to remove
function removeSecondaryManager(address strategy, address manager) external;
/// @notice Changes the primary manager of a strategy immediately (only callable by SuperGovernor)
/// @notice A manager can either be secondary or primary
/// @param strategy Address of the strategy
/// @param newManager Address of the new primary manager
/// @param feeRecipient Address of the new fee recipient
function changePrimaryManager(address strategy, address newManager, address feeRecipient) external;
/// @notice Proposes a change to the primary manager (callable by secondary managers)
/// @notice A manager can either be secondary or primary
/// @param strategy Address of the strategy
/// @param newManager Address of the proposed new primary manager
/// @param feeRecipient Address of the new fee recipient
function proposeChangePrimaryManager(address strategy, address newManager, address feeRecipient) external;
/// @notice Cancels a pending primary manager change proposal
/// @dev Only the current primary manager can cancel the proposal
/// @param strategy Address of the strategy
function cancelChangePrimaryManager(address strategy) external;
/// @notice Executes a previously proposed change to the primary manager after timelock
/// @param strategy Address of the strategy
function executeChangePrimaryManager(address strategy) external;
/// @notice Resets the strategy's performance-fee high-water mark to PPS
/// @dev Only callable by SuperGovernor
/// @param strategy Address of the strategy
function resetHighWaterMark(address strategy) external;
/*//////////////////////////////////////////////////////////////
HOOK VALIDATION FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Sets a new hooks root update timelock duration
/// @param newTimelock The new timelock duration in seconds
function setHooksRootUpdateTimelock(uint256 newTimelock) external;
/// @notice Proposes an update to the global hooks Merkle root
/// @dev Only callable by SUPER_GOVERNOR
/// @param newRoot New Merkle root for global hooks validation
function proposeGlobalHooksRoot(bytes32 newRoot) external;
/// @notice Executes a previously proposed global hooks root update after timelock period
/// @dev Can be called by anyone after the timelock period has elapsed
function executeGlobalHooksRootUpdate() external;
/// @notice Proposes an update to a strategy-specific hooks Merkle root
/// @dev Only callable by the main manager for the strategy
/// @param strategy Address of the strategy
/// @param newRoot New Merkle root for strategy-specific hooks
function proposeStrategyHooksRoot(address strategy, bytes32 newRoot) external;
/// @notice Executes a previously proposed strategy hooks root update after timelock period
/// @dev Can be called by anyone after the timelock period has elapsed
/// @param strategy Address of the strategy whose root update to execute
function executeStrategyHooksRootUpdate(address strategy) external;
/// @notice Set veto status for the global hooks root
/// @dev Only callable by SuperGovernor
/// @param vetoed Whether to veto (true) or unveto (false) the global hooks root
function setGlobalHooksRootVetoStatus(bool vetoed) external;
/// @notice Set veto status for a strategy-specific hooks root
/// @notice Sets the veto status of a strategy's hooks Merkle root
/// @param strategy Address of the strategy
/// @param vetoed Whether to veto (true) or unveto (false)
function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external;
/// @notice Updates the deviation threshold for a strategy
/// @param strategy Address of the strategy
/// @param deviationThreshold_ New deviation threshold (abs diff/current ratio, scaled by 1e18)
function updateDeviationThreshold(address strategy, uint256 deviationThreshold_) external;
/// @notice Changes the banned status of global leaves for a specific strategy
/// @dev Only callable by the primary manager of the strategy
/// @param leaves Array of leaf hashes to change status for
/// @param statuses Array of banned statuses (true = banned, false = allowed)
/// @param strategy Address of the strategy to change banned leaves for
function changeGlobalLeavesStatus(bytes32[] memory leaves, bool[] memory statuses, address strategy) external;
/*//////////////////////////////////////////////////////////////
MIN UPDATE INTERVAL MANAGEMENT
//////////////////////////////////////////////////////////////*/
/// @notice Proposes a change to the minimum update interval for a strategy
/// @param strategy Address of the strategy
/// @param newMinUpdateInterval The proposed new minimum update interval (in seconds)
/// @dev Only the main manager can propose. Must be less than maxStaleness
function proposeMinUpdateIntervalChange(address strategy, uint256 newMinUpdateInterval) external;
/// @notice Executes a previously proposed minUpdateInterval change after timelock
/// @param strategy Address of the strategy whose minUpdateInterval to update
/// @dev Can be called by anyone after the timelock period has elapsed
function executeMinUpdateIntervalChange(address strategy) external;
/// @notice Cancels a pending minUpdateInterval change proposal
/// @param strategy Address of the strategy
/// @dev Only the main manager can cancel
function cancelMinUpdateIntervalChange(address strategy) external;
/// @notice Gets the proposed minUpdateInterval and effective time
/// @param strategy Address of the strategy
/// @return proposedInterval The proposed minimum update interval
/// @return effectiveTime The timestamp when the proposed interval becomes effective
function getProposedMinUpdateInterval(address strategy)
external
view
returns (uint256 proposedInterval, uint256 effectiveTime);
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Returns the current vault creation nonce
/// @dev This nonce is incremented every time a new vault is created
/// @return Current vault creation nonce
function getCurrentNonce() external view returns (uint256);
/// @notice Check if the global hooks root is currently vetoed
/// @return vetoed True if the global hooks root is vetoed
function isGlobalHooksRootVetoed() external view returns (bool vetoed);
/// @notice Check if a strategy hooks root is currently vetoed
/// @param strategy Address of the strategy to check
/// @return vetoed True if the strategy hooks root is vetoed
function isStrategyHooksRootVetoed(address strategy) external view returns (bool vetoed);
/// @notice Gets the current hooks root update timelock duration
/// @return The current timelock duration in seconds
function getHooksRootUpdateTimelock() external view returns (uint256);
/// @notice Gets the current PPS (price-per-share) for a strategy
/// @param strategy Address of the strategy
/// @return pps Current price-per-share value
function getPPS(address strategy) external view returns (uint256 pps);
/// @notice Gets the last update timestamp for a strategy's PPS
/// @param strategy Address of the strategy
/// @return timestamp Last update timestamp
function getLastUpdateTimestamp(address strategy) external view returns (uint256 timestamp);
/// @notice Gets the minimum update interval for a strategy
/// @param strategy Address of the strategy
/// @return interval Minimum time between updates
function getMinUpdateInterval(address strategy) external view returns (uint256 interval);
/// @notice Gets the maximum staleness period for a strategy
/// @param strategy Address of the strategy
/// @return staleness Maximum time allowed between updates
function getMaxStaleness(address strategy) external view returns (uint256 staleness);
/// @notice Gets the deviation threshold for a strategy
/// @param strategy Address of the strategy
/// @return deviationThreshold The current deviation threshold (abs diff/current ratio, scaled by 1e18)
function getDeviationThreshold(address strategy) external view returns (uint256 deviationThreshold);
/// @notice Checks if a strategy is currently paused
/// @param strategy Address of the strategy
/// @return isPaused True if paused, false otherwise
function isStrategyPaused(address strategy) external view returns (bool isPaused);
/// @notice Checks if a strategy's PPS is stale
/// @dev PPS is automatically set to stale when the strategy is paused due to
/// lack of upkeep payment in `SuperVaultAggregator`
/// @param strategy Address of the strategy
/// @return isStale True if stale, false otherwise
function isPPSStale(address strategy) external view returns (bool isStale);
/// @notice Gets the last unpause timestamp for a strategy
/// @param strategy Address of the strategy
/// @return timestamp Last unpause timestamp (0 if never unpaused)
function getLastUnpauseTimestamp(address strategy) external view returns (uint256 timestamp);
/// @notice Gets the current upkeep balance for a strategy
/// @param strategy Address of the strategy
/// @return balance Current upkeep balance in upkeep tokens
function getUpkeepBalance(address strategy) external view returns (uint256 balance);
/// @notice Gets the main manager for a strategy
/// @param strategy Address of the strategy
/// @return manager Address of the main manager
function getMainManager(address strategy) external view returns (address manager);
/// @notice Gets pending primary manager change details
/// @param strategy Address of the strategy
/// @return proposedManager Address of the proposed new manager (address(0) if no pending change)
/// @return effectiveTime Timestamp when the change can be executed (0 if no pending change)
function getPendingManagerChange(address strategy)
external
view
returns (address proposedManager, uint256 effectiveTime);
/// @notice Checks if an address is the main manager for a strategy
/// @param manager Address of the manager
/// @param strategy Address of the strategy
/// @return isMainManager True if the address is the main manager, false otherwise
function isMainManager(address manager, address strategy) external view returns (bool isMainManager);
/// @notice Gets all secondary managers for a strategy
/// @param strategy Address of the strategy
/// @return secondaryManagers Array of secondary manager addresses
function getSecondaryManagers(address strategy) external view returns (address[] memory secondaryManagers);
/// @notice Checks if an address is a secondary manager for a strategy
/// @param manager Address of the manager
/// @param strategy Address of the strategy
/// @return isSecondaryManager True if the address is a secondary manager, false otherwise
function isSecondaryManager(address manager, address strategy) external view returns (bool isSecondaryManager);
/// @dev Internal helper function to check if an address is any kind of manager (primary or secondary)
/// @param manager Address to check
/// @param strategy The strategy to check against
/// @return True if the address is either the primary manager or a secondary manager
function isAnyManager(address manager, address strategy) external view returns (bool);
/// @notice Gets all created SuperVaults
/// @return Array of SuperVault addresses
function getAllSuperVaults() external view returns (address[] memory);
/// @notice Gets a SuperVault by index
/// @param index The index of the SuperVault
/// @return The SuperVault address at the given index
function superVaults(uint256 index) external view returns (address);
/// @notice Gets all created SuperVaultStrategies
/// @return Array of SuperVaultStrategy addresses
function getAllSuperVaultStrategies() external view returns (address[] memory);
/// @notice Gets a SuperVaultStrategy by index
/// @param index The index of the SuperVaultStrategy
/// @return The SuperVaultStrategy address at the given index
function superVaultStrategies(uint256 index) external view returns (address);
/// @notice Gets all created SuperVaultEscrows
/// @return Array of SuperVaultEscrow addresses
function getAllSuperVaultEscrows() external view returns (address[] memory);
/// @notice Gets a SuperVaultEscrow by index
/// @param index The index of the SuperVaultEscrow
/// @return The SuperVaultEscrow address at the given index
function superVaultEscrows(uint256 index) external view returns (address);
/// @notice Validates a hook against both global and strategy-specific Merkle roots
/// @param strategy Address of the strategy
/// @param args Arguments for hook validation
/// @return isValid True if the hook is valid against either root
function validateHook(address strategy, ValidateHookArgs calldata args) external view returns (bool isValid);
/// @notice Batch validates multiple hooks against Merkle roots
/// @param strategy Address of the strategy
/// @param argsArray Array of hook validation arguments
/// @return validHooks Array of booleans indicating which hooks are valid
function validateHooks(
address strategy,
ValidateHookArgs[] calldata argsArray
)
external
view
returns (bool[] memory validHooks);
/// @notice Gets the current global hooks Merkle root
/// @return root The current global hooks Merkle root
function getGlobalHooksRoot() external view returns (bytes32 root);
/// @notice Gets the proposed global hooks root and effective time
/// @return root The proposed global hooks Merkle root
/// @return effectiveTime The timestamp when the proposed root becomes effective
function getProposedGlobalHooksRoot() external view returns (bytes32 root, uint256 effectiveTime);
/// @notice Checks if the global hooks root is active (timelock period has passed)
/// @return isActive True if the global hooks root is active
function isGlobalHooksRootActive() external view returns (bool);
/// @notice Gets the hooks Merkle root for a specific strategy
/// @param strategy Address of the strategy
/// @return root The strategy-specific hooks Merkle root
function getStrategyHooksRoot(address strategy) external view returns (bytes32 root);
/// @notice Gets the proposed strategy hooks root and effective time
/// @param strategy Address of the strategy
/// @return root The proposed strategy hooks Merkle root
/// @return effectiveTime The timestamp when the proposed root becomes effective
function getProposedStrategyHooksRoot(address strategy) external view returns (bytes32 root, uint256 effectiveTime);
/// @notice Gets the total number of SuperVaults
/// @return count The total number of SuperVaults
function getSuperVaultsCount() external view returns (uint256);
/// @notice Gets the total number of SuperVaultStrategies
/// @return count The total number of SuperVaultStrategies
function getSuperVaultStrategiesCount() external view returns (uint256);
/// @notice Gets the total number of SuperVaultEscrows
/// @return count The total number of SuperVaultEscrows
function getSuperVaultEscrowsCount() external view returns (uint256);
}
AssetMetadataLib.sol 34 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
/// @title AssetMetadataLib
/// @author Superform Labs
/// @notice Library for handling ERC20 metadata operations
library AssetMetadataLib {
error INVALID_ASSET();
/**
* @notice Attempts to fetch an asset's decimals
* @dev A return value of false indicates that the attempt failed in some way
* @param asset_ The address of the token to query
* @return ok Boolean indicating if the operation was successful
* @return assetDecimals The token's decimals if successful, 0 otherwise
*/
function tryGetAssetDecimals(address asset_) internal view returns (bool ok, uint8 assetDecimals) {
if (asset_.code.length == 0) revert INVALID_ASSET();
(bool success, bytes memory encodedDecimals) =
address(asset_).staticcall(abi.encodeCall(IERC20Metadata.decimals, ()));
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
// casting to 'uint8' is safe because the returned decimals is a valid uint8
// forge-lint: disable-next-line(unsafe-typecast)
return (true, uint8(returnedDecimals));
}
}
return (false, 0);
}
}
Panic.sol 57 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)
pragma solidity ^0.8.20;
/**
* @dev Helper library for emitting standardized panic codes.
*
* ```solidity
* contract Example {
* using Panic for uint256;
*
* // Use any of the declared internal constants
* function foo() { Panic.GENERIC.panic(); }
*
* // Alternatively
* function foo() { Panic.panic(Panic.GENERIC); }
* }
* ```
*
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
*
* _Available since v5.1._
*/
// slither-disable-next-line unused-state
library Panic {
/// @dev generic / unspecified error
uint256 internal constant GENERIC = 0x00;
/// @dev used by the assert() builtin
uint256 internal constant ASSERT = 0x01;
/// @dev arithmetic underflow or overflow
uint256 internal constant UNDER_OVERFLOW = 0x11;
/// @dev division or modulo by zero
uint256 internal constant DIVISION_BY_ZERO = 0x12;
/// @dev enum conversion error
uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
/// @dev invalid encoding in storage
uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
/// @dev empty array pop
uint256 internal constant EMPTY_ARRAY_POP = 0x31;
/// @dev array out of bounds access
uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
/// @dev resource error (too large allocation or too large array)
uint256 internal constant RESOURCE_ERROR = 0x41;
/// @dev calling invalid internal function
uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
/// @dev Reverts with a panic code. Recommended to use with
/// the internal constants with predefined codes.
function panic(uint256 code) internal pure {
assembly ("memory-safe") {
mstore(0x00, 0x4e487b71)
mstore(0x20, code)
revert(0x1c, 0x24)
}
}
}
SafeCast.sol 1162 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeCast {
/**
* @dev Value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev An uint value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toUint248(uint256 value) internal pure returns (uint248) {
if (value > type(uint248).max) {
revert SafeCastOverflowedUintDowncast(248, value);
}
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toUint240(uint256 value) internal pure returns (uint240) {
if (value > type(uint240).max) {
revert SafeCastOverflowedUintDowncast(240, value);
}
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toUint232(uint256 value) internal pure returns (uint232) {
if (value > type(uint232).max) {
revert SafeCastOverflowedUintDowncast(232, value);
}
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
if (value > type(uint224).max) {
revert SafeCastOverflowedUintDowncast(224, value);
}
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toUint216(uint256 value) internal pure returns (uint216) {
if (value > type(uint216).max) {
revert SafeCastOverflowedUintDowncast(216, value);
}
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toUint208(uint256 value) internal pure returns (uint208) {
if (value > type(uint208).max) {
revert SafeCastOverflowedUintDowncast(208, value);
}
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toUint200(uint256 value) internal pure returns (uint200) {
if (value > type(uint200).max) {
revert SafeCastOverflowedUintDowncast(200, value);
}
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toUint192(uint256 value) internal pure returns (uint192) {
if (value > type(uint192).max) {
revert SafeCastOverflowedUintDowncast(192, value);
}
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toUint184(uint256 value) internal pure returns (uint184) {
if (value > type(uint184).max) {
revert SafeCastOverflowedUintDowncast(184, value);
}
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toUint176(uint256 value) internal pure returns (uint176) {
if (value > type(uint176).max) {
revert SafeCastOverflowedUintDowncast(176, value);
}
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toUint168(uint256 value) internal pure returns (uint168) {
if (value > type(uint168).max) {
revert SafeCastOverflowedUintDowncast(168, value);
}
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toUint160(uint256 value) internal pure returns (uint160) {
if (value > type(uint160).max) {
revert SafeCastOverflowedUintDowncast(160, value);
}
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toUint152(uint256 value) internal pure returns (uint152) {
if (value > type(uint152).max) {
revert SafeCastOverflowedUintDowncast(152, value);
}
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toUint144(uint256 value) internal pure returns (uint144) {
if (value > type(uint144).max) {
revert SafeCastOverflowedUintDowncast(144, value);
}
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toUint136(uint256 value) internal pure returns (uint136) {
if (value > type(uint136).max) {
revert SafeCastOverflowedUintDowncast(136, value);
}
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) {
revert SafeCastOverflowedUintDowncast(128, value);
}
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toUint120(uint256 value) internal pure returns (uint120) {
if (value > type(uint120).max) {
revert SafeCastOverflowedUintDowncast(120, value);
}
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toUint112(uint256 value) internal pure returns (uint112) {
if (value > type(uint112).max) {
revert SafeCastOverflowedUintDowncast(112, value);
}
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toUint104(uint256 value) internal pure returns (uint104) {
if (value > type(uint104).max) {
revert SafeCastOverflowedUintDowncast(104, value);
}
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
if (value > type(uint96).max) {
revert SafeCastOverflowedUintDowncast(96, value);
}
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toUint88(uint256 value) internal pure returns (uint88) {
if (value > type(uint88).max) {
revert SafeCastOverflowedUintDowncast(88, value);
}
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toUint80(uint256 value) internal pure returns (uint80) {
if (value > type(uint80).max) {
revert SafeCastOverflowedUintDowncast(80, value);
}
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toUint72(uint256 value) internal pure returns (uint72) {
if (value > type(uint72).max) {
revert SafeCastOverflowedUintDowncast(72, value);
}
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
if (value > type(uint64).max) {
revert SafeCastOverflowedUintDowncast(64, value);
}
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toUint56(uint256 value) internal pure returns (uint56) {
if (value > type(uint56).max) {
revert SafeCastOverflowedUintDowncast(56, value);
}
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toUint48(uint256 value) internal pure returns (uint48) {
if (value > type(uint48).max) {
revert SafeCastOverflowedUintDowncast(48, value);
}
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toUint40(uint256 value) internal pure returns (uint40) {
if (value > type(uint40).max) {
revert SafeCastOverflowedUintDowncast(40, value);
}
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
if (value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, value);
}
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toUint24(uint256 value) internal pure returns (uint24) {
if (value > type(uint24).max) {
revert SafeCastOverflowedUintDowncast(24, value);
}
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
if (value > type(uint16).max) {
revert SafeCastOverflowedUintDowncast(16, value);
}
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toUint8(uint256 value) internal pure returns (uint8) {
if (value > type(uint8).max) {
revert SafeCastOverflowedUintDowncast(8, value);
}
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(248, value);
}
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(240, value);
}
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(232, value);
}
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(224, value);
}
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(216, value);
}
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(208, value);
}
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(200, value);
}
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(192, value);
}
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(184, value);
}
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(176, value);
}
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(168, value);
}
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(160, value);
}
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(152, value);
}
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(144, value);
}
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(136, value);
}
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(128, value);
}
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(120, value);
}
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(112, value);
}
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(104, value);
}
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(96, value);
}
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(88, value);
}
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(80, value);
}
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(72, value);
}
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(64, value);
}
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(56, value);
}
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(48, value);
}
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(40, value);
}
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(32, value);
}
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(24, value);
}
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(16, value);
}
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(8, value);
}
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
if (value > uint256(type(int256).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int256(value);
}
/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function toUint(bool b) internal pure returns (uint256 u) {
assembly ("memory-safe") {
u := iszero(iszero(b))
}
}
}
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
Create2.sol 92 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
Errors.sol 34 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
Arrays.sol 552 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Arrays.sol)
// This file was procedurally generated from scripts/generate/templates/Arrays.js.
pragma solidity ^0.8.20;
import {Comparators} from "./Comparators.sol";
import {SlotDerivation} from "./SlotDerivation.sol";
import {StorageSlot} from "./StorageSlot.sol";
import {Math} from "./math/Math.sol";
/**
* @dev Collection of functions related to array types.
*/
library Arrays {
using SlotDerivation for bytes32;
using StorageSlot for bytes32;
/**
* @dev Sort an array of uint256 (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
uint256[] memory array,
function(uint256, uint256) pure returns (bool) comp
) internal pure returns (uint256[] memory) {
_quickSort(_begin(array), _end(array), comp);
return array;
}
/**
* @dev Variant of {sort} that sorts an array of uint256 in increasing order.
*/
function sort(uint256[] memory array) internal pure returns (uint256[] memory) {
sort(array, Comparators.lt);
return array;
}
/**
* @dev Sort an array of address (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
address[] memory array,
function(address, address) pure returns (bool) comp
) internal pure returns (address[] memory) {
sort(_castToUint256Array(array), _castToUint256Comp(comp));
return array;
}
/**
* @dev Variant of {sort} that sorts an array of address in increasing order.
*/
function sort(address[] memory array) internal pure returns (address[] memory) {
sort(_castToUint256Array(array), Comparators.lt);
return array;
}
/**
* @dev Sort an array of bytes32 (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
* array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
bytes32[] memory array,
function(bytes32, bytes32) pure returns (bool) comp
) internal pure returns (bytes32[] memory) {
sort(_castToUint256Array(array), _castToUint256Comp(comp));
return array;
}
/**
* @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
*/
function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
sort(_castToUint256Array(array), Comparators.lt);
return array;
}
/**
* @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops
* at end (exclusive). Sorting follows the `comp` comparator.
*
* Invariant: `begin <= end`. This is the case when initially called by {sort} and is preserved in subcalls.
*
* IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should
* be used only if the limits are within a memory array.
*/
function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
unchecked {
if (end - begin < 0x40) return;
// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;
for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
}
}
_swap(begin, pos); // Swap pivot into place
_quickSort(begin, pos, comp); // Sort the left side of the pivot
_quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot
}
}
/**
* @dev Pointer to the memory location of the first element of `array`.
*/
function _begin(uint256[] memory array) private pure returns (uint256 ptr) {
assembly ("memory-safe") {
ptr := add(array, 0x20)
}
}
/**
* @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(uint256[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
}
}
/**
* @dev Load memory word (as a uint256) at location `ptr`.
*/
function _mload(uint256 ptr) private pure returns (uint256 value) {
assembly {
value := mload(ptr)
}
}
/**
* @dev Swaps the elements memory location `ptr1` and `ptr2`.
*/
function _swap(uint256 ptr1, uint256 ptr2) private pure {
assembly {
let value1 := mload(ptr1)
let value2 := mload(ptr2)
mstore(ptr1, value2)
mstore(ptr2, value1)
}
}
/// @dev Helper: low level cast address memory array to uint256 memory array
function _castToUint256Array(address[] memory input) private pure returns (uint256[] memory output) {
assembly {
output := input
}
}
/// @dev Helper: low level cast bytes32 memory array to uint256 memory array
function _castToUint256Array(bytes32[] memory input) private pure returns (uint256[] memory output) {
assembly {
output := input
}
}
/// @dev Helper: low level cast address comp function to uint256 comp function
function _castToUint256Comp(
function(address, address) pure returns (bool) input
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
assembly {
output := input
}
}
/// @dev Helper: low level cast bytes32 comp function to uint256 comp function
function _castToUint256Comp(
function(bytes32, bytes32) pure returns (bool) input
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
assembly {
output := input
}
}
/**
* @dev Searches a sorted `array` and returns the first index that contains
* a value greater or equal to `element`. If no such index exists (i.e. all
* values in the array are strictly less than `element`), the array length is
* returned. Time complexity O(log n).
*
* NOTE: The `array` is expected to be sorted in ascending order, and to
* contain no repeated elements.
*
* IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks
* support for repeated elements in the array. The {lowerBound} function should
* be used instead.
*/
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
return low - 1;
} else {
return low;
}
}
/**
* @dev Searches an `array` sorted in ascending order and returns the first
* index that contains a value greater or equal than `element`. If no such index
* exists (i.e. all values in the array are strictly less than `element`), the array
* length is returned. Time complexity O(log n).
*
* See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound].
*/
function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value < element) {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
} else {
high = mid;
}
}
return low;
}
/**
* @dev Searches an `array` sorted in ascending order and returns the first
* index that contains a value strictly greater than `element`. If no such index
* exists (i.e. all values in the array are strictly less than `element`), the array
* length is returned. Time complexity O(log n).
*
* See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound].
*/
function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
}
}
return low;
}
/**
* @dev Same as {lowerBound}, but with an array in memory.
*/
function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeMemoryAccess(array, mid) < element) {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
} else {
high = mid;
}
}
return low;
}
/**
* @dev Same as {upperBound}, but with an array in memory.
*/
function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeMemoryAccess(array, mid) > element) {
high = mid;
} else {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
}
}
return low;
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getAddressSlot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getBytes32Slot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getUint256Slot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(bytes[] storage arr, uint256 pos) internal pure returns (StorageSlot.BytesSlot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getBytesSlot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeAccess(string[] storage arr, uint256 pos) internal pure returns (StorageSlot.StringSlot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).getStringSlot();
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(bytes32[] memory arr, uint256 pos) internal pure returns (bytes32 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(bytes[] memory arr, uint256 pos) internal pure returns (bytes memory res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain `pos` is lower than the array length.
*/
function unsafeMemoryAccess(string[] memory arr, uint256 pos) internal pure returns (string memory res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(address[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(uint256[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(bytes[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
/**
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(string[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
}
Hashes.sol 31 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/Hashes.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of standard hash functions.
*
* _Available since v5.1._
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? efficientKeccak256(a, b) : efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
ECDSA.sol 180 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
IERC4626.sol 230 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC4626.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
Initializable.sol 238 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
}
ReentrancyGuardUpgradeable.sol 108 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}
ERC20Upgradeable.sol 330 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20
struct ERC20Storage {
mapping(address account => uint256) _balances;
mapping(address account => mapping(address spender => uint256)) _allowances;
uint256 _totalSupply;
string _name;
string _symbol;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;
function _getERC20Storage() private pure returns (ERC20Storage storage $) {
assembly {
$.slot := ERC20StorageLocation
}
}
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
ERC20Storage storage $ = _getERC20Storage();
$._name = name_;
$._symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
return $._name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
return $._symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/// @inheritdoc IERC20
function totalSupply() public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._totalSupply;
}
/// @inheritdoc IERC20
function balanceOf(address account) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/// @inheritdoc IERC20
function allowance(address owner, address spender) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
ERC20Storage storage $ = _getERC20Storage();
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
$._totalSupply += value;
} else {
uint256 fromBalance = $._balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
$._balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
$._totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
$._balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
ERC20Storage storage $ = _getERC20Storage();
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
$._allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";
EIP712Upgradeable.sol 207 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: The upgradeable version of this contract does not use an immutable cache and recomputes the domain separator
* each time {_domainSeparatorV4} is called. That is cheaper than accessing a cached version in cold storage.
*/
abstract contract EIP712Upgradeable is Initializable, IERC5267 {
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @custom:storage-location erc7201:openzeppelin.storage.EIP712
struct EIP712Storage {
/// @custom:oz-renamed-from _HASHED_NAME
bytes32 _hashedName;
/// @custom:oz-renamed-from _HASHED_VERSION
bytes32 _hashedVersion;
string _name;
string _version;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;
function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
assembly {
$.slot := EIP712StorageLocation
}
}
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
__EIP712_init_unchained(name, version);
}
function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
EIP712Storage storage $ = _getEIP712Storage();
$._name = name;
$._version = version;
// Reset prior values in storage if upgrading
$._hashedName = 0;
$._hashedVersion = 0;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
return _buildDomainSeparator();
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/// @inheritdoc IERC5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
EIP712Storage storage $ = _getEIP712Storage();
// If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
// and the EIP712 domain is not reliable, as it will be missing name and version.
require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Name() internal view virtual returns (string memory) {
EIP712Storage storage $ = _getEIP712Storage();
return $._name;
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Version() internal view virtual returns (string memory) {
EIP712Storage storage $ = _getEIP712Storage();
return $._version;
}
/**
* @dev The hash of the name parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
*/
function _EIP712NameHash() internal view returns (bytes32) {
EIP712Storage storage $ = _getEIP712Storage();
string memory name = _EIP712Name();
if (bytes(name).length > 0) {
return keccak256(bytes(name));
} else {
// If the name is empty, the contract may have been upgraded without initializing the new storage.
// We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
bytes32 hashedName = $._hashedName;
if (hashedName != 0) {
return hashedName;
} else {
return keccak256("");
}
}
}
/**
* @dev The hash of the version parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
*/
function _EIP712VersionHash() internal view returns (bytes32) {
EIP712Storage storage $ = _getEIP712Storage();
string memory version = _EIP712Version();
if (bytes(version).length > 0) {
return keccak256(bytes(version));
} else {
// If the version is empty, the contract may have been upgraded without initializing the new storage.
// We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
bytes32 hashedVersion = $._hashedVersion;
if (hashedVersion != 0) {
return hashedVersion;
} else {
return keccak256("");
}
}
}
}
IERC20Metadata.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20Metadata.sol)
pragma solidity >=0.6.2;
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
ISuperVault.sol 56 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IERC7540Redeem, IERC7540CancelRedeem } from "../../vendor/standards/ERC7540/IERC7540Vault.sol";
import { IERC7741 } from "../../vendor/standards/ERC7741/IERC7741.sol";
/// @title ISuperVault
/// @notice Interface for SuperVault core contract that manages share minting
/// @author Superform Labs
interface ISuperVault is IERC4626, IERC7540Redeem, IERC7741, IERC7540CancelRedeem {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error INVALID_ASSET();
error ZERO_ADDRESS();
error ZERO_AMOUNT();
error INVALID_AMOUNT();
error UNAUTHORIZED();
error DEADLINE_PASSED();
error INVALID_SIGNATURE();
error NOT_IMPLEMENTED();
error INVALID_NONCE();
error INVALID_WITHDRAW_PRICE();
error INVALID_CONTROLLER();
error CONTROLLER_MUST_EQUAL_OWNER();
error RECEIVER_MUST_EQUAL_CONTROLLER();
error NOT_ENOUGH_ASSETS();
error CANCELLATION_REDEEM_REQUEST_PENDING();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event NonceInvalidated(address indexed sender, bytes32 indexed nonce);
event SuperGovernorSet(address indexed superGovernor);
event Initialized(address indexed asset, address indexed strategy, address indexed escrow);
/*//////////////////////////////////////////////////////////////
EXTERNAL METHODS
//////////////////////////////////////////////////////////////*/
/// @notice Burn shares, only callable by strategy
/// @param amount The amount of shares to burn
function burnShares(uint256 amount) external;
/// @notice Get the amount of assets escrowed
function getEscrowedAssets() external view returns (uint256);
/*//////////////////////////////////////////////////////////////
VIEW METHODS
//////////////////////////////////////////////////////////////*/
/// @notice Get the escrow address
function escrow() external view returns (address);
}
IERC7540Vault.sol 267 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import { IERC7741 } from "../ERC7741/IERC7741.sol";
interface IERC7540Operator {
/**
* @dev The event emitted when an operator is set.
*
* @param controller The address of the controller.
* @param operator The address of the operator.
* @param approved The approval status.
*/
event OperatorSet(address indexed controller, address indexed operator, bool approved);
/**
* @dev Sets or removes an operator for the caller.
*
* @param operator The address of the operator.
* @param approved The approval status.
* @return Whether the call was executed successfully or not
*/
function setOperator(address operator, bool approved) external returns (bool);
/**
* @dev Returns `true` if the `operator` is approved as an operator for an `controller`.
*
* @param controller The address of the controller.
* @param operator The address of the operator.
* @return status The approval status
*/
function isOperator(address controller, address operator) external view returns (bool status);
}
interface IERC7540Deposit is IERC7540Operator {
event DepositRequest(
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets
);
/**
* @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit.
*
* - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow.
* - MUST revert if all of assets cannot be requested for deposit.
* - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller,
* approval of ERC-20 tokens from owner to sender is NOT enough.
*
* @param assets the amount of deposit assets to transfer from owner
* @param controller the controller of the request who will be able to operate the request
* @param owner the source of the deposit assets
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token.
*/
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId);
/**
* @dev Returns the amount of requested assets in Pending state.
*
* - MUST NOT include any assets in Claimable state for deposit or mint.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function pendingDepositRequest(uint256 requestId, address controller) external view returns (uint256 pendingAssets);
/**
* @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint.
*
* - MUST NOT include any assets in Pending state.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function claimableDepositRequest(
uint256 requestId,
address controller
)
external
view
returns (uint256 claimableAssets);
/**
* @dev Mints shares Vault shares to receiver by claiming the Request of the controller.
*
* - MUST emit the Deposit event.
* - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator.
*/
function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares);
/**
* @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller.
*
* - MUST emit the Deposit event.
* - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator.
*/
function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets);
}
interface IERC7540Redeem is IERC7540Operator {
event RedeemRequest(
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets
);
/**
* @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem.
*
* - MUST support a redeem Request flow where the control of shares is taken from sender directly
* where msg.sender has ERC-20 approval over the shares of owner.
* - MUST revert if all of shares cannot be requested for redeem.
*
* @param shares the amount of shares to be redeemed to transfer from owner
* @param controller the controller of the request who will be able to operate the request
* @param owner the source of the shares to be redeemed
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault's share token.
*/
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId);
/**
* @dev Returns the amount of requested shares in Pending state.
*
* - MUST NOT include any shares in Claimable state for redeem or withdraw.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function pendingRedeemRequest(uint256 requestId, address controller) external view returns (uint256 pendingShares);
/**
* @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw.
*
* - MUST NOT include any shares in Pending state for redeem or withdraw.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function claimableRedeemRequest(
uint256 requestId,
address controller
)
external
view
returns (uint256 claimableShares);
}
interface IERC7540CancelDeposit {
event CancelDepositRequest(address indexed controller, uint256 indexed requestId, address sender);
event CancelDepositClaim(
address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 assets
);
/**
* @dev Submits a Request for cancelling the pending deposit Request
*
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller,
* approval of ERC-20 tokens from controller to sender is NOT enough.
* - MUST set pendingCancelDepositRequest to `true` for the returned requestId after request
* - MUST increase claimableCancelDepositRequest for the returned requestId after fulfillment
* - SHOULD be claimable using `claimCancelDepositRequest`
* Note: while `pendingCancelDepositRequest` is `true`, `requestDeposit` cannot be called
*/
function cancelDepositRequest(uint256 requestId, address controller) external;
/**
* @dev Returns whether the deposit Request is pending cancelation
*
* - MUST NOT show any variations depending on the caller.
*/
function pendingCancelDepositRequest(uint256 requestId, address controller) external view returns (bool isPending);
/**
* @dev Returns the amount of assets that were canceled from a deposit Request, and can now be claimed.
*
* - MUST NOT show any variations depending on the caller.
*/
function claimableCancelDepositRequest(
uint256 requestId,
address controller
)
external
view
returns (uint256 claimableAssets);
/**
* @dev Claims the canceled deposit assets, and removes the pending cancelation Request
*
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller,
* approval of ERC-20 tokens from controller to sender is NOT enough.
* - MUST set pendingCancelDepositRequest to `false` for the returned requestId after request
* - MUST set claimableCancelDepositRequest to 0 for the returned requestId after fulfillment
*/
function claimCancelDepositRequest(
uint256 requestId,
address receiver,
address controller
)
external
returns (uint256 assets);
}
//IERC7887Redeem
interface IERC7540CancelRedeem {
event CancelRedeemRequest(address indexed controller, uint256 indexed requestId, address sender);
event CancelRedeemClaim(
address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 shares
);
/**
* @dev Submits a Request for cancelling the pending redeem Request
*
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller,
* approval of ERC-20 tokens from controller to sender is NOT enough.
* - MUST set pendingCancelRedeemRequest to `true` for the returned requestId after request
* - MUST increase claimableCancelRedeemRequest for the returned requestId after fulfillment
* - SHOULD be claimable using `claimCancelRedeemRequest`
* Note: while `pendingCancelRedeemRequest` is `true`, `requestRedeem` cannot be called
*/
function cancelRedeemRequest(uint256 requestId, address controller) external;
/**
* @dev Returns whether the redeem Request is pending cancelation
*
* - MUST NOT show any variations depending on the caller.
*/
function pendingCancelRedeemRequest(uint256 requestId, address controller) external view returns (bool isPending);
/**
* @dev Returns the amount of shares that were canceled from a redeem Request, and can now be claimed.
*
* - MUST NOT show any variations depending on the caller.
*/
function claimableCancelRedeemRequest(
uint256 requestId,
address controller
)
external
view
returns (uint256 claimableShares);
/**
* @dev Claims the canceled redeem shares, and removes the pending cancelation Request
*
* - controller MUST be msg.sender unless some unspecified explicit approval is given by the caller,
* approval of ERC-20 tokens from controller to sender is NOT enough.
* - MUST set pendingCancelRedeemRequest to `false` for the returned requestId after request
* - MUST set claimableCancelRedeemRequest to 0 for the returned requestId after fulfillment
*/
function claimCancelRedeemRequest(
uint256 requestId,
address receiver,
address controller
)
external
returns (uint256 shares);
}
/**
* @title IERC7540
* @dev Fully async ERC7540 implementation according to the standard
* @dev Adapted from Centrifuge's IERC7540 implementation
*/
interface IERC7540 is IERC7540Deposit, IERC7540Redeem { }
/**
* @title IERC7540Vault
* @dev This is the specific set of interfaces used by the SuperVaults
*/
interface IERC7540Vault is IERC7540, IERC7741 {
event DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares);
event RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares);
}
IERC7741.sol 36 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
interface IERC7741 {
/**
* @dev Grants or revokes permissions for `operator` to manage Requests on behalf of the
* `msg.sender`, using an [EIP-712](./eip-712.md) signature.
*/
function authorizeOperator(
address controller,
address operator,
bool approved,
bytes32 nonce,
uint256 deadline,
bytes memory signature
)
external
returns (bool);
/**
* @dev Revokes the given `nonce` for `msg.sender` as the `owner`.
*/
function invalidateNonce(bytes32 nonce) external;
/**
* @dev Returns whether the given `nonce` has been used for the `controller`.
*/
function authorizations(address controller, bytes32 nonce) external view returns (bool used);
/**
* @dev Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPARATOR
* should be unique to the contract and chain to prevent replay attacks from other domains,
* and satisfy the requirements of EIP-712, but is otherwise unconstrained.
*/
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
IERC7575.sol 243 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
interface IERC7575 is IERC165 {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the address of the share token
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function share() external view returns (address shareTokenAddress);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
ISuperVaultEscrow.sol 64 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
/// @title ISuperVaultEscrow
/// @notice Interface for SuperVault escrow contract that holds shares during request/claim process
/// @author Superform Labs
interface ISuperVaultEscrow {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error ALREADY_INITIALIZED();
error UNAUTHORIZED();
error ZERO_ADDRESS();
error ZERO_AMOUNT();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when escrow is initialized
/// @param vault The vault contract address
event Initialized(address indexed vault);
/// @notice Emitted when shares are transferred to escrow
/// @param from The address shares were transferred from
/// @param amount The amount of shares escrowed
event SharesEscrowed(address indexed from, uint256 amount);
/// @notice Emitted when shares are returned from escrow
/// @param to The address shares were returned to
/// @param amount The amount of shares returned
event SharesReturned(address indexed to, uint256 amount);
/// @notice Emitted when assets are returned from escrow
/// @param to The address assets were returned to
/// @param amount The amount of assets returned
event AssetsReturned(address indexed to, uint256 amount);
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
/// @notice Initialize the escrow with required parameters
/// @param vaultAddress The vault contract address
function initialize(address vaultAddress) external;
/*//////////////////////////////////////////////////////////////
VAULT FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Transfer shares from user to escrow during redeem request
/// @param from The address to transfer shares from
/// @param amount The amount of shares to transfer
function escrowShares(address from, uint256 amount) external;
/// @notice Return shares from escrow to user during redeem cancellation
/// @param to The address to return shares to
/// @param amount The amount of shares to return
function returnShares(address to, uint256 amount) external;
/// @notice Return assets from escrow to vault during deposit cancellation
/// @param to The address to return assets to
/// @param amount The amount of assets to return
function returnAssets(address to, uint256 amount) external;
}
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
LibSort.sol 942 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Optimized sorts and operations for sorted arrays.
/// @author Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibSort.sol)
library LibSort {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INSERTION SORT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// - Faster on small arrays (32 or lesser elements).
// - Faster on almost sorted arrays.
// - Smaller bytecode (about 300 bytes smaller than sort, which uses intro-quicksort).
// - May be suitable for view functions intended for off-chain querying.
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a) // Length of `a`.
mstore(a, 0) // For insertion sort's inner loop to terminate.
let h := add(a, shl(5, n)) // High slot.
let w := not(0x1f)
for { let i := add(a, 0x20) } 1 {} {
i := add(i, 0x20)
if gt(i, h) { break }
let k := mload(i) // Key.
let j := add(i, w) // The slot before the current slot.
let v := mload(j) // The value of `j`.
if iszero(gt(v, k)) { continue }
for {} 1 {} {
mstore(add(j, 0x20), v)
j := add(j, w) // `sub(j, 0x20)`.
v := mload(j)
if iszero(gt(v, k)) { break }
}
mstore(add(j, 0x20), k)
}
mstore(a, n) // Restore the length of `a`.
}
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(int256[] memory a) internal pure {
_flipSign(a);
insertionSort(_toUints(a));
_flipSign(a);
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(address[] memory a) internal pure {
insertionSort(_toUints(a));
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(bytes32[] memory a) internal pure {
insertionSort(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTRO-QUICKSORT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// - Faster on larger arrays (more than 32 elements).
// - Robust performance.
// - Larger bytecode.
/// @dev Sorts the array in-place with intro-quicksort.
function sort(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
function swap(a_, b_) -> _a, _b {
_b := a_
_a := b_
}
function mswap(i_, j_) {
let t_ := mload(i_)
mstore(i_, mload(j_))
mstore(j_, t_)
}
function sortInner(w_, l_, h_) {
// Do insertion sort if `h_ - l_ <= 0x20 * 12`.
// Threshold is fine-tuned via trial and error.
if iszero(gt(sub(h_, l_), 0x180)) {
// Hardcode sort the first 2 elements.
let i_ := add(l_, 0x20)
if iszero(lt(mload(l_), mload(i_))) { mswap(i_, l_) }
for {} 1 {} {
i_ := add(i_, 0x20)
if gt(i_, h_) { break }
let k_ := mload(i_) // Key.
let j_ := add(i_, w_) // The slot before the current slot.
let v_ := mload(j_) // The value of `j_`.
if iszero(gt(v_, k_)) { continue }
for {} 1 {} {
mstore(add(j_, 0x20), v_)
j_ := add(j_, w_)
v_ := mload(j_)
if iszero(gt(v_, k_)) { break }
}
mstore(add(j_, 0x20), k_)
}
leave
}
// Pivot slot is the average of `l_` and `h_`.
let p_ := add(shl(5, shr(6, add(l_, h_))), and(31, l_))
// Median of 3 with sorting.
{
let e0_ := mload(l_)
let e1_ := mload(p_)
if iszero(lt(e0_, e1_)) { e0_, e1_ := swap(e0_, e1_) }
let e2_ := mload(h_)
if iszero(lt(e1_, e2_)) {
e1_, e2_ := swap(e1_, e2_)
if iszero(lt(e0_, e1_)) { e0_, e1_ := swap(e0_, e1_) }
}
mstore(h_, e2_)
mstore(p_, e1_)
mstore(l_, e0_)
}
// Hoare's partition.
{
// The value of the pivot slot.
let x_ := mload(p_)
p_ := h_
for { let i_ := l_ } 1 {} {
for {} 1 {} {
i_ := add(0x20, i_)
if iszero(gt(x_, mload(i_))) { break }
}
let j_ := p_
for {} 1 {} {
j_ := add(w_, j_)
if iszero(lt(x_, mload(j_))) { break }
}
p_ := j_
if iszero(lt(i_, p_)) { break }
mswap(i_, p_)
}
}
if iszero(eq(add(p_, 0x20), h_)) { sortInner(w_, add(p_, 0x20), h_) }
if iszero(eq(p_, l_)) { sortInner(w_, l_, p_) }
}
for { let n := mload(a) } iszero(lt(n, 2)) {} {
let w := not(0x1f) // `-0x20`.
let l := add(a, 0x20) // Low slot.
let h := add(a, shl(5, n)) // High slot.
let j := h
// While `mload(j - 0x20) <= mload(j): j -= 0x20`.
for {} iszero(gt(mload(add(w, j)), mload(j))) {} { j := add(w, j) }
// If the array is already sorted, break.
if iszero(gt(j, l)) { break }
// While `mload(j - 0x20) >= mload(j): j -= 0x20`.
for { j := h } iszero(lt(mload(add(w, j)), mload(j))) {} { j := add(w, j) }
// If the array is reversed sorted.
if iszero(gt(j, l)) {
for {} 1 {} {
let t := mload(l)
mstore(l, mload(h))
mstore(h, t)
h := add(w, h)
l := add(l, 0x20)
if iszero(lt(l, h)) { break }
}
break
}
mstore(a, 0) // For insertion sort's inner loop to terminate.
sortInner(w, l, h)
mstore(a, n) // Restore the length of `a`.
break
}
}
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(int256[] memory a) internal pure {
_flipSign(a);
sort(_toUints(a));
_flipSign(a);
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(address[] memory a) internal pure {
sort(_toUints(a));
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(bytes32[] memory a) internal pure {
sort(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OTHER USEFUL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, the `uniquifySorted` methods will not revert if the
// array is not sorted -- it will simply remove consecutive duplicate elements.
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
// If the length of `a` is greater than 1.
if iszero(lt(mload(a), 2)) {
let x := add(a, 0x20)
let y := add(a, 0x40)
let end := add(a, shl(5, add(mload(a), 1)))
for {} 1 {} {
if iszero(eq(mload(x), mload(y))) {
x := add(x, 0x20)
mstore(x, mload(y))
}
y := add(y, 0x20)
if eq(y, end) { break }
}
mstore(a, shr(5, sub(x, a)))
}
}
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(int256[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(address[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(bytes32[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(uint256[] memory a, uint256 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(a, needle, 0);
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(int256[] memory a, int256 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint256(needle), 1 << 255);
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(address[] memory a, address needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint160(needle), 0);
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function searchSorted(bytes32[] memory a, bytes32 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint256(needle), 0);
}
/// @dev Returns whether `a` contains `needle`.
function inSorted(uint256[] memory a, uint256 needle) internal pure returns (bool found) {
(found,) = searchSorted(a, needle);
}
/// @dev Returns whether `a` contains `needle`.
function inSorted(int256[] memory a, int256 needle) internal pure returns (bool found) {
(found,) = searchSorted(a, needle);
}
/// @dev Returns whether `a` contains `needle`.
function inSorted(address[] memory a, address needle) internal pure returns (bool found) {
(found,) = searchSorted(a, needle);
}
/// @dev Returns whether `a` contains `needle`.
function inSorted(bytes32[] memory a, bytes32 needle) internal pure returns (bool found) {
(found,) = searchSorted(a, needle);
}
/// @dev Reverses the array in-place.
function reverse(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(mload(a), 2)) {
let s := 0x20
let w := not(0x1f)
let h := add(a, shl(5, mload(a)))
for { a := add(a, s) } 1 {} {
let t := mload(a)
mstore(a, mload(h))
mstore(h, t)
h := add(h, w)
a := add(a, s)
if iszero(lt(a, h)) { break }
}
}
}
}
/// @dev Reverses the array in-place.
function reverse(int256[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Reverses the array in-place.
function reverse(address[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Reverses the array in-place.
function reverse(bytes32[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Returns a copy of the array.
function copy(uint256[] memory a) internal pure returns (uint256[] memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
let end := add(add(result, 0x20), shl(5, mload(a)))
let o := result
for { let d := sub(a, result) } 1 {} {
mstore(o, mload(add(o, d)))
o := add(0x20, o)
if eq(o, end) { break }
}
mstore(0x40, o)
}
}
/// @dev Returns a copy of the array.
function copy(int256[] memory a) internal pure returns (int256[] memory result) {
result = _toInts(copy(_toUints(a)));
}
/// @dev Returns a copy of the array.
function copy(address[] memory a) internal pure returns (address[] memory result) {
result = _toAddresses(copy(_toUints(a)));
}
/// @dev Returns a copy of the array.
function copy(bytes32[] memory a) internal pure returns (bytes32[] memory result) {
result = _toBytes32s(copy(_toUints(a)));
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := iszero(gt(p, mload(a)))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(int256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := iszero(sgt(p, mload(a)))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(address[] memory a) internal pure returns (bool result) {
result = isSorted(_toUints(a));
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(bytes32[] memory a) internal pure returns (bool result) {
result = isSorted(_toUints(a));
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := lt(p, mload(a))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(int256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := slt(p, mload(a))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(address[] memory a) internal pure returns (bool result) {
result = isSortedAndUniquified(_toUints(a));
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(bytes32[] memory a) internal pure returns (bool result) {
result = isSortedAndUniquified(_toUints(a));
}
/// @dev Returns the sorted set difference of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _difference(a, b, 0);
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_difference(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_difference(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(bytes32[] memory a, bytes32[] memory b)
internal
pure
returns (bytes32[] memory c)
{
c = _toBytes32s(_difference(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _intersection(a, b, 0);
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_intersection(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_intersection(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(bytes32[] memory a, bytes32[] memory b)
internal
pure
returns (bytes32[] memory c)
{
c = _toBytes32s(_intersection(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _union(a, b, 0);
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_union(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set union between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_union(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set union between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(bytes32[] memory a, bytes32[] memory b)
internal
pure
returns (bytes32[] memory c)
{
c = _toBytes32s(_union(_toUints(a), _toUints(b), 0));
}
/// @dev Cleans the upper 96 bits of the addresses.
/// In case `a` is produced via assembly and might have dirty upper bits.
function clean(address[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let addressMask := shr(96, not(0))
for { let end := add(a, shl(5, mload(a))) } iszero(eq(a, end)) {} {
a := add(a, 0x20)
mstore(a, and(mload(a), addressMask))
}
}
}
/// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key.
function groupSum(uint256[] memory keys, uint256[] memory values) internal pure {
/// @solidity memory-safe-assembly
assembly {
function mswap(i_, j_) {
let t_ := mload(i_)
mstore(i_, mload(j_))
mstore(j_, t_)
}
function sortInner(l_, h_, d_) {
let p_ := mload(l_)
let j_ := l_
for { let i_ := add(l_, 0x20) } 1 {} {
if lt(mload(i_), p_) {
j_ := add(j_, 0x20)
mswap(i_, j_)
mswap(add(i_, d_), add(j_, d_))
}
i_ := add(0x20, i_)
if iszero(lt(i_, h_)) { break }
}
mswap(l_, j_)
mswap(add(l_, d_), add(j_, d_))
if iszero(gt(add(0x40, l_), j_)) { sortInner(l_, j_, d_) }
if iszero(gt(add(0x60, j_), h_)) { sortInner(add(j_, 0x20), h_, d_) }
}
let n := mload(values)
if iszero(eq(mload(keys), n)) {
mstore(0x00, 0x4e487b71)
mstore(0x20, 0x32) // Array out of bounds panic if the arrays lengths differ.
revert(0x1c, 0x24)
}
if iszero(lt(n, 2)) {
let d := sub(values, keys)
let x := add(keys, 0x20)
let end := add(x, shl(5, n))
sortInner(x, end, d)
let s := mload(add(x, d))
for { let y := add(keys, 0x40) } 1 {} {
if iszero(eq(mload(x), mload(y))) {
mstore(add(x, d), s) // Write sum.
s := 0
x := add(x, 0x20)
mstore(x, mload(y))
}
s := add(s, mload(add(y, d)))
if lt(s, mload(add(y, d))) {
mstore(0x00, 0x4e487b71)
mstore(0x20, 0x11) // Overflow panic if the addition overflows.
revert(0x1c, 0x24)
}
y := add(y, 0x20)
if eq(y, end) { break }
}
mstore(add(x, d), s) // Write sum.
mstore(keys, shr(5, sub(x, keys))) // Truncate.
mstore(values, mload(keys)) // Truncate.
}
}
}
/// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key.
function groupSum(address[] memory keys, uint256[] memory values) internal pure {
groupSum(_toUints(keys), values);
}
/// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key.
function groupSum(bytes32[] memory keys, uint256[] memory values) internal pure {
groupSum(_toUints(keys), values);
}
/// @dev Sorts and uniquifies `keys`. Updates `values` with the grouped sums by key.
function groupSum(int256[] memory keys, uint256[] memory values) internal pure {
groupSum(_toUints(keys), values);
}
/// @dev Returns if `a` has any duplicate. Does NOT mutate `a`. `O(n)`.
function hasDuplicate(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
function p(i_, x_) -> _y {
_y := or(shr(i_, x_), x_)
}
let n := mload(a)
if iszero(lt(n, 2)) {
let m := mload(0x40) // Use free memory temporarily for hashmap.
let w := not(0x1f) // `-0x20`.
let c := and(w, p(16, p(8, p(4, p(2, p(1, mul(0x30, n)))))))
calldatacopy(m, calldatasize(), add(0x20, c)) // Zeroize hashmap.
for { let i := add(a, shl(5, n)) } 1 {} {
// See LibPRNG for explanation of this formula.
let r := mulmod(mload(i), 0x100000000000000000000000000000051, not(0xbc))
// Linear probing.
for {} 1 { r := add(0x20, r) } {
let o := add(m, and(r, c)) // Non-zero pointer into hashmap.
if iszero(mload(o)) {
mstore(o, i) // Store non-zero pointer into hashmap.
break
}
if eq(mload(mload(o)), mload(i)) {
result := 1
i := a // To break the outer loop.
break
}
}
i := add(i, w) // Iterate `a` backwards.
if iszero(lt(a, i)) { break }
}
if shr(31, n) { invalid() } // Just in case.
}
}
}
/// @dev Returns if `a` has any duplicate. Does NOT mutate `a`. `O(n)`.
function hasDuplicate(address[] memory a) internal pure returns (bool) {
return hasDuplicate(_toUints(a));
}
/// @dev Returns if `a` has any duplicate. Does NOT mutate `a`. `O(n)`.
function hasDuplicate(bytes32[] memory a) internal pure returns (bool) {
return hasDuplicate(_toUints(a));
}
/// @dev Returns if `a` has any duplicate. Does NOT mutate `a`. `O(n)`.
function hasDuplicate(int256[] memory a) internal pure returns (bool) {
return hasDuplicate(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Reinterpret cast to an uint256 array.
function _toUints(int256[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an uint256 array.
function _toUints(address[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
// As any address written to memory will have the upper 96 bits
// of the word zeroized (as per Solidity spec), we can directly
// compare these addresses as if they are whole uint256 words.
casted := a
}
}
/// @dev Reinterpret cast to an uint256 array.
function _toUints(bytes32[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an int array.
function _toInts(uint256[] memory a) private pure returns (int256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an address array.
function _toAddresses(uint256[] memory a) private pure returns (address[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an bytes32 array.
function _toBytes32s(uint256[] memory a) private pure returns (bytes32[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Converts an array of signed integers to unsigned
/// integers suitable for sorting or vice versa.
function _flipSign(int256[] memory a) private pure {
/// @solidity memory-safe-assembly
assembly {
let q := shl(255, 1)
for { let i := add(a, shl(5, mload(a))) } iszero(eq(a, i)) {} {
mstore(i, add(mload(i), q))
i := sub(i, 0x20)
}
}
}
/// @dev Returns whether `a` contains `needle`, and the index of `needle`.
/// `index` precedence: equal to > nearest before > nearest after.
function _searchSorted(uint256[] memory a, uint256 needle, uint256 signed)
private
pure
returns (bool found, uint256 index)
{
/// @solidity memory-safe-assembly
assembly {
let w := not(0)
let l := 1
let h := mload(a)
let t := 0
for { needle := add(signed, needle) } 1 {} {
index := shr(1, add(l, h))
t := add(signed, mload(add(a, shl(5, index))))
if or(gt(l, h), eq(t, needle)) { break }
// Decide whether to search the left or right half.
if iszero(gt(needle, t)) {
h := add(index, w)
continue
}
l := add(index, 1)
}
// `index` will be zero in the case of an empty array,
// or when the value is less than the smallest value in the array.
found := eq(t, needle)
t := iszero(iszero(index))
index := mul(add(index, w), t)
found := and(found, t)
}
}
/// @dev Returns the sorted set difference of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _difference(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
b := add(b, s)
continue
}
k := add(k, s)
mstore(k, u)
a := add(a, s)
}
for {} iszero(gt(a, aEnd)) {} {
k := add(k, s)
mstore(k, mload(a))
a := add(a, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _intersection(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
k := add(k, s)
mstore(k, u)
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
b := add(b, s)
continue
}
a := add(a, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _union(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
k := add(k, s)
mstore(k, u)
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
k := add(k, s)
mstore(k, v)
b := add(b, s)
continue
}
k := add(k, s)
mstore(k, u)
a := add(a, s)
}
for {} iszero(gt(a, aEnd)) {} {
k := add(k, s)
mstore(k, mload(a))
a := add(a, s)
}
for {} iszero(gt(b, bEnd)) {} {
k := add(k, s)
mstore(k, mload(b))
b := add(b, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
}
ISuperHook.sol 264 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
// external
import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
/**
* @title SuperHook System
* @author Superform Labs
* @notice The hook system provides a modular and composable way to execute operations on assets
* @dev The hook system architecture consists of several interfaces that work together:
* - ISuperHook: The base interface all hooks implement, with lifecycle methods
* - ISuperHookResult: Provides execution results and output information
* - Specialized interfaces (ISuperHookOutflow, ISuperHookLoans, etc.) for specific behaviors
*
* Hooks are executed in sequence, where each hook can access the results from previous hooks.
* The three main types of hooks are:
* - NONACCOUNTING: Utility hooks that don't update the accounting system
* - INFLOW: Hooks that process deposits or additions to positions
* - OUTFLOW: Hooks that process withdrawals or reductions to positions
*/
interface ISuperLockableHook {
/// @notice The vault bank address used to lock SuperPositions
/// @dev Only relevant for cross-chain operations where positions are locked
/// @return The vault bank address, or address(0) if not applicable
function vaultBank() external view returns (address);
/// @notice The destination chain ID for cross-chain operations
/// @dev Used to identify the target chain for cross-chain position transfers
/// @return The destination chain ID, or 0 if not a cross-chain operation
function dstChainId() external view returns (uint256);
}
interface ISuperHookSetter {
/// @notice Sets the output amount for the hook
/// @dev Used for updating `outAmount` when fees were deducted
/// @param outAmount The amount of tokens processed by the hook
/// @param caller The caller address for context identification
function setOutAmount(uint256 outAmount, address caller) external;
}
/// @title ISuperHookInspector
/// @author Superform Labs
/// @notice Interface for the SuperHookInspector contract that manages hook inspection
interface ISuperHookInspector {
/// @notice Inspect the hook
/// @param data The hook data to inspect
/// @return argsEncoded The arguments of the hook encoded
function inspect(bytes calldata data) external view returns (bytes memory argsEncoded);
}
/// @title ISuperHookResult
/// @author Superform Labs
/// @notice Interface that exposes the result of a hook execution
/// @dev All hooks must implement this interface to provide standardized access to execution results.
/// These results are used by subsequent hooks in the execution chain and by the executor.
interface ISuperHookResult {
/*//////////////////////////////////////////////////////////////
VIEW METHODS
//////////////////////////////////////////////////////////////*/
/// @notice The type of hook
/// @dev Used to determine how accounting should process this hook's results
/// @return The hook type (NONACCOUNTING, INFLOW, or OUTFLOW)
function hookType() external view returns (ISuperHook.HookType);
/// @notice The SuperPosition (SP) token associated with this hook
/// @dev For vault hooks, this would be the tokenized position representing shares
/// @return The address of the SP token, or address(0) if not applicable
function spToken() external view returns (address);
/// @notice The underlying asset token being processed
/// @dev For most hooks, this is the actual token being deposited or withdrawn
/// @return The address of the asset token, or address(0) for native assets
function asset() external view returns (address);
/// @notice The amount of tokens processed by the hook in a given caller context, subject to fees after update
/// @dev This is the primary output value used by subsequent hooks
/// @param caller The caller address for context identification
/// @return The amount of tokens (assets or shares) processed
function getOutAmount(address caller) external view returns (uint256);
}
/// @title ISuperHookContextAware
/// @author Superform Labs
/// @notice Interface for hooks that can use previous hook results in their execution
/// @dev Enables contextual awareness and data flow between hooks in a chain
interface ISuperHookContextAware {
/// @notice Determines if this hook should use the amount from the previous hook
/// @dev Used to create hook chains where output from one hook becomes input to the next
/// @param data The hook-specific data containing configuration
/// @return True if the hook should use the previous hook's output amount
function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool);
}
/// @title ISuperHookInflowOutflow
/// @author Superform Labs
/// @notice Interface for hooks that handle both inflows and outflows
/// @dev Provides standardized amount extraction for both deposit and withdrawal operations
interface ISuperHookInflowOutflow {
/// @notice Extracts the amount from the hook's calldata
/// @dev Used to determine the quantity of assets or shares being processed
/// @param data The hook-specific calldata containing the amount
/// @return The amount of tokens to process
function decodeAmount(bytes memory data) external pure returns (uint256);
}
/// @title ISuperHookOutflow
/// @author Superform Labs
/// @notice Interface for hooks that specifically handle outflows (withdrawals)
/// @dev Provides additional functionality needed only for outflow operations
interface ISuperHookOutflow {
/// @notice Replace the amount in the calldata
/// @param data The data to replace the amount in
/// @param amount The amount to replace
/// @return data The data with the replaced amount
function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory);
}
/// @title ISuperHookResultOutflow
/// @author Superform Labs
/// @notice Extended result interface for outflow hook operations
/// @dev Extends the base result interface with outflow-specific information
interface ISuperHookResultOutflow is ISuperHookResult {
/// @notice The amount of shares consumed during outflow processing
/// @dev Used for cost basis calculation in the accounting system
/// @return The amount of shares consumed from the user's position
function usedShares() external view returns (uint256);
}
/// @title ISuperHookLoans
/// @author Superform Labs
/// @notice Interface for hooks that interact with lending protocols
/// @dev Extends context awareness to enable loan operations within hook chains
interface ISuperHookLoans is ISuperHookContextAware {
/// @notice Gets the address of the token being borrowed
/// @dev Used to identify which asset is being borrowed from the lending protocol
/// @param data The hook-specific data containing loan information
/// @return The address of the borrowed token
function getLoanTokenAddress(bytes memory data) external pure returns (address);
/// @notice Gets the address of the token used as collateral
/// @dev Used to identify which asset is being used to secure the loan
/// @param data The hook-specific data containing collateral information
/// @return The address of the collateral token
function getCollateralTokenAddress(bytes memory data) external view returns (address);
/// @notice Gets the current loan token balance for an account
/// @dev Used to track outstanding loan amounts
/// @param account The account to check the loan balance for
/// @param data The hook-specific data containing loan parameters
/// @return The amount of tokens currently borrowed
function getLoanTokenBalance(address account, bytes memory data) external view returns (uint256);
/// @notice Gets the current collateral token balance for an account
/// @dev Used to track collateral positions
/// @param account The account to check the collateral balance for
/// @param data The hook-specific data containing collateral parameters
/// @return The amount of tokens currently used as collateral
function getCollateralTokenBalance(address account, bytes memory data) external view returns (uint256);
}
/// @title ISuperHookAsyncCancelations
/// @author Superform Labs
/// @notice Interface for hooks that can cancel asynchronous operations
/// @dev Used to handle cancellation of pending operations that haven't completed
interface ISuperHookAsyncCancelations {
/// @notice Types of cancellations that can be performed
/// @dev Distinguishes between different operation types that can be canceled
enum CancelationType {
NONE, // Not a cancelation hook
INFLOW, // Cancels a pending deposit operation
OUTFLOW // Cancels a pending withdrawal operation
}
/// @notice Identifies the type of async operation this hook can cancel
/// @dev Used to verify the hook is appropriate for the operation being canceled
/// @return asyncType The type of cancellation this hook performs
function isAsyncCancelHook() external pure returns (CancelationType asyncType);
}
/// @title ISuperHook
/// @author Superform Labs
/// @notice The core hook interface that all hooks must implement
/// @dev Defines the lifecycle methods and execution flow for the hook system
/// Hooks are executed in sequence with results passed between them
interface ISuperHook {
/*//////////////////////////////////////////////////////////////
ENUMS
//////////////////////////////////////////////////////////////*/
/// @notice Defines the possible types of hooks in the system
/// @dev Used to determine how the hook affects accounting and what operations it performs
enum HookType {
NONACCOUNTING, // Hook doesn't affect accounting (e.g., a swap or bridge)
INFLOW, // Hook processes deposits or positions being added
OUTFLOW // Hook processes withdrawals or positions being removed
}
/*//////////////////////////////////////////////////////////////
VIEW METHODS
//////////////////////////////////////////////////////////////*/
/// @notice Builds the execution array for the hook operation
/// @dev This is the core method where hooks define their on-chain interactions
/// The returned executions are a sequence of contract calls to perform
/// No state changes should occur in this method
/// @param prevHook The address of the previous hook in the chain, or address(0) if first
/// @param account The account to perform executions for (usually an ERC7579 account)
/// @param data The hook-specific parameters and configuration data
/// @return executions Array of Execution structs defining calls to make
function build(
address prevHook,
address account,
bytes calldata data
)
external
view
returns (Execution[] memory executions);
/*//////////////////////////////////////////////////////////////
PUBLIC METHODS
//////////////////////////////////////////////////////////////*/
/// @notice Prepares the hook for execution
/// @dev Called before the main execution, used to validate inputs and set execution context
/// This method may perform state changes to set up the hook's execution state
/// @param prevHook The address of the previous hook in the chain, or address(0) if first
/// @param account The account to perform operations for
/// @param data The hook-specific parameters and configuration data
function preExecute(address prevHook, address account, bytes memory data) external;
/// @notice Finalizes the hook after execution
/// @dev Called after the main execution, used to update hook state and calculate results
/// Sets output values (outAmount, usedShares, etc.) for subsequent hooks
/// @param prevHook The address of the previous hook in the chain, or address(0) if first
/// @param account The account operations were performed for
/// @param data The hook-specific parameters and configuration data
function postExecute(address prevHook, address account, bytes memory data) external;
/// @notice Returns the specific subtype identification for this hook
/// @dev Used to categorize hooks beyond the basic HookType
/// For example, a hook might be of type INFLOW but subtype VAULT_DEPOSIT
/// @return A bytes32 identifier for the specific hook functionality
function subtype() external view returns (bytes32);
/// @notice Resets hook mutexes
/// @param caller The caller address for context identification
function resetExecutionState(address caller) external;
/// @notice Sets the caller address that initiated the execution
/// @dev Used for security validation between preExecute and postExecute calls
/// @param caller The caller address for context identification
function setExecutionContext(address caller) external;
/// @notice Returns the execution nonce for the current execution context
/// @dev Used to ensure unique execution contexts and prevent replay attacks
/// @return The execution nonce
function executionNonce() external view returns (uint256);
/// @notice Returns the last caller registered by `setExecutionContext`
/// @return The last caller address
function lastCaller() external view returns (address);
}
HookDataDecoder.sol 17 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { BytesLib } from "../vendor/BytesLib.sol";
/// @title HookDataDecoder
/// @author Superform Labs
/// @notice Library for decoding hook data
library HookDataDecoder {
function extractYieldSourceOracleId(bytes memory data) internal pure returns (bytes32) {
return bytes32(BytesLib.slice(data, 0, 32));
}
function extractYieldSource(bytes memory data) internal pure returns (address) {
return BytesLib.toAddress(data, 32);
}
}
SuperVaultAccountingLib.sol 82 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.30;
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
/// @title SuperVaultAccountingLib
/// @author Superform Labs
/// @notice Stateless library for SuperVault accounting calculations
/// @dev All functions are pure for easy auditing and testing
library SuperVaultAccountingLib {
using Math for uint256;
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error INSUFFICIENT_LIQUIDITY();
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
uint256 private constant BPS_PRECISION = 10_000;
/*//////////////////////////////////////////////////////////////
ACCOUNTING FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Compute minimum acceptable assets (slippage floor)
/// @param requestedShares Number of shares being redeemed
/// @param averageRequestPPS PPS at time of request (slippage anchor)
/// @param slippageBps User's slippage tolerance in basis points
/// @param precision Precision constant for PPS calculations
/// @return minAssetsOut User's minimum acceptable assets
function computeMinNetOut(
uint256 requestedShares,
uint256 averageRequestPPS,
uint16 slippageBps,
uint256 precision
)
internal
pure
returns (uint256 minAssetsOut)
{
uint256 expectedAssets = requestedShares.mulDiv(averageRequestPPS, precision, Math.Rounding.Floor);
minAssetsOut = expectedAssets.mulDiv(BPS_PRECISION - slippageBps, BPS_PRECISION, Math.Rounding.Floor);
}
/// @notice Calculate updated average withdraw price
/// @param currentMaxWithdraw Current max withdrawable assets
/// @param currentAverageWithdrawPrice Current average withdraw price
/// @param requestedShares New shares being fulfilled
/// @param fulfilledAssets Assets received from fulfilling the redeem request
/// @param precision Precision constant
/// @return newAverageWithdrawPrice Updated average withdraw price
function calculateAverageWithdrawPrice(
uint256 currentMaxWithdraw,
uint256 currentAverageWithdrawPrice,
uint256 requestedShares,
uint256 fulfilledAssets,
uint256 precision
)
internal
pure
returns (uint256 newAverageWithdrawPrice)
{
uint256 existingShares;
uint256 existingAssets;
if (currentMaxWithdraw > 0 && currentAverageWithdrawPrice > 0) {
existingShares = currentMaxWithdraw.mulDiv(precision, currentAverageWithdrawPrice, Math.Rounding.Floor);
existingAssets = currentMaxWithdraw;
}
uint256 newTotalShares = existingShares + requestedShares;
uint256 newTotalAssets = existingAssets + fulfilledAssets;
if (newTotalShares > 0) {
newAverageWithdrawPrice = newTotalAssets.mulDiv(precision, newTotalShares, Math.Rounding.Floor);
}
return newAverageWithdrawPrice;
}
}
IAccessControl.sol 98 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/IAccessControl.sol)
pragma solidity >=0.8.4;
/**
* @dev External interface of AccessControl declared to support ERC-165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted to signal this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @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.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @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 granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";
Comparators.sol 19 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides a set of functions to compare values.
*
* _Available since v5.1._
*/
library Comparators {
function lt(uint256 a, uint256 b) internal pure returns (bool) {
return a < b;
}
function gt(uint256 a, uint256 b) internal pure returns (bool) {
return a > b;
}
}
SlotDerivation.sol 155 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/SlotDerivation.sol)
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.
pragma solidity ^0.8.20;
/**
* @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
* corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
* the solidity language / compiler.
*
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
*
* Example usage:
* ```solidity
* contract Example {
* // Add the library methods
* using StorageSlot for bytes32;
* using SlotDerivation for bytes32;
*
* // Declare a namespace
* string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot
*
* function setValueInNamespace(uint256 key, address newValue) internal {
* _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
* }
*
* function getValueInNamespace(uint256 key) internal view returns (address) {
* return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
* }
* }
* ```
*
* TIP: Consider using this library along with {StorageSlot}.
*
* NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
* upgrade safety will ignore the slots accessed through this library.
*
* _Available since v5.1._
*/
library SlotDerivation {
/**
* @dev Derive an ERC-7201 slot from a string (namespace).
*/
function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
assembly ("memory-safe") {
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
slot := and(keccak256(0x00, 0x20), not(0xff))
}
}
/**
* @dev Add an offset to a slot to get the n-th element of a structure or an array.
*/
function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
unchecked {
return bytes32(uint256(slot) + pos);
}
}
/**
* @dev Derive the location of the first element in an array from the slot where the length is stored.
*/
function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, slot)
result := keccak256(0x00, 0x20)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, and(key, shr(96, not(0))))
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, iszero(iszero(key)))
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
assembly ("memory-safe") {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
}
StorageSlot.sol 143 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}
ContextUpgradeable.sol 34 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
draft-IERC6093.sol 161 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)
pragma solidity >=0.8.4;
/**
* @dev Standard ERC-20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC-721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC-1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
MessageHashUtils.sol 99 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
*/
function toDataWithIntendedValidatorHash(
address validator,
bytes32 messageHash
) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, hex"19_00")
mstore(0x02, shl(96, validator))
mstore(0x16, messageHash)
digest := keccak256(0x00, 0x36)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}
IERC5267.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)
pragma solidity >=0.4.16;
interface IERC5267 {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}
ExecutionLib.sol 86 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23 <0.9.0;
// Types
import { Execution } from "../../common/interfaces/IERC7579Account.sol";
/**
* Helper Library for decoding Execution calldata
* malloc for memory allocation is bad for gas. use this assembly instead
*/
library ExecutionLib {
error ERC7579DecodingError();
/**
* @notice Decode a batch of `Execution` executionBatch from a `bytes` calldata.
* @dev code is copied from solady's LibERC7579.sol
* https://github.com/Vectorized/solady/blob/740812cedc9a1fc11e17cb3d4569744367dedf19/src/accounts/LibERC7579.sol#L146
* Credits to Vectorized and the Solady Team
*/
function decodeBatch(bytes calldata executionCalldata)
internal
pure
returns (Execution[] calldata executionBatch)
{
/// @solidity memory-safe-assembly
assembly {
let u := calldataload(executionCalldata.offset)
let s := add(executionCalldata.offset, u)
let e := sub(add(executionCalldata.offset, executionCalldata.length), 0x20)
executionBatch.offset := add(s, 0x20)
executionBatch.length := calldataload(s)
if or(shr(64, u), gt(add(s, shl(5, executionBatch.length)), e)) {
mstore(0x00, 0xba597e7e) // `DecodingError()`.
revert(0x1c, 0x04)
}
if executionBatch.length {
// Perform bounds checks on the decoded `executionBatch`.
// Loop runs out-of-gas if `executionBatch.length` is big enough to cause overflows.
for { let i := executionBatch.length } 1 { } {
i := sub(i, 1)
let p := calldataload(add(executionBatch.offset, shl(5, i)))
let c := add(executionBatch.offset, p)
let q := calldataload(add(c, 0x40))
let o := add(c, q)
// forgefmt: disable-next-item
if or(shr(64, or(calldataload(o), or(p, q))),
or(gt(add(c, 0x40), e), gt(add(o, calldataload(o)), e))) {
mstore(0x00, 0xba597e7e) // `DecodingError()`.
revert(0x1c, 0x04)
}
if iszero(i) { break }
}
}
}
}
function encodeBatch(Execution[] memory executions)
internal
pure
returns (bytes memory callData)
{
callData = abi.encode(executions);
}
function decodeSingle(bytes calldata executionCalldata)
internal
pure
returns (address target, uint256 value, bytes calldata callData)
{
target = address(bytes20(executionCalldata[0:20]));
value = uint256(bytes32(executionCalldata[20:52]));
callData = executionCalldata[52:];
}
function encodeSingle(
address target,
uint256 value,
bytes memory callData
)
internal
pure
returns (bytes memory userOpCalldata)
{
userOpCalldata = abi.encodePacked(target, value, callData);
}
}
BytesLib.sol 480 lines
// SPDX-License-Identifier: Unlicense /* * @title Solidity Bytes Arrays Utils * @author Gonçalo Sá <[email protected]> * * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. */ pragma solidity 0.8.30; library BytesLib { function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // Store the length of the first bytes array at the beginning of // the memory for tempBytes. let length := mload(_preBytes) mstore(tempBytes, length) // Maintain a memory counter for the current write location in the // temp bytes array by adding the 32 bytes for the array length to // the starting location. let mc := add(tempBytes, 0x20) // Stop copying when the memory counter reaches the length of the // first bytes array. let end := add(mc, length) for { // Initialize a copy counter to the start of the _preBytes data, // 32 bytes into its memory. let cc := add(_preBytes, 0x20) } lt(mc, end) { // Increase both counters by 32 bytes each iteration. mc := add(mc, 0x20) cc := add(cc, 0x20) } { // Write the _preBytes data into the tempBytes memory 32 bytes // at a time. mstore(mc, mload(cc)) } // Add the length of _postBytes to the current length of tempBytes // and store it as the new length in the first 32 bytes of the // tempBytes memory. length := mload(_postBytes) mstore(tempBytes, add(length, mload(tempBytes))) // Move the memory counter back from a multiple of 0x20 to the // actual end of the _preBytes data. mc := end // Stop copying when the memory counter reaches the new combined // length of the arrays. end := add(mc, length) for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } // Update the free-memory pointer by padding our last write location // to 32 bytes: add 31 bytes to the end of tempBytes to move to the // next 32 byte block, then round down to the nearest multiple of // 32. If the sum of the length of the two arrays is zero then add // one before rounding down to leave a blank 32 bytes (the length block with 0). mstore( 0x40, and( add(add(end, iszero(add(length, mload(_preBytes)))), 31), not(31) // Round down to the nearest 32 bytes. ) ) } return tempBytes; } function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { assembly { // Read the first 32 bytes of _preBytes storage, which is the length // of the array. (We don't need to use the offset into the slot // because arrays use the entire slot.) let fslot := sload(_preBytes.slot) // Arrays of 31 bytes or less have an even value in their slot, // while longer arrays have an odd value. The actual length is // the slot divided by two for odd values, and the lowest order // byte divided by two for even values. // If the slot is even, bitwise and the slot with 255 and divide by // two to get the length. If the slot is odd, bitwise and the slot // with -1 and divide by two. let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) let newlength := add(slength, mlength) // slength can contain both the length and contents of the array // if length < 32 bytes so let's prepare for that // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage switch add(lt(slength, 32), lt(newlength, 32)) case 2 { // Since the new array still fits in the slot, we just need to // update the contents of the slot. // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length sstore( _preBytes.slot, // all the modifications to the slot are inside this // next block add( // we can just add to the slot contents because the // bytes we want to change are the LSBs fslot, add( mul( div( // load the bytes from memory mload(add(_postBytes, 0x20)), // zero all bytes to the right exp(0x100, sub(32, mlength)) ), // and now shift left the number of bytes to // leave space for the length in the slot exp(0x100, sub(32, newlength)) ), // increase length by the double of the memory // bytes length mul(mlength, 2) ) ) ) } case 1 { // The stored value fits in the slot, but the combined value // will exceed it. // get the keccak hash to get the contents of the array mstore(0x0, _preBytes.slot) let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // save new length sstore(_preBytes.slot, add(mul(newlength, 2), 1)) // The contents of the _postBytes array start 32 bytes into // the structure. Our first read should obtain the `submod` // bytes that can fit into the unused space in the last word // of the stored array. To get this, we read 32 bytes starting // from `submod`, so the data we read overlaps with the array // contents by `submod` bytes. Masking the lowest-order // `submod` bytes allows us to add that value directly to the // stored value. let submod := sub(32, slength) let mc := add(_postBytes, submod) let end := add(_postBytes, mlength) let mask := sub(exp(0x100, submod), 1) sstore( sc, add( and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), and(mload(mc), mask) ) ) for { mc := add(mc, 0x20) sc := add(sc, 1) } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } default { // get the keccak hash to get the contents of the array mstore(0x0, _preBytes.slot) // Start copying to the last used word of the stored array. let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // save new length sstore(_preBytes.slot, add(mul(newlength, 2), 1)) // Copy over the first `submod` bytes of the new data as in // case 1 above. let slengthmod := mod(slength, 32) let mlengthmod := mod(mlength, 32) let submod := sub(32, slengthmod) let mc := add(_postBytes, submod) let end := add(_postBytes, mlength) let mask := sub(exp(0x100, submod), 1) sstore(sc, add(sload(sc), and(mload(mc), mask))) for { sc := add(sc, 1) mc := add(mc, 0x20) } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } } } function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { // We're using the unchecked block below because otherwise execution ends // with the native overflow error code. unchecked { require(_length + 31 >= _length, "slice_overflow"); } require(_bytes.length >= _start + _length, "slice_outOfBounds"); bytes memory tempBytes; assembly { switch iszero(_length) case 0 { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // The first word of the slice result is potentially a partial // word read from the original array. To read it, we calculate // the length of that partial word and start copying that many // bytes into the array. The first word we copy will start with // data we don't care about, but the last `lengthmod` bytes will // land at the beginning of the contents of the new array. When // we're done copying, we overwrite the full first word with // the actual length of the slice. let lengthmod := and(_length, 31) // The multiplication in the next line is necessary // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) //update free-memory pointer //allocating the array padded to 32 bytes like the compiler does now mstore(0x40, and(add(mc, 31), not(31))) } //if we want a zero-length slice let's just return a zero-length array default { tempBytes := mload(0x40) //zero out the 32 bytes slice we are about to return //we need to do it because Solidity does not garbage collect mstore(tempBytes, 0) mstore(0x40, add(tempBytes, 0x20)) } } return tempBytes; } function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); address tempAddress; assembly { tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) } return tempAddress; } function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); uint8 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x1), _start)) } return tempUint; } function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); uint16 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x2), _start)) } return tempUint; } function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); uint32 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x4), _start)) } return tempUint; } function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); uint64 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x8), _start)) } return tempUint; } function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); uint96 tempUint; assembly { tempUint := mload(add(add(_bytes, 0xc), _start)) } return tempUint; } function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); uint128 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x10), _start)) } return tempUint; } function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); uint256 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x20), _start)) } return tempUint; } function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); bytes32 tempBytes32; assembly { tempBytes32 := mload(add(add(_bytes, 0x20), _start)) } return tempBytes32; } function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { bool success = true; assembly { let length := mload(_preBytes) // if lengths don't match the arrays are not equal switch eq(length, mload(_postBytes)) case 1 { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 let mc := add(_preBytes, 0x20) let end := add(mc, length) for { let cc := add(_postBytes, 0x20) } // the next line is the loop condition: // while(uint256(mc < end) + cb == 2) eq(add(lt(mc, end), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { // if any of these checks fails then arrays are not equal if iszero(eq(mload(mc), mload(cc))) { // unsuccess: success := 0 cb := 0 } } } default { // unsuccess: success := 0 } } return success; } function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { bool success = true; assembly { // we know _preBytes_offset is 0 let fslot := sload(_preBytes.slot) // Decode the length of the stored array like in concatStorage(). let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) // if lengths don't match the arrays are not equal switch eq(slength, mlength) case 1 { // slength can contain both the length and contents of the array // if length < 32 bytes so let's prepare for that // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage if iszero(iszero(slength)) { switch lt(slength, 32) case 1 { // blank the last byte which is the length fslot := mul(div(fslot, 0x100), 0x100) if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { // unsuccess: success := 0 } } default { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 // get the keccak hash to get the contents of the array mstore(0x0, _preBytes.slot) let sc := keccak256(0x0, 0x20) let mc := add(_postBytes, 0x20) let end := add(mc, mlength) // the next line is the loop condition: // while(uint256(mc < end) + cb == 2) for { } eq(add(lt(mc, end), cb), 2) { sc := add(sc, 1) mc := add(mc, 0x20) } { if iszero(eq(sload(sc), mload(mc))) { // unsuccess: success := 0 cb := 0 } } } } } default { // unsuccess: success := 0 } } return success; } }
Strings.sol 507 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
uint256 private constant SPECIAL_CHARS_LOOKUP =
(1 << 0x08) | // backspace
(1 << 0x09) | // tab
(1 << 0x0a) | // newline
(1 << 0x0c) | // form feed
(1 << 0x0d) | // carriage return
(1 << 0x22) | // double quote
(1 << 0x5c); // backslash
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(add(buffer, 0x20), length)
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Converts a `bytes` buffer to its ASCII `string` hexadecimal representation.
*/
function toHexString(bytes memory input) internal pure returns (string memory) {
unchecked {
bytes memory buffer = new bytes(2 * input.length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 0; i < input.length; ++i) {
uint8 v = uint8(input[i]);
buffer[2 * i + 2] = HEX_DIGITS[v >> 4];
buffer[2 * i + 3] = HEX_DIGITS[v & 0xf];
}
return string(buffer);
}
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress-string} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
bytes memory output = new bytes(2 * buffer.length); // worst case scenario
uint256 outputLength = 0;
for (uint256 i; i < buffer.length; ++i) {
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
output[outputLength++] = "\\";
if (char == 0x08) output[outputLength++] = "b";
else if (char == 0x09) output[outputLength++] = "t";
else if (char == 0x0a) output[outputLength++] = "n";
else if (char == 0x0c) output[outputLength++] = "f";
else if (char == 0x0d) output[outputLength++] = "r";
else if (char == 0x5c) output[outputLength++] = "\\";
else if (char == 0x22) {
// solhint-disable-next-line quotes
output[outputLength++] = '"';
}
} else {
output[outputLength++] = char;
}
}
// write the actual length and deallocate unused memory
assembly ("memory-safe") {
mstore(output, outputLength)
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
}
return string(output);
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(add(buffer, 0x20), offset))
}
}
}
IERC7579Account.sol 131 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23 <0.9.0;
/* solhint-disable no-unused-import */
// Types
import { CallType, ExecType, ModeCode } from "../lib/ModeLib.sol";
// Structs
struct Execution {
address target;
uint256 value;
bytes callData;
}
interface IERC7579Account {
event ModuleInstalled(uint256 moduleTypeId, address module);
event ModuleUninstalled(uint256 moduleTypeId, address module);
/**
* @dev Executes a transaction on behalf of the account.
* This function is intended to be called by ERC-4337 EntryPoint.sol
* @dev Ensure adequate authorization control: i.e. onlyEntryPointOrSelf
*
* @dev MSA MUST implement this function signature.
* If a mode is requested that is not supported by the Account, it MUST revert
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
* @param executionCalldata The encoded execution call data
*/
function execute(ModeCode mode, bytes calldata executionCalldata) external payable;
/**
* @dev Executes a transaction on behalf of the account.
* This function is intended to be called by Executor Modules
* @dev Ensure adequate authorization control: i.e. onlyExecutorModule
*
* @dev MSA MUST implement this function signature.
* If a mode is requested that is not supported by the Account, it MUST revert
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
* @param executionCalldata The encoded execution call data
*/
function executeFromExecutor(
ModeCode mode,
bytes calldata executionCalldata
)
external
payable
returns (bytes[] memory returnData);
/**
* @dev ERC-1271 isValidSignature
* This function is intended to be used to validate a smart account signature
* and may forward the call to a validator module
*
* @param hash The hash of the data that is signed
* @param data The data that is signed
*/
function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4);
/**
* @dev installs a Module of a certain type on the smart account
* @dev Implement Authorization control of your chosing
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param initData arbitrary data that may be required on the module during `onInstall`
* initialization.
*/
function installModule(
uint256 moduleTypeId,
address module,
bytes calldata initData
)
external
payable;
/**
* @dev uninstalls a Module of a certain type on the smart account
* @dev Implement Authorization control of your chosing
* @param moduleTypeId the module type ID according the ERC-7579 spec
* @param module the module address
* @param deInitData arbitrary data that may be required on the module during `onUninstall`
* de-initialization.
*/
function uninstallModule(
uint256 moduleTypeId,
address module,
bytes calldata deInitData
)
external
payable;
/**
* Function to check if the account supports a certain CallType or ExecType (see ModeLib.sol)
* @param encodedMode the encoded mode
*/
function supportsExecutionMode(ModeCode encodedMode) external view returns (bool);
/**
* Function to check if the account supports installation of a certain module type Id
* @param moduleTypeId the module type ID according the ERC-7579 spec
*/
function supportsModule(uint256 moduleTypeId) external view returns (bool);
/**
* Function to check if the account has a certain module installed
* @param moduleTypeId the module type ID according the ERC-7579 spec
* Note: keep in mind that some contracts can be multiple module types at the same time. It
* thus may be necessary to query multiple module types
* @param module the module address
* @param additionalContext additional context data that the smart account may interpret to
* identifiy conditions under which the module is installed.
* usually this is not necessary, but for some special hooks that
* are stored in mappings, this param might be needed
*/
function isModuleInstalled(
uint256 moduleTypeId,
address module,
bytes calldata additionalContext
)
external
view
returns (bool);
/**
* @dev Returns the account id of the smart account
* @return accountImplementationId the account id of the smart account
* the accountId should be structured like so:
* "vendorname.accountname.semver"
*/
function accountId() external view returns (string memory accountImplementationId);
}
SignedMath.sol 68 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
}
}
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson.
// Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,
// taking advantage of the most significant (or "sign" bit) in two's complement representation.
// This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,
// the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).
int256 mask = n >> 255;
// A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.
return uint256((n + mask) ^ mask);
}
}
}
ModeLib.sol 160 lines
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
/**
* @title ModeLib
* @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat)
* To allow smart accounts to be very simple, but allow for more complex execution, A custom mode
* encoding is used.
* Function Signature of execute function:
* function execute(ModeCode mode, bytes calldata executionCalldata) external payable;
* This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and
* context.
* NOTE: Simple Account implementations only have to scope for the most significant byte. Account that
* implement
* more complex execution modes may use the entire bytes32.
*
* |--------------------------------------------------------------------|
* | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload |
* |--------------------------------------------------------------------|
* | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes |
* |--------------------------------------------------------------------|
*
* CALLTYPE: 1 byte
* CallType is used to determine how the executeCalldata paramter of the execute function has to be
* decoded.
* It can be either single, batch or delegatecall. In the future different calls could be added.
* CALLTYPE can be used by a validation module to determine how to decode <userOp.callData[36:]>.
*
* EXECTYPE: 1 byte
* ExecType is used to determine how the account should handle the execution.
* It can indicate if the execution should revert on failure or continue execution.
* In the future more execution modes may be added.
* Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in
* a batch fails, the entire batch is reverted
*
* UNUSED: 4 bytes
* Unused bytes are reserved for future use.
*
* ModeSelector: bytes4
* The "optional" mode selector can be used by account vendors, to implement custom behavior in
* their accounts.
* the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename"))
* this is to prevent collisions between different vendors, while allowing innovation and the
* development of new features without coordination between ERC-7579 implementing accounts
*
* ModePayload: 22 bytes
* Mode payload is used to pass additional data to the smart account execution, this may be
* interpreted depending on the ModeSelector
*
* ExecutionCallData: n bytes
* single, delegatecall or batch exec abi.encoded as bytes
*/
// Custom type for improved developer experience
type ModeCode is bytes32;
type CallType is bytes1;
type ExecType is bytes1;
type ModeSelector is bytes4;
type ModePayload is bytes22;
// Default CallType
CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00);
// Batched CallType
CallType constant CALLTYPE_BATCH = CallType.wrap(0x01);
CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE);
// @dev Implementing delegatecall is OPTIONAL!
// implement delegatecall with extreme care.
CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF);
// @dev default behavior is to revert on failure
// To allow very simple accounts to use mode encoding, the default behavior is to revert on failure
// Since this is value 0x00, no additional encoding is required for simple accounts
ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);
// @dev account may elect to change execution behavior. For example "try exec" / "allow fail"
ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01);
ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000));
// Example declaration of a custom mode selector
ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset")));
/**
* @dev ModeLib is a helper library to encode/decode ModeCodes
*/
library ModeLib {
function decode(ModeCode mode)
internal
pure
returns (
CallType _calltype,
ExecType _execType,
ModeSelector _modeSelector,
ModePayload _modePayload
)
{
// solhint-disable-next-line no-inline-assembly
assembly {
_calltype := mode
_execType := shl(8, mode)
_modeSelector := shl(48, mode)
_modePayload := shl(80, mode)
}
}
function encode(
CallType callType,
ExecType execType,
ModeSelector mode,
ModePayload payload
)
internal
pure
returns (ModeCode)
{
return ModeCode.wrap(
bytes32(
abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload)
)
);
}
function encodeSimpleBatch() internal pure returns (ModeCode mode) {
mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00));
}
function encodeSimpleSingle() internal pure returns (ModeCode mode) {
mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00));
}
function getCallType(ModeCode mode) internal pure returns (CallType calltype) {
// solhint-disable-next-line no-inline-assembly
assembly {
calltype := mode
}
}
}
using { eqModeSelector as == } for ModeSelector global;
using { eqCallType as == } for CallType global;
using { neqCallType as != } for CallType global;
using { eqExecType as == } for ExecType global;
function eqCallType(CallType a, CallType b) pure returns (bool) {
return CallType.unwrap(a) == CallType.unwrap(b);
}
function neqCallType(CallType a, CallType b) pure returns (bool) {
return CallType.unwrap(a) == CallType.unwrap(b);
}
function eqExecType(ExecType a, ExecType b) pure returns (bool) {
return ExecType.unwrap(a) == ExecType.unwrap(b);
}
function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) {
return ModeSelector.unwrap(a) == ModeSelector.unwrap(b);
}
Read Contract
ESCROW_IMPLEMENTATION 0x1de18ae6 → address
MAX_SECONDARY_MANAGERS 0x08db8901 → uint256
STRATEGY_IMPLEMENTATION 0xf301061d → address
SUPER_GOVERNOR 0x39c7d246 → address
UPKEEP_WITHDRAWAL_TIMELOCK 0xf00e2f88 → uint256
VAULT_IMPLEMENTATION 0x1f9b5aaf → address
claimableUpkeep 0x05027eee → uint256
getAllSuperVaultEscrows 0x154fd23f → address[]
getAllSuperVaultStrategies 0x8e9615c9 → address[]
getAllSuperVaults 0xa5256bf2 → address[]
getCurrentNonce 0x3a60c386 → uint256
getDeviationThreshold 0x1a63b6fb → uint256
getGlobalHooksRoot 0x2a90a055 → bytes32
getHooksRootUpdateTimelock 0x7be3d10f → uint256
getLastUnpauseTimestamp 0x92352ed2 → uint256
getLastUpdateTimestamp 0x1a351d62 → uint256
getMainManager 0xceb7b7a3 → address
getMaxStaleness 0xc25b784f → uint256
getMinUpdateInterval 0x3ab973a3 → uint256
getPPS 0xbef02b8c → uint256
getPendingManagerChange 0x0c9431b5 → address, uint256
getProposedGlobalHooksRoot 0x9ab4e37b → bytes32, uint256
getProposedMinUpdateInterval 0xa618940f → uint256, uint256
getProposedStrategyHooksRoot 0x2b4bb841 → bytes32, uint256
getSecondaryManagers 0x5f853d40 → address[]
getStrategyHooksRoot 0xc99d2c89 → bytes32
getSuperVaultEscrowsCount 0x5558c3cc → uint256
getSuperVaultStrategiesCount 0x94459ea4 → uint256
getSuperVaultsCount 0x07cbebc1 → uint256
getUpkeepBalance 0x1aef3510 → uint256
isAnyManager 0x9e87cb3f → bool
isGlobalHooksRootActive 0x28f36ff0 → bool
isGlobalHooksRootVetoed 0x81ed8df4 → bool
isMainManager 0xeb91a9b2 → bool
isPPSStale 0x7e8c1517 → bool
isSecondaryManager 0x83aa6836 → bool
isStrategyHooksRootVetoed 0xa8485b73 → bool
isStrategyPaused 0xc06a02e8 → bool
pendingUpkeepWithdrawals 0x5f4bc1be → uint256, uint256
superVaultEscrows 0xbda262d7 → address
superVaultStrategies 0x9dc0ad84 → address
superVaults 0x44648c76 → address
validateHook 0xedbe3a0f → bool
validateHooks 0x3e13bb0a → bool[]
Write Contract 28 functions
These functions modify contract state and require a wallet transaction to execute.
addSecondaryManager 0xc0c3bbd8
address strategy
address manager
cancelChangePrimaryManager 0x464229b4
address strategy
cancelMinUpdateIntervalChange 0x40bb67e1
address strategy
changeGlobalLeavesStatus 0xf430b328
bytes32[] leaves
bool[] statuses
address strategy
changePrimaryManager 0xbd579e55
address strategy
address newManager
address feeRecipient
claimUpkeep 0xd4eb9083
uint256 amount
createVault 0xacf42833
tuple params
returns: address, address, address
depositUpkeep 0x6fe79652
address strategy
uint256 amount
executeChangePrimaryManager 0x9249c392
address strategy
executeGlobalHooksRootUpdate 0x0a48d243
No parameters
executeMinUpdateIntervalChange 0x98b00504
address strategy
executeStrategyHooksRootUpdate 0x7825784b
address strategy
executeWithdrawUpkeep 0x888e3cde
address strategy
forwardPPS 0xc0b4d762
tuple args
pauseStrategy 0xd3f6b598
address strategy
proposeChangePrimaryManager 0xbea3edb1
address strategy
address newManager
address feeRecipient
proposeGlobalHooksRoot 0xb0e5173b
bytes32 newRoot
proposeMinUpdateIntervalChange 0xf3cd2a33
address strategy
uint256 newMinUpdateInterval
proposeStrategyHooksRoot 0x5e12b2db
address strategy
bytes32 newRoot
proposeWithdrawUpkeep 0x7911232a
address strategy
removeSecondaryManager 0x3c248029
address strategy
address manager
resetHighWaterMark 0x7ac432ff
address strategy
setGlobalHooksRootVetoStatus 0xd5f3cd86
bool vetoed
setHooksRootUpdateTimelock 0x272b7add
uint256 newTimelock
setStrategyHooksRootVetoStatus 0xf5297a47
address strategy
bool vetoed
unpauseStrategy 0x0ff323a3
address strategy
updateDeviationThreshold 0x00ee68ba
address strategy
uint256 deviationThreshold_
updatePPSAfterSkim 0x12e1ac5a
uint256 newPPS
uint256 feeAmount
Recent Transactions
No transactions found for this address