Cryo Explorer Ethereum Mainnet

Address Contract Partially Verified

Address 0x0159AEA190C7bEa09873B9b42Fe8fD836DB8a254
Balance 0 ETH
Nonce 71
Code Size 21339 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

21339 bytes
0x608060405234801561000f575f80fd5b506004361061007a575f3560e01c8063374040171161005857806337404017146101105780634238b3af146101305780635dbe8f8b14610152578063a8c62e7614610173575f80fd5b806308e85a781461007e578063187a4ad9146100c257806323ee74fb146100e9575b5f80fd5b6100a57f000000000000000000000000ab253b304b0bfbe38ef7ea1f086d01a6ce1c502881565b6040516001600160a01b0390911681526020015b60405180910390f35b6100a57f0000000000000000000000000c5bc4c8406fe03214d18bbf2962ae2fa378c6f781565b6100a57f000000000000000000000000aeb6bdd95c502390db8f52c8909f703e9af6a34681565b61012361011e366004611684565b61019a565b6040516100b99190611826565b61014361013e366004611868565b6103c4565b6040516100b9939291906118a6565b610165610160366004611868565b61159e565b6040519081526020016100b9565b6100a57f0000000000000000000000003aa61e6196fb3eb1170e578ad924898624f54ad681565b6101a26115cd565b5f828152602081815260409182902082516060808201855282546001600160a01b039081168352600184015481168386015285516101e081018752600285018054831682526003860154831696820196909652600485015482168188015260058501548216928101929092526006840154811660808301526007840154811660a083015260088401541660c0820152600983015460e0820152600a83015460ff808216151561010084810191909152909104161515610120820152600b83018054929593949386019391926101408401919061027d906118da565b80601f01602080910402602001604051908101604052809291908181526020018280546102a9906118da565b80156102f45780601f106102cb576101008083540402835291602001916102f4565b820191905f5260205f20905b8154815290600101906020018083116102d757829003601f168201915b50505050508152602001600a8201805461030d906118da565b80601f0160208091040260200160405190810160405280929190818152602001828054610339906118da565b80156103845780601f1061035b57610100808354040283529160200191610384565b820191905f5260205f20905b81548152906001019060200180831161036757829003601f168201915b5050509183525050600b8201546001600160401b038082166020840152600160401b909104166040820152600c9091015460609091015290525092915050565b5f806103ce6115f0565b5f6103d88561159e565b9050807f0000000000000000000000000c5bc4c8406fe03214d18bbf2962ae2fa378c6f761040c604088016020890161192d565b604080515f81526020810191829052906104259061166a565b6104319392919061195a565b8190604051809103905ff590508015801561044e573d5f803e3d5ffd5b50935080847f000000000000000000000000aeb6bdd95c502390db8f52c8909f703e9af6a3467f000000000000000000000000ab253b304b0bfbe38ef7ea1f086d01a6ce1c50286104a560408a0160208b0161192d565b6040516104b190611677565b6001600160a01b0394851681529284166020840152908316604083015290911660608201526080018190604051809103905ff59050801580156104f6573d5f803e3d5ffd5b50604080516101e08101825230815260e088013560208201529194505f9190810161052961012089016101008a01611994565b1515815260200161054261014089016101208a01611994565b151581526020015f61055a60a08a0160808b0161192d565b6001600160a01b03161415815260200161057a60c0890160a08a0161192d565b6001600160a01b031681526020016105966101408901896119ad565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505050908252506020016105dd6101608901896119ad565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505050908252506001600160a01b037f0000000000000000000000003aa61e6196fb3eb1170e578ad924898624f54ad6166020820181905260408201819052606082015260800161066360e0890160c08a0161192d565b6001600160a01b03908116825286811660208301525f60408084018290526060909301529051639bfe9e2360e01b8152919250861690639bfe9e23906106ad9084906004016119f6565b5f604051808303815f87803b1580156106c4575f80fd5b505af11580156106d6573d5f803e3d5ffd5b505f92506106ed91505060a088016080890161192d565b6001600160a01b03161461085057604051632f2ff15d60e01b81527f1867ae69910bc7238ccbbb445aa11a0dbdd472b851b7fac0c991101aca0a360d60048201819052306024830152906001600160a01b03871690632f2ff15d906044015f604051808303815f87803b158015610762575f80fd5b505af1158015610774573d5f803e3d5ffd5b5050506001600160a01b038716905063a286146661079860a08a0160808b0161192d565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152600160248201526044015f604051808303815f87803b1580156107dd575f80fd5b505af11580156107ef573d5f803e3d5ffd5b5050604051631b2b455f60e11b8152600481018490523060248201526001600160a01b03891692506336568abe91506044015f604051808303815f87803b158015610838575f80fd5b505af115801561084a573d5f803e3d5ffd5b50505050505b846001600160a01b0316632f2ff15d866001600160a01b031663a217fddf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561089b573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108bf9190611b60565b6108cc60208a018a61192d565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b031660248201526044015f604051808303815f87803b158015610910575f80fd5b505af1158015610922573d5f803e3d5ffd5b50505050846001600160a01b0316632f2ff15d866001600160a01b031663c2e9dcd66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610971573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109959190611b60565b6109a260208a018a61192d565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b031660248201526044015f604051808303815f87803b1580156109e6575f80fd5b505af11580156109f8573d5f803e3d5ffd5b50505050846001600160a01b0316632f2ff15d866001600160a01b0316635680e1456040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a47573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a6b9190611b60565b610a7b60608a0160408b0161192d565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b031660248201526044015f604051808303815f87803b158015610abf575f80fd5b505af1158015610ad1573d5f803e3d5ffd5b50505050846001600160a01b0316632f2ff15d866001600160a01b0316633a2bbe496040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b20573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b449190611b60565b610b5460608a0160408b0161192d565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b031660248201526044015f604051808303815f87803b158015610b98575f80fd5b505af1158015610baa573d5f803e3d5ffd5b5050506001600160a01b0386169050632f2ff15d7f6e5811d60b7d57973a97208b6158fed3b8e064ca747403e6a8c81f56a8f9e75f610bef60608a0160408b0161192d565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b031660248201526044015f604051808303815f87803b158015610c33575f80fd5b505af1158015610c45573d5f803e3d5ffd5b50505050846001600160a01b0316632f2ff15d7f0000000000000000000000003aa61e6196fb3eb1170e578ad924898624f54ad66001600160a01b031663cf8dda3b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cb4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cd89190611b60565b610ce860608a0160408b0161192d565b6040516001600160e01b031960e085901b16815260048101929092526001600160a01b031660248201526044015f604051808303815f87803b158015610d2c575f80fd5b505af1158015610d3e573d5f803e3d5ffd5b505f9250610d55915050608088016060890161192d565b6001600160a01b0316146112a857846001600160a01b0316632f2ff15d7f0000000000000000000000003aa61e6196fb3eb1170e578ad924898624f54ad66001600160a01b031663cf8dda3b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610dce573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610df29190611b60565b6040516001600160e01b031960e084901b16815260048101919091523060248201526044015f604051808303815f87803b158015610e2e575f80fd5b505af1158015610e40573d5f803e3d5ffd5b50505050846001600160a01b0316632f2ff15d866001600160a01b0316633a2bbe496040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e8f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610eb39190611b60565b6040516001600160e01b031960e084901b16815260048101919091523060248201526044015f604051808303815f87803b158015610eef575f80fd5b505af1158015610f01573d5f803e3d5ffd5b5050506001600160a01b0386169050634b6a58fe610f256080890160608a0161192d565b5f6040518363ffffffff1660e01b8152600401610f43929190611b77565b5f604051808303815f87803b158015610f5a575f80fd5b505af1158015610f6c573d5f803e3d5ffd5b505f925060019150610f7b9050565b604051908082528060200260200182016040528015610fa4578160200160208202803683370190505b509050610fb7608088016060890161192d565b815f81518110610fc957610fc9611bb0565b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092525f91816020015b604080518082019091525f8082526020820152815260200190600190039081610ff8575050604080518082019091529091508061103d6101a08b016101808c01611bda565b6001600160401b0316815260200161105d6101c08b016101a08c01611bda565b6001600160401b0316815250815f8151811061107b5761107b611bb0565b6020908102919091010152604051637166956d60e01b81526001600160a01b037f0000000000000000000000003aa61e6196fb3eb1170e578ad924898624f54ad61690637166956d906110d6908a9086908690600401611bf3565b5f604051808303815f87803b1580156110ed575f80fd5b505af11580156110ff573d5f803e3d5ffd5b50505050866001600160a01b03166336568abe7f0000000000000000000000003aa61e6196fb3eb1170e578ad924898624f54ad66001600160a01b031663cf8dda3b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561116e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111929190611b60565b6040516001600160e01b031960e084901b16815260048101919091523060248201526044015f604051808303815f87803b1580156111ce575f80fd5b505af11580156111e0573d5f803e3d5ffd5b50505050866001600160a01b03166336568abe886001600160a01b0316633a2bbe496040518163ffffffff1660e01b8152600401602060405180830381865afa15801561122f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112539190611b60565b6040516001600160e01b031960e084901b16815260048101919091523060248201526044015f604051808303815f87803b15801561128f575f80fd5b505af11580156112a1573d5f803e3d5ffd5b5050505050505b846001600160a01b03166336568abe866001600160a01b031663a217fddf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112f3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113179190611b60565b6040516001600160e01b031960e084901b16815260048101919091523060248201526044015f604051808303815f87803b158015611353575f80fd5b505af1158015611365573d5f803e3d5ffd5b5050505081846001600160a01b0316866001600160a01b03167f941c9195b6fb499b0f8e1b227134859845dadb7b8e5f025a9e682118ea8bb34a896040516113ad9190611d0c565b60405180910390a46040518060600160405280866001600160a01b03168152602001856001600160a01b03168152602001876113e890611f83565b90525f8381526020818152604091829020835181546001600160a01b03199081166001600160a01b039283161783558584015160018401805483169184169190911790558585015180516002850180548416918516919091178155948101516003850180548416918516919091179055948501516004840180548316918416919091179055606085015160058401805483169184169190911790556080850151600684018054831691841691909117905560a0850151600784018054831691841691909117905560c0850151600884018054909216921691909117905560e0830151600982015561010080840151600a8301805461012087015161ffff1990911692151561ff0019169290921791151590920217905561014083015190929190600b8401906115179082612112565b50610160820151600a82019061152d9082612112565b50610180820151600b820180546101a08501516001600160401b03908116600160401b026fffffffffffffffffffffffffffffffff199092169316929092179190911790556101c090910151600c9091015550859050848761158e81611f83565b9299919850919650945050505050565b5f816040516020016115b09190611d0c565b604051602081830303815290604052805190602001209050919050565b604080516060810182525f80825260208201529081016115eb6115f0565b905290565b604080516101e0810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820181905261016082015261018081018290526101a081018290526101c081019190915290565b610dbc806121d283390190565b61239880612f8e83390190565b5f60208284031215611694575f80fd5b5035919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b80516001600160a01b031682525f6101e060208301516116f460208601826001600160a01b03169052565b50604083015161170f60408601826001600160a01b03169052565b50606083015161172a60608601826001600160a01b03169052565b50608083015161174560808601826001600160a01b03169052565b5060a083015161176060a08601826001600160a01b03169052565b5060c083015161177b60c08601826001600160a01b03169052565b5060e083015160e08501526101008084015161179a8287018215159052565b505061012083810151151590850152610140808401518186018390526117c28387018261169b565b9250505061016080840151858303828701526117de838261169b565b92505050610180808401516117fd828701826001600160401b03169052565b50506101a0838101516001600160401b0316908501526101c09283015192909301919091525090565b602081525f60018060a01b0380845116602084015280602085015116604084015250604083015160608084015261186060808401826116c9565b949350505050565b5f60208284031215611878575f80fd5b81356001600160401b0381111561188d575f80fd5b82016101e0818503121561189f575f80fd5b9392505050565b6001600160a01b038481168252831660208201526060604082018190525f906118d1908301846116c9565b95945050505050565b600181811c908216806118ee57607f821691505b60208210810361190c57634e487b7160e01b5f52602260045260245ffd5b50919050565b80356001600160a01b0381168114611928575f80fd5b919050565b5f6020828403121561193d575f80fd5b61189f82611912565b634e487b7160e01b5f52604160045260245ffd5b6001600160a01b038481168252831660208201526060604082018190525f906118d19083018461169b565b80358015158114611928575f80fd5b5f602082840312156119a4575f80fd5b61189f82611985565b5f808335601e198436030181126119c2575f80fd5b8301803591506001600160401b038211156119db575f80fd5b6020019150368190038213156119ef575f80fd5b9250929050565b60208152611a106020820183516001600160a01b03169052565b602082015160408201525f6040830151611a2e606084018215159052565b506060830151801515608084015250608083015180151560a08401525060a08301516001600160a01b03811660c08401525060c08301516101e08060e0850152611a7c61020085018361169b565b915060e0850151610100601f198685030181870152611a9b848361169b565b935080870151915050610120611abb818701836001600160a01b03169052565b8601519050610140611ad7868201836001600160a01b03169052565b8601519050610160611af3868201836001600160a01b03169052565b8601519050610180611b0f868201836001600160a01b03169052565b86015190506101a0611b2b868201836001600160a01b03169052565b86015190506101c0611b47868201836001600160a01b03169052565b909501516001600160a01b031693019290925250919050565b5f60208284031215611b70575f80fd5b5051919050565b6001600160a01b03831681526040810160038310611ba357634e487b7160e01b5f52602160045260245ffd5b8260208301529392505050565b634e487b7160e01b5f52603260045260245ffd5b80356001600160401b0381168114611928575f80fd5b5f60208284031215611bea575f80fd5b61189f82611bc4565b6001600160a01b0384811682526060602080840182905285519184018290525f92868201929091906080860190855b81811015611c40578551851683529483019491830191600101611c22565b50506040935085810360408701528092508651808252828201935082880191505f5b81811015611c9457825180516001600160401b0390811687529085015116848601529385019391830191600101611c62565b50929998505050505050505050565b5f808335601e19843603018112611cb8575f80fd5b83016020810192503590506001600160401b03811115611cd6575f80fd5b8036038213156119ef575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b60208152611d2d60208201611d2084611912565b6001600160a01b03169052565b5f611d3a60208401611912565b6001600160a01b038116604084015250611d5660408401611912565b6001600160a01b038116606084015250611d7260608401611912565b6001600160a01b038116608084015250611d8e60808401611912565b6001600160a01b03811660a084015250611daa60a08401611912565b6001600160a01b03811660c084015250611dc660c08401611912565b6001600160a01b03811660e08401525061010060e084013581840152611ded818501611985565b9050610120611dff8185018315159052565b611e0a818601611985565b915050610140611e1d8185018315159052565b611e2981860186611ca3565b925090506101e06101608181870152611e4761020087018585611ce4565b9350611e5581880188611ca3565b93509050610180601f198786030181880152611e72858584611ce4565b9450611e7f818901611bc4565b935050506101a0611e9a818701846001600160401b03169052565b611ea5818801611bc4565b9250506101c0611ebf818701846001600160401b03169052565b9590950135939094019290925250919050565b6040516101e081016001600160401b0381118282101715611ef557611ef5611946565b60405290565b5f82601f830112611f0a575f80fd5b81356001600160401b0380821115611f2457611f24611946565b604051601f8301601f19908116603f01168101908282118183101715611f4c57611f4c611946565b81604052838152866020858801011115611f64575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f6101e08236031215611f94575f80fd5b611f9c611ed2565b611fa583611912565b8152611fb360208401611912565b6020820152611fc460408401611912565b6040820152611fd560608401611912565b6060820152611fe660808401611912565b6080820152611ff760a08401611912565b60a082015261200860c08401611912565b60c082015260e083013560e0820152610100612025818501611985565b90820152610120612037848201611985565b90820152610140838101356001600160401b0380821115612056575f80fd5b61206236838801611efb565b8385015261016092508286013591508082111561207d575f80fd5b5061208a36828701611efb565b82840152505061018061209e818501611bc4565b908201526101a06120b0848201611bc4565b908201526101c092830135928101929092525090565b601f82111561210d57805f5260205f20601f840160051c810160208510156120eb5750805b601f840160051c820191505b8181101561210a575f81556001016120f7565b50505b505050565b81516001600160401b0381111561212b5761212b611946565b61213f8161213984546118da565b846120c6565b602080601f831160018114612172575f841561215b5750858301515b5f19600386901b1c1916600185901b1785556121c9565b5f85815260208120601f198616915b828110156121a057888601518255948401946001909101908401612181565b50858210156121bd57878501515f19600388901b60f8161c191681555b505060018460011b0185555b50505050505056fe60a0604052604051610dbc380380610dbc8339810160408190526100229161036a565b828161002e828261008c565b50508160405161003d9061032e565b6001600160a01b039091168152602001604051809103905ff080158015610066573d5f803e3d5ffd5b506001600160a01b031660805261008461007f60805190565b6100ea565b50505061044b565b61009582610157565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156100de576100d982826101d5565b505050565b6100e6610248565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6101295f80516020610d9c833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a161015481610269565b50565b806001600160a01b03163b5f0361019157604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516101f19190610435565b5f60405180830381855af49150503d805f8114610229576040519150601f19603f3d011682016040523d82523d5f602084013e61022e565b606091505b50909250905061023f8583836102a6565b95945050505050565b34156102675760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b03811661029257604051633173bdd160e11b81525f6004820152602401610188565b805f80516020610d9c8339815191526101b4565b6060826102bb576102b682610305565b6102fe565b81511580156102d257506001600160a01b0384163b155b156102fb57604051639996b31560e01b81526001600160a01b0385166004820152602401610188565b50805b9392505050565b8051156103155780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6104e7806108b583390190565b80516001600160a01b0381168114610351575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f6060848603121561037c575f80fd5b6103858461033b565b92506103936020850161033b565b60408501519092506001600160401b03808211156103af575f80fd5b818601915086601f8301126103c2575f80fd5b8151818111156103d4576103d4610356565b604051601f8201601f19908116603f011681019083821181831017156103fc576103fc610356565b81604052828152896020848701011115610414575f80fd5b8260208601602083015e5f6020848301015280955050505050509250925092565b5f82518060208501845e5f920191825250919050565b6080516104536104625f395f601001526104535ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220e6e83e6b94bd1c904c450a0f7d266eefd5304d703d4586030e57945cfc84fa7d64736f6c63430008190033608060405234801561000f575f80fd5b506040516104e73803806104e783398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b6103f2806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b604051610085919061035d565b3480156100e9575f80fd5b506100616100f8366004610376565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef2869034906101489086908690600401610391565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f61036f602083018461032f565b9392505050565b5f60208284031215610386575f80fd5b813561036f81610238565b6001600160a01b03831681526040602082018190525f906103b49083018461032f565b94935050505056fea2646970667358221220201f933fb8df9921a6b060f6eac1014eaca99e8d26d0affaeed42773b42d620f64736f6c63430008190033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103610100604052348015610010575f80fd5b5060405161239838038061239883398101604081905261002f9161006c565b6001600160a01b0393841660805291831660a052821660c0521660e0526100bd565b80516001600160a01b0381168114610067575f80fd5b919050565b5f805f806080858703121561007f575f80fd5b61008885610051565b935061009660208601610051565b92506100a460408601610051565b91506100b260608601610051565b905092959194509250565b60805160a05160c05160e05161224d61014b5f395f81816101030152610b5101525f818161018e0152610b2f01525f818161023b01526109f601525f8181610275015281816102a20152818161032401528181610409015281816105ec0152818161071001528181610754015281816107f90152818161094401528181610aff0152610c1b015261224d5ff3fe608060405234801561000f575f80fd5b50600436106100e5575f3560e01c806386fa944211610088578063c28ef59111610063578063c28ef59114610223578063d8a06f7314610236578063ef36bbde1461025d578063fbfa77cf14610270575f80fd5b806386fa9442146101c35780638e6adb03146101eb5780639cdf7ad8146101fe575f80fd5b8063413b4bab116100c3578063413b4bab1461016357806347e7ef2414610176578063563a96e11461018957806371f96211146101b0575f80fd5b806301ee7642146100e95780633e47158c146100fe578063402d267d14610142575b5f80fd5b6100fc6100f7366004611071565b610297565b005b6101257f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b610155610150366004611157565b610386565b604051908152602001610139565b6100fc610171366004611172565b6105e1565b6100fc6101843660046111d6565b610705565b6101257f000000000000000000000000000000000000000000000000000000000000000081565b6101256101be366004611157565b610877565b6101256101d1366004611157565b5f602081905290815260409020546001600160a01b031681565b6100fc6101f9366004611200565b6108de565b61021361020c36600461123f565b5f92915050565b6040519015158152602001610139565b610125610231366004611157565b610938565b6101257f000000000000000000000000000000000000000000000000000000000000000081565b61015561026b366004611157565b610c04565b6101257f000000000000000000000000000000000000000000000000000000000000000081565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102e85760405162461bcd60e51b81526004016102df90611276565b60405180910390fd5b5f818060200190518101906102fd91906112ba565b604051635d0b520560e01b81529091506001600160a01b03821690635d0b520590610352907f0000000000000000000000000000000000000000000000000000000000000000908990899089906004016112d5565b5f604051808303815f87803b158015610369575f80fd5b505af115801561037b573d5f803e3d5ffd5b505050505050505050565b5f80829050806001600160a01b03166348d3b7756040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103c7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103eb919061131f565b801561047c575060405163794b15b760e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015282169063794b15b790602401602060405180830381865afa158015610456573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061047a919061131f565b155b1561048957505f92915050565b806001600160a01b031663a1b122026040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104c5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104e9919061131f565b6104f657505f1992915050565b5f816001600160a01b031663bd49c35f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610533573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610557919061133e565b90505f826001600160a01b031663ecf708586040518163ffffffff1660e01b8152600401602060405180830381865afa158015610596573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105ba919061133e565b9050818111156105d7576105ce8282611369565b95945050505050565b505f949350505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106295760405162461bcd60e51b81526004016102df90611276565b60405163f3fef3a360e01b81526001600160a01b038581166004830152602482018490525f919087169063f3fef3a39060440160408051808303815f875af1158015610677573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061069b919061137c565b60405163c8c01a5560e01b81526001600160a01b03878116600483015260248201839052919350908716915063c8c01a55906044015f604051808303815f87803b1580156106e7575f80fd5b505af11580156106f9573d5f803e3d5ffd5b50505050505050505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461074d5760405162461bcd60e51b81526004016102df90611276565b6107e282827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166338d52e0f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ae573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d291906112ba565b6001600160a01b03169190610c8f565b6040516311f9fbc960e21b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018390528316906347e7ef249060440160408051808303815f875af115801561084d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610871919061137c565b50505050565b5f816001600160a01b031663d8dfeb456040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108b4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108d891906112ba565b92915050565b602081146108fe5760405162461bcd60e51b81526004016102df9061139e565b5f61090b82840184611157565b90506001600160a01b0381166109335760405162461bcd60e51b81526004016102df9061139e565b505050565b5f336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146109b15760405162461bcd60e51b815260206004820152601c60248201527f53796d62696f746963416461707465723a206f6e6c79207661756c740000000060448201526064016102df565b506001600160a01b038082165f908152602081905260409020541680156109d757919050565b6040516302910f8b60e31b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906314887c5890602401602060405180830381865afa158015610a3b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5f919061131f565b610abd5760405162461bcd60e51b815260206004820152602960248201527f53796d62696f746963416461707465723a20696e76616c69642073796d62696f6044820152681d1a58c81d985d5b1d60ba1b60648201526084016102df565b6040516bffffffffffffffffffffffff19606084901b16602082015260340160408051601f198184030181529082905280516020909101206001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116602484015284166044830152907f0000000000000000000000000000000000000000000000000000000000000000907f00000000000000000000000000000000000000000000000000000000000000009060640160408051601f198184030181529181526020820180516001600160e01b031663485cc95560e01b17905251610ba890610ff7565b610bb4939291906113e3565b8190604051809103905ff5905080158015610bd1573d5f803e3d5ffd5b506001600160a01b039283165f90815260208190526040902080546001600160a01b031916938216939093179092555090565b6040516359f769a960e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301525f91908316906359f769a990602401602060405180830381865afa158015610c6b573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108d8919061133e565b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301525f919085169063dd62ed3e90604401602060405180830381865afa158015610cdc573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d00919061133e565b90506108718484610d118585611432565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610d628482610dc4565b61087157604080516001600160a01b03851660248201525f6044808301919091528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610dba908590610e61565b6108718482610e61565b5f805f846001600160a01b031684604051610ddf9190611445565b5f604051808303815f865af19150503d805f8114610e18576040519150601f19603f3d011682016040523d82523d5f602084013e610e1d565b606091505b5091509150818015610e47575080511580610e47575080806020019051810190610e47919061131f565b80156105ce5750505050506001600160a01b03163b151590565b5f610e756001600160a01b03841683610ec2565b905080515f14158015610e99575080806020019051810190610e97919061131f565b155b1561093357604051635274afe760e01b81526001600160a01b03841660048201526024016102df565b6060610ecf83835f610ed6565b9392505050565b606081471015610efb5760405163cd78605960e01b81523060048201526024016102df565b5f80856001600160a01b03168486604051610f169190611445565b5f6040518083038185875af1925050503d805f8114610f50576040519150601f19603f3d011682016040523d82523d5f602084013e610f55565b606091505b5091509150610f65868383610f6f565b9695505050505050565b606082610f8457610f7f82610fcb565b610ecf565b8151158015610f9b57506001600160a01b0384163b155b15610fc457604051639996b31560e01b81526001600160a01b03851660048201526024016102df565b5080610ecf565b805115610fdb5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b610dbc8061145c83390190565b6001600160a01b0381168114610ff4575f80fd5b5f8083601f840112611028575f80fd5b50813567ffffffffffffffff81111561103f575f80fd5b602083019150836020828501011115611056575f80fd5b9250929050565b634e487b7160e01b5f52604160045260245ffd5b5f805f8060608587031215611084575f80fd5b843561108f81611004565b9350602085013567ffffffffffffffff808211156110ab575f80fd5b6110b788838901611018565b909550935060408701359150808211156110cf575f80fd5b818701915087601f8301126110e2575f80fd5b8135818111156110f4576110f461105d565b604051601f8201601f19908116603f0116810190838211818310171561111c5761111c61105d565b816040528281528a6020848701011115611134575f80fd5b826020860160208301375f60208483010152809550505050505092959194509250565b5f60208284031215611167575f80fd5b8135610ecf81611004565b5f805f805f60a08688031215611186575f80fd5b853561119181611004565b945060208601356111a181611004565b935060408601356111b181611004565b92506060860135915060808601356111c881611004565b809150509295509295909350565b5f80604083850312156111e7575f80fd5b82356111f281611004565b946020939093013593505050565b5f8060208385031215611211575f80fd5b823567ffffffffffffffff811115611227575f80fd5b61123385828601611018565b90969095509350505050565b5f8060408385031215611250575f80fd5b823561125b81611004565b9150602083013561126b81611004565b809150509250929050565b60208082526024908201527f53796d62696f746963416461707465723a2064656c65676174652063616c6c206040820152636f6e6c7960e01b606082015260800190565b5f602082840312156112ca575f80fd5b8151610ecf81611004565b6001600160a01b0385811682528416602082015260606040820181905281018290525f828460808401375f608084840101526080601f19601f850116830101905095945050505050565b5f6020828403121561132f575f80fd5b81518015158114610ecf575f80fd5b5f6020828403121561134e575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b818103818111156108d8576108d8611355565b5f806040838503121561138d575f80fd5b505080516020909101519092909150565b60208082526025908201527f53796d62696f746963416461707465723a20696e76616c696420726577617264604082015264206461746160d81b606082015260800190565b5f60018060a01b0380861683528085166020840152506060604083015282518060608401528060208501608085015e5f608082850101526080601f19601f830116840101915050949350505050565b808201808211156108d8576108d8611355565b5f82518060208501845e5f92019182525091905056fe60a0604052604051610dbc380380610dbc8339810160408190526100229161036a565b828161002e828261008c565b50508160405161003d9061032e565b6001600160a01b039091168152602001604051809103905ff080158015610066573d5f803e3d5ffd5b506001600160a01b031660805261008461007f60805190565b6100ea565b50505061044b565b61009582610157565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156100de576100d982826101d5565b505050565b6100e6610248565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6101295f80516020610d9c833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a161015481610269565b50565b806001600160a01b03163b5f0361019157604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f80846001600160a01b0316846040516101f19190610435565b5f60405180830381855af49150503d805f8114610229576040519150601f19603f3d011682016040523d82523d5f602084013e61022e565b606091505b50909250905061023f8583836102a6565b95945050505050565b34156102675760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b03811661029257604051633173bdd160e11b81525f6004820152602401610188565b805f80516020610d9c8339815191526101b4565b6060826102bb576102b682610305565b6102fe565b81511580156102d257506001600160a01b0384163b155b156102fb57604051639996b31560e01b81526001600160a01b0385166004820152602401610188565b50805b9392505050565b8051156103155780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6104e7806108b583390190565b80516001600160a01b0381168114610351575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f6060848603121561037c575f80fd5b6103858461033b565b92506103936020850161033b565b60408501519092506001600160401b03808211156103af575f80fd5b818601915086601f8301126103c2575f80fd5b8151818111156103d4576103d4610356565b604051601f8201601f19908116603f011681019083821181831017156103fc576103fc610356565b81604052828152896020848701011115610414575f80fd5b8260208601602083015e5f6020848301015280955050505050509250925092565b5f82518060208501845e5f920191825250919050565b6080516104536104625f395f601001526104535ff3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007a575f356001600160e01b03191663278f794360e11b14610070576040516334ad5dbb60e21b815260040160405180910390fd5b610078610082565b565b6100786100b0565b5f806100913660048184610303565b81019061009e919061033e565b915091506100ac82826100c0565b5050565b6100786100bb61011a565b610151565b6100c98261016f565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156101125761010d82826101ea565b505050565b6100ac61025c565b5f61014c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e80801561016b573d5ff35b3d5ffd5b806001600160a01b03163b5f036101a957604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516102069190610407565b5f60405180830381855af49150503d805f811461023e576040519150601f19603f3d011682016040523d82523d5f602084013e610243565b606091505b509150915061025385838361027b565b95945050505050565b34156100785760405163b398979f60e01b815260040160405180910390fd5b6060826102905761028b826102da565b6102d3565b81511580156102a757506001600160a01b0384163b155b156102d057604051639996b31560e01b81526001600160a01b03851660048201526024016101a0565b50805b9392505050565b8051156102ea5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f8085851115610311575f80fd5b8386111561031d575f80fd5b5050820193919092039150565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561034f575f80fd5b82356001600160a01b0381168114610365575f80fd5b9150602083013567ffffffffffffffff80821115610381575f80fd5b818501915085601f830112610394575f80fd5b8135818111156103a6576103a661032a565b604051601f8201601f19908116603f011681019083821181831017156103ce576103ce61032a565b816040528281528860208487010111156103e6575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f82518060208501845e5f92019182525091905056fea2646970667358221220e6e83e6b94bd1c904c450a0f7d266eefd5304d703d4586030e57945cfc84fa7d64736f6c63430008190033608060405234801561000f575f80fd5b506040516104e73803806104e783398101604081905261002e916100bb565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b50506100e8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100cb575f80fd5b81516001600160a01b03811681146100e1575f80fd5b9392505050565b6103f2806100f55f395ff3fe608060405260043610610049575f3560e01c8063715018a61461004d5780638da5cb5b146100635780639623609d1461008e578063ad3cb1cc146100a1578063f2fde38b146100de575b5f80fd5b348015610058575f80fd5b506100616100fd565b005b34801561006e575f80fd5b505f546040516001600160a01b0390911681526020015b60405180910390f35b61006161009c366004610260565b610110565b3480156100ac575f80fd5b506100d1604051806040016040528060058152602001640352e302e360dc1b81525081565b604051610085919061035d565b3480156100e9575f80fd5b506100616100f8366004610376565b61017b565b6101056101bd565b61010e5f6101e9565b565b6101186101bd565b60405163278f794360e11b81526001600160a01b03841690634f1ef2869034906101489086908690600401610391565b5f604051808303818588803b15801561015f575f80fd5b505af1158015610171573d5f803e3d5ffd5b5050505050505050565b6101836101bd565b6001600160a01b0381166101b157604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6101ba816101e9565b50565b5f546001600160a01b0316331461010e5760405163118cdaa760e01b81523360048201526024016101a8565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146101ba575f80fd5b634e487b7160e01b5f52604160045260245ffd5b5f805f60608486031215610272575f80fd5b833561027d81610238565b9250602084013561028d81610238565b9150604084013567ffffffffffffffff808211156102a9575f80fd5b818601915086601f8301126102bc575f80fd5b8135818111156102ce576102ce61024c565b604051601f8201601f19908116603f011681019083821181831017156102f6576102f661024c565b8160405282815289602084870101111561030e575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f61036f602083018461032f565b9392505050565b5f60208284031215610386575f80fd5b813561036f81610238565b6001600160a01b03831681526040602082018190525f906103b49083018461032f565b94935050505056fea2646970667358221220201f933fb8df9921a6b060f6eac1014eaca99e8d26d0affaeed42773b42d620f64736f6c63430008190033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a26469706673582212206e9e76c911f1ae16b5758eb33a466666f70a881317a30d3f2698910ad1c2892664736f6c63430008190033a264697066735822122051c8e9164a1524a68f56082963d17725caf8b912821f5bf2bb530979754b454a64736f6c63430008190033

Verified Source Code Partial Match

Compiler: v0.8.25+commit.b61c2a91 EVM: cancun Optimization: Yes (200 runs)
MultiVaultDeployScript.sol 154 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../../src/adapters/SymbioticAdapter.sol";
import "../../src/strategies/RatiosStrategy.sol";
import "../../src/vaults/MultiVault.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract MultiVaultDeployScript {
    address public immutable symbioticVaultFactory;
    RatiosStrategy public immutable strategy;
    address public immutable multiVaultImplementation;
    address public immutable symbioticWithdrawalQueueImplementation;

    constructor(
        address symbioticVaultFactory_,
        address strategy_,
        address multiVaultImplementation_,
        address symbioticWithdrawalQueueImplementation_
    ) {
        symbioticVaultFactory = symbioticVaultFactory_;
        strategy = RatiosStrategy(strategy_);
        multiVaultImplementation = multiVaultImplementation_;
        symbioticWithdrawalQueueImplementation = symbioticWithdrawalQueueImplementation_;
    }

    struct DeployParams {
        // actors
        address admin;
        address proxyAdmin;
        address curator;
        // external contracts
        address symbioticVault;
        address depositWrapper;
        address asset;
        address defaultCollateral;
        // vault setup
        uint256 limit;
        bool depositPause;
        bool withdrawalPause;
        string name;
        string symbol;
        // strategy setup
        uint64 minRatioD18;
        uint64 maxRatioD18;
        // salt
        bytes32 salt;
    }

    struct Deployment {
        address multiVault;
        address symbioticAdapter;
        DeployParams params;
    }

    mapping(bytes32 salt => Deployment) private _deployments;

    function calculateSalt(DeployParams calldata params) public pure returns (bytes32) {
        return keccak256(abi.encode(params));
    }

    function deployments(bytes32 salt) public view returns (Deployment memory) {
        return _deployments[salt];
    }

    function deploy(DeployParams calldata params)
        external
        returns (MultiVault multiVault, address symbioticAdapter, DeployParams memory)
    {
        bytes32 salt = calculateSalt(params);
        multiVault = MultiVault(
            address(
                new TransparentUpgradeableProxy{salt: salt}(
                    multiVaultImplementation, params.proxyAdmin, new bytes(0)
                )
            )
        );

        symbioticAdapter = address(
            new SymbioticAdapter{salt: salt}(
                address(multiVault),
                symbioticVaultFactory,
                symbioticWithdrawalQueueImplementation,
                params.proxyAdmin
            )
        );

        IMultiVault.InitParams memory initParams = IMultiVault.InitParams({
            admin: address(this),
            limit: params.limit,
            depositPause: params.depositPause,
            withdrawalPause: params.withdrawalPause,
            depositWhitelist: params.depositWrapper != address(0),
            asset: params.asset,
            name: params.name,
            symbol: params.symbol,
            depositStrategy: address(strategy),
            withdrawalStrategy: address(strategy),
            rebalanceStrategy: address(strategy),
            defaultCollateral: params.defaultCollateral,
            symbioticAdapter: symbioticAdapter,
            eigenLayerAdapter: address(0),
            erc4626Adapter: address(0)
        });

        multiVault.initialize(initParams);

        if (params.depositWrapper != address(0)) {
            bytes32 SET_DEPOSITOR_WHITELIST_STATUS_ROLE =
                keccak256("SET_DEPOSITOR_WHITELIST_STATUS_ROLE");
            multiVault.grantRole(SET_DEPOSITOR_WHITELIST_STATUS_ROLE, address(this));
            multiVault.setDepositorWhitelistStatus(params.depositWrapper, true);
            multiVault.renounceRole(SET_DEPOSITOR_WHITELIST_STATUS_ROLE, address(this));
        }

        // admin roles
        multiVault.grantRole(multiVault.DEFAULT_ADMIN_ROLE(), params.admin);
        multiVault.grantRole(multiVault.SET_FARM_ROLE(), params.admin);

        // curator roles
        multiVault.grantRole(multiVault.REBALANCE_ROLE(), params.curator);
        multiVault.grantRole(multiVault.ADD_SUBVAULT_ROLE(), params.curator);
        multiVault.grantRole(keccak256("SET_LIMIT_ROLE"), params.curator);
        multiVault.grantRole(strategy.RATIOS_STRATEGY_SET_RATIOS_ROLE(), params.curator);

        if (params.symbioticVault != address(0)) {
            multiVault.grantRole(strategy.RATIOS_STRATEGY_SET_RATIOS_ROLE(), address(this));
            multiVault.grantRole(multiVault.ADD_SUBVAULT_ROLE(), address(this));
            multiVault.addSubvault(params.symbioticVault, IMultiVaultStorage.Protocol.SYMBIOTIC);

            address[] memory subvaults = new address[](1);
            subvaults[0] = address(params.symbioticVault);

            IRatiosStrategy.Ratio[] memory ratios_ = new IRatiosStrategy.Ratio[](1);
            ratios_[0] = IRatiosStrategy.Ratio(params.minRatioD18, params.maxRatioD18);

            strategy.setRatios(address(multiVault), subvaults, ratios_);
            multiVault.renounceRole(strategy.RATIOS_STRATEGY_SET_RATIOS_ROLE(), address(this));
            multiVault.renounceRole(multiVault.ADD_SUBVAULT_ROLE(), address(this));
        }
        multiVault.renounceRole(multiVault.DEFAULT_ADMIN_ROLE(), address(this));

        emit Deployed(address(multiVault), symbioticAdapter, salt, params);
        _deployments[salt] = Deployment(address(multiVault), symbioticAdapter, params);
        return (multiVault, symbioticAdapter, params);
    }

    event Deployed(
        address indexed multiVault,
        address indexed symbioticAdapter,
        bytes32 indexed salt,
        DeployParams deployParams
    );
}
SymbioticAdapter.sol 121 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/adapters/ISymbioticAdapter.sol";
import {SymbioticWithdrawalQueue} from "../queues/SymbioticWithdrawalQueue.sol";

contract SymbioticAdapter is ISymbioticAdapter {
    using SafeERC20 for IERC20;

    /// @inheritdoc IProtocolAdapter
    address public immutable vault;
    /// @inheritdoc ISymbioticAdapter
    IRegistry public immutable vaultFactory;

    address public immutable withdrawalQueueSingleton;

    address public immutable proxyAdmin;
    /// @inheritdoc ISymbioticAdapter
    mapping(address symbioticVault => address withdrawalQueue) public withdrawalQueues;

    constructor(
        address vault_,
        address vaultFactory_,
        address withdrawalQueueSingleton_,
        address proxyAdmin_
    ) {
        vault = vault_;
        vaultFactory = IRegistry(vaultFactory_);
        withdrawalQueueSingleton = withdrawalQueueSingleton_;
        proxyAdmin = proxyAdmin_;
    }

    /// @inheritdoc IProtocolAdapter
    function maxDeposit(address symbioticVault) external view returns (uint256) {
        ISymbioticVault vault_ = ISymbioticVault(symbioticVault);
        if (vault_.depositWhitelist() && !vault_.isDepositorWhitelisted(vault)) {
            return 0;
        }
        if (!vault_.isDepositLimit()) {
            return type(uint256).max;
        }
        uint256 activeStake = vault_.activeStake();
        uint256 limit = vault_.depositLimit();
        if (limit > activeStake) {
            return limit - activeStake;
        }
        return 0;
    }

    /// @inheritdoc IProtocolAdapter
    function assetOf(address symbioticVault) external view returns (address) {
        return ISymbioticVault(symbioticVault).collateral();
    }

    /// @inheritdoc IProtocolAdapter
    function stakedAt(address symbioticVault) external view returns (uint256) {
        return ISymbioticVault(symbioticVault).activeBalanceOf(vault);
    }

    /// @inheritdoc IProtocolAdapter
    function handleVault(address symbioticVault) external returns (address withdrawalQueue) {
        require(msg.sender == vault, "SymbioticAdapter: only vault");
        withdrawalQueue = withdrawalQueues[symbioticVault];
        if (withdrawalQueue != address(0)) {
            return withdrawalQueue;
        }
        require(vaultFactory.isEntity(symbioticVault), "SymbioticAdapter: invalid symbiotic vault");
        withdrawalQueue = address(
            new TransparentUpgradeableProxy{salt: keccak256(abi.encodePacked(symbioticVault))}(
                withdrawalQueueSingleton,
                proxyAdmin,
                abi.encodeCall(SymbioticWithdrawalQueue.initialize, (vault, symbioticVault))
            )
        );
        withdrawalQueues[symbioticVault] = withdrawalQueue;
    }

    /// @inheritdoc IProtocolAdapter
    function validateRewardData(bytes calldata data) external pure {
        require(data.length == 32, "SymbioticAdapter: invalid reward data");
        address symbioticFarm = abi.decode(data, (address));
        require(symbioticFarm != address(0), "SymbioticAdapter: invalid reward data");
    }

    /// @inheritdoc IProtocolAdapter
    function pushRewards(address rewardToken, bytes calldata farmData, bytes memory rewardData)
        external
    {
        require(address(this) == vault, "SymbioticAdapter: delegate call only");
        address symbioticFarm = abi.decode(rewardData, (address));
        IStakerRewards(symbioticFarm).claimRewards(vault, address(rewardToken), farmData);
    }

    /// @inheritdoc IProtocolAdapter
    function withdraw(
        address symbioticVault,
        address withdrawalQueue,
        address receiver,
        uint256 request,
        address /* owner */
    ) external {
        require(address(this) == vault, "SymbioticAdapter: delegate call only");
        (, uint256 requestedShares) =
            ISymbioticVault(symbioticVault).withdraw(withdrawalQueue, request);
        ISymbioticWithdrawalQueue(withdrawalQueue).request(receiver, requestedShares);
    }

    /// @inheritdoc IProtocolAdapter
    function deposit(address symbioticVault, uint256 assets) external {
        require(address(this) == vault, "SymbioticAdapter: delegate call only");
        IERC20(IERC4626(vault).asset()).safeIncreaseAllowance(symbioticVault, assets);
        ISymbioticVault(symbioticVault).deposit(vault, assets);
    }

    /// @inheritdoc IProtocolAdapter
    function areWithdrawalsPaused(address, /* symbioticVault */ address /* account */ )
        external
        view
        returns (bool)
    {}
}
RatiosStrategy.sol 320 lines
// // SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/strategies/IRatiosStrategy.sol";

contract RatiosStrategy is IRatiosStrategy {
    /// @inheritdoc IRatiosStrategy
    uint256 public constant D18 = 1e18;
    /// @inheritdoc IRatiosStrategy
    bytes32 public constant RATIOS_STRATEGY_SET_RATIOS_ROLE =
        keccak256("RATIOS_STRATEGY_SET_RATIOS_ROLE");

    /// @inheritdoc IRatiosStrategy
    mapping(address vault => mapping(address subvault => Ratio)) public ratios;

    /// @inheritdoc IRatiosStrategy
    function setRatios(address vault, address[] calldata subvaults, Ratio[] calldata ratios_)
        external
    {
        require(
            IAccessControl(vault).hasRole(RATIOS_STRATEGY_SET_RATIOS_ROLE, msg.sender),
            "RatiosStrategy: forbidden"
        );
        require(
            subvaults.length == ratios_.length,
            "RatiosStrategy: subvaults and ratios length mismatch"
        );
        IMultiVault multiVault = IMultiVault(vault);
        uint256 n = subvaults.length;
        for (uint256 i = 0; i < n; i++) {
            if (multiVault.indexOfSubvault(subvaults[i]) != 0) {
                require(
                    ratios_[i].minRatioD18 <= ratios_[i].maxRatioD18
                        && ratios_[i].maxRatioD18 <= D18,
                    "RatiosStrategy: invalid ratios"
                );
            } else {
                require(
                    ratios_[i].minRatioD18 == 0 && ratios_[i].maxRatioD18 == 0,
                    "RatiosStrategy: invalid subvault"
                );
            }
        }
        mapping(address => Ratio) storage vaultRatios_ = ratios[vault];
        for (uint256 i = 0; i < n; i++) {
            vaultRatios_[subvaults[i]] = ratios_[i];
        }

        emit RatiosSet(vault, subvaults, ratios_);
    }

    /// @inheritdoc IRatiosStrategy
    function calculateState(address vault, bool isDeposit, uint256 increment)
        public
        view
        returns (Amounts[] memory state, uint256 liquid)
    {
        IMultiVault multiVault = IMultiVault(vault);
        uint256 n = multiVault.subvaultsCount();
        state = new Amounts[](n);

        liquid = IERC20(IERC4626(vault).asset()).balanceOf(vault);
        IDefaultCollateral collateral = multiVault.defaultCollateral();
        if (address(collateral) != address(0)) {
            liquid += collateral.balanceOf(vault);
        }
        uint256 totalAssets = liquid;
        IMultiVaultStorage.Subvault memory subvault;
        for (uint256 i = 0; i < n; i++) {
            subvault = multiVault.subvaultAt(i);
            IProtocolAdapter adapter = multiVault.adapterOf(subvault.protocol);
            state[i].staked = adapter.stakedAt(subvault.vault);
            if (!isDeposit && adapter.areWithdrawalsPaused(subvault.vault, vault)) {
                revert("RatiosStrategy: withdrawals paused");
            }
            if (subvault.withdrawalQueue != address(0)) {
                state[i].claimable =
                    IWithdrawalQueue(subvault.withdrawalQueue).claimableAssetsOf(vault);
                state[i].pending = IWithdrawalQueue(subvault.withdrawalQueue).pendingAssetsOf(vault);
                totalAssets += state[i].staked + state[i].pending + state[i].claimable;
            } else {
                totalAssets += state[i].staked;
            }
            uint256 maxDeposit = adapter.maxDeposit(subvault.vault);
            if (type(uint256).max - state[i].staked > maxDeposit) {
                state[i].max = maxDeposit + state[i].staked;
            } else {
                state[i].max = type(uint256).max;
            }
        }
        totalAssets = isDeposit ? totalAssets + increment : totalAssets - increment;
        mapping(address => Ratio) storage vaultRatios_ = ratios[vault];
        for (uint256 i = 0; i < n; i++) {
            Ratio memory ratio = vaultRatios_[multiVault.subvaultAt(i).vault];
            if (ratio.maxRatioD18 == 0) {
                state[i].max = 0;
                state[i].min = 0;
            } else {
                state[i].max = Math.min(state[i].max, (totalAssets * ratio.maxRatioD18) / D18);
                state[i].min = Math.min(state[i].max, (totalAssets * ratio.minRatioD18) / D18);
            }
        }
    }

    /// @inheritdoc IDepositStrategy
    function calculateDepositAmounts(address vault, uint256 amount)
        external
        view
        override
        returns (DepositData[] memory data)
    {
        (Amounts[] memory state,) = calculateState(vault, true, amount);
        uint256 n = state.length;
        for (uint256 i = 0; i < n; i++) {
            uint256 assets_ = state[i].staked;
            if (state[i].min > assets_) {
                state[i].min -= assets_;
                state[i].max -= assets_;
            } else if (state[i].max > assets_) {
                state[i].min = 0;
                state[i].max -= assets_;
            } else {
                state[i].min = 0;
                state[i].max = 0;
            }
        }
        data = new DepositData[](n);
        for (uint256 i = 0; i < n && amount != 0; i++) {
            data[i].subvaultIndex = i;
            if (state[i].min == 0) {
                continue;
            }
            uint256 assets_ = Math.min(state[i].min, amount);
            state[i].max -= assets_;
            amount -= assets_;
            data[i].deposit = assets_;
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].max == 0) {
                continue;
            }
            uint256 assets_ = Math.min(state[i].max, amount);
            amount -= assets_;
            data[i].deposit += assets_;
        }
        uint256 count = 0;
        for (uint256 i = 0; i < n; i++) {
            if (data[i].deposit != 0) {
                if (count != i) {
                    data[count] = data[i];
                }
                count++;
            }
        }
        assembly {
            mstore(data, count)
        }
    }

    /// @inheritdoc IWithdrawalStrategy
    function calculateWithdrawalAmounts(address vault, uint256 amount)
        external
        view
        override
        returns (WithdrawalData[] memory data)
    {
        (Amounts[] memory state, uint256 liquid) = calculateState(vault, false, amount);
        if (amount <= liquid) {
            return data;
        }
        amount -= liquid;
        uint256 n = state.length;
        data = new WithdrawalData[](n);
        for (uint256 i = 0; i < n && amount != 0; i++) {
            data[i].subvaultIndex = i;
            if (state[i].staked > state[i].max) {
                uint256 extra = state[i].staked - state[i].max;
                if (extra > amount) {
                    data[i].staked = amount;
                    amount = 0;
                } else {
                    data[i].staked = extra;
                    amount -= extra;
                }
                state[i].staked -= data[i].staked;
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].staked > state[i].min) {
                uint256 allowed = state[i].staked - state[i].min;
                if (allowed > amount) {
                    data[i].staked += amount;
                    amount = 0;
                } else {
                    data[i].staked += allowed;
                    amount -= allowed;
                    state[i].staked -= allowed;
                }
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].pending > 0) {
                if (state[i].pending > amount) {
                    data[i].pending += amount;
                    amount = 0;
                } else {
                    data[i].pending += state[i].pending;
                    amount -= state[i].pending;
                }
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].claimable > 0) {
                if (state[i].claimable > amount) {
                    data[i].claimable += amount;
                    amount = 0;
                } else {
                    data[i].claimable += state[i].claimable;
                    amount -= state[i].claimable;
                }
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            uint256 staked = state[i].staked;
            if (staked > 0) {
                if (staked > amount) {
                    data[i].staked += amount;
                    amount = 0;
                } else {
                    data[i].staked += staked;
                    amount -= staked;
                }
            }
        }

        uint256 count = 0;
        for (uint256 i = 0; i < n; i++) {
            if (data[i].staked + data[i].pending + data[i].claimable != 0) {
                if (count != i) {
                    data[count] = data[i];
                }
                count++;
            }
        }
        assembly {
            mstore(data, count)
        }
    }

    /// @inheritdoc IRebalanceStrategy
    function calculateRebalanceAmounts(address vault)
        external
        view
        override
        returns (RebalanceData[] memory data)
    {
        (Amounts[] memory state, uint256 liquid) = calculateState(vault, false, 0);
        uint256 n = state.length;
        data = new RebalanceData[](n);
        uint256 totalRequired = 0;
        uint256 pending = 0;
        for (uint256 i = 0; i < n; i++) {
            data[i].subvaultIndex = i;
            data[i].claimable = state[i].claimable;
            liquid += state[i].claimable;
            pending += state[i].pending;
            if (state[i].staked > state[i].max) {
                data[i].staked = state[i].staked - state[i].max;
                pending += data[i].staked;
                state[i].staked = state[i].max;
            }
            if (state[i].min > state[i].staked) {
                totalRequired += state[i].min - state[i].staked;
            }
        }

        if (totalRequired > liquid + pending) {
            uint256 unstake = totalRequired - liquid - pending;
            for (uint256 i = 0; i < n && unstake > 0; i++) {
                if (state[i].staked > state[i].min) {
                    uint256 allowed = state[i].staked - state[i].min;
                    if (allowed > unstake) {
                        data[i].staked += unstake;
                        unstake = 0;
                    } else {
                        data[i].staked += allowed;
                        unstake -= allowed;
                    }
                }
            }
        }

        for (uint256 i = 0; i < n && liquid > 0; i++) {
            if (state[i].staked < state[i].min) {
                uint256 required = state[i].min - state[i].staked;
                if (required > liquid) {
                    data[i].deposit = liquid;
                    liquid = 0;
                } else {
                    data[i].deposit = required;
                    liquid -= required;
                    state[i].max -= data[i].deposit;
                }
            }
        }

        for (uint256 i = 0; i < n && liquid > 0; i++) {
            if (state[i].staked < state[i].max) {
                uint256 allowed = state[i].max - state[i].staked;
                if (allowed > liquid) {
                    data[i].deposit += liquid;
                    liquid = 0;
                } else {
                    data[i].deposit += allowed;
                    liquid -= allowed;
                }
            }
        }
    }
}
MultiVault.sol 375 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/vaults/IMultiVault.sol";
import {ERC4626Vault} from "./ERC4626Vault.sol";
import {MultiVaultStorage} from "./MultiVaultStorage.sol";
import {VaultControlStorage} from "./VaultControlStorage.sol";

contract MultiVault is IMultiVault, ERC4626Vault, MultiVaultStorage {
    using SafeERC20 for IERC20;
    using Math for uint256;

    uint256 private constant D6 = 1e6;
    bytes32 public constant ADD_SUBVAULT_ROLE = keccak256("ADD_SUBVAULT_ROLE");
    bytes32 public constant REMOVE_SUBVAULT_ROLE = keccak256("REMOVE_SUBVAULT_ROLE");
    bytes32 public constant SET_STRATEGY_ROLE = keccak256("SET_STRATEGY_ROLE");
    bytes32 public constant SET_FARM_ROLE = keccak256("SET_FARM_ROLE");
    bytes32 public constant REBALANCE_ROLE = keccak256("REBALANCE_ROLE");
    bytes32 public constant SET_DEFAULT_COLLATERAL_ROLE = keccak256("SET_DEFAULT_COLLATERAL_ROLE");
    bytes32 public constant SET_ADAPTER_ROLE = keccak256("SET_ADAPTER_ROLE");

    constructor(bytes32 name_, uint256 version_)
        VaultControlStorage(name_, version_)
        MultiVaultStorage(name_, version_)
    {
        _disableInitializers();
    }

    // ------------------------------- EXTERNAL VIEW FUNCTIONS -------------------------------

    /// @inheritdoc IERC4626
    function totalAssets()
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256 assets_)
    {
        address this_ = address(this);
        assets_ = IERC20(asset()).balanceOf(this_);
        IDefaultCollateral collateral = defaultCollateral();
        if (address(collateral) != address(0)) {
            assets_ += collateral.balanceOf(this_);
        }

        uint256 length = subvaultsCount();
        Subvault memory subvault;
        for (uint256 i = 0; i < length; i++) {
            subvault = subvaultAt(i);
            assets_ += adapterOf(subvault.protocol).stakedAt(subvault.vault);
            if (subvault.withdrawalQueue != address(0)) {
                assets_ += IWithdrawalQueue(subvault.withdrawalQueue).claimableAssetsOf(this_)
                    + IWithdrawalQueue(subvault.withdrawalQueue).pendingAssetsOf(this_);
            }
        }
    }

    // ------------------------------- EXTERNAL MUTATIVE FUNCTIONS -------------------------------

    /// @inheritdoc IMultiVault
    function initialize(InitParams calldata initParams) public virtual reinitializer(2) {
        __initializeERC4626(
            initParams.admin,
            initParams.limit,
            initParams.depositPause,
            initParams.withdrawalPause,
            initParams.depositWhitelist,
            initParams.asset,
            initParams.name,
            initParams.symbol
        );
        __initializeMultiVaultStorage(
            initParams.depositStrategy,
            initParams.withdrawalStrategy,
            initParams.rebalanceStrategy,
            initParams.defaultCollateral,
            initParams.symbioticAdapter,
            initParams.eigenLayerAdapter,
            initParams.erc4626Adapter
        );
        require(
            initParams.defaultCollateral == address(0)
                || IDefaultCollateral(initParams.defaultCollateral).asset() == initParams.asset,
            "MultiVault: default collateral asset does not match the vault asset"
        );
    }

    /// @inheritdoc IMultiVault
    function addSubvault(address vault, Protocol protocol) external onlyRole(ADD_SUBVAULT_ROLE) {
        IProtocolAdapter adapter = adapterOf(protocol);
        require(
            adapter.assetOf(vault) == asset(),
            "MultiVault: subvault asset does not match the vault asset"
        );
        _addSubvault(vault, adapter.handleVault(vault), protocol);
    }

    /// @inheritdoc IMultiVault
    function removeSubvault(address subvault) external onlyRole(REMOVE_SUBVAULT_ROLE) {
        _removeSubvault(subvault);
    }

    /// @inheritdoc IMultiVault
    function setDepositStrategy(address newDepositStrategy) external onlyRole(SET_STRATEGY_ROLE) {
        require(
            newDepositStrategy != address(0), "MultiVault: deposit strategy cannot be zero address"
        );
        _setDepositStrategy(newDepositStrategy);
    }

    /// @inheritdoc IMultiVault
    function setWithdrawalStrategy(address newWithdrawalStrategy)
        external
        onlyRole(SET_STRATEGY_ROLE)
    {
        require(
            newWithdrawalStrategy != address(0),
            "MultiVault: withdrawal strategy cannot be zero address"
        );
        _setWithdrawalStrategy(newWithdrawalStrategy);
    }

    /// @inheritdoc IMultiVault
    function setRebalanceStrategy(address newRebalanceStrategy)
        external
        onlyRole(SET_STRATEGY_ROLE)
    {
        require(
            newRebalanceStrategy != address(0),
            "MultiVault: rebalance strategy cannot be zero address"
        );
        _setRebalanceStrategy(newRebalanceStrategy);
    }

    /// @inheritdoc IMultiVault
    function setDefaultCollateral(address defaultCollateral_)
        external
        onlyRole(SET_DEFAULT_COLLATERAL_ROLE)
    {
        require(
            address(defaultCollateral()) == address(0) && defaultCollateral_ != address(0),
            "MultiVault: default collateral already set or cannot be zero address"
        );
        require(
            IDefaultCollateral(defaultCollateral_).asset() == asset(),
            "MultiVault: default collateral asset does not match the vault asset"
        );
        _setDefaultCollateral(defaultCollateral_);
    }

    /// @inheritdoc IMultiVault
    function setSymbioticAdapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
        require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
        _setSymbioticAdapter(adapter_);
    }

    /// @inheritdoc IMultiVault
    function setEigenLayerAdapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
        require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
        _setEigenLayerAdapter(adapter_);
    }

    /// @inheritdoc IMultiVault
    function setERC4626Adapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
        require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
        _setERC4626Adapter(adapter_);
    }

    /// @inheritdoc IMultiVault
    function setRewardsData(uint256 farmId, RewardData calldata rewardData)
        external
        onlyRole(SET_FARM_ROLE)
    {
        if (rewardData.token != address(0)) {
            require(
                rewardData.token != asset() && rewardData.token != address(defaultCollateral()),
                "MultiVault: reward token cannot be the same as the asset or default collateral"
            );
            require(rewardData.curatorFeeD6 <= D6, "MultiVault: curator fee exceeds 100%");
            require(
                rewardData.distributionFarm != address(0),
                "MultiVault: distribution farm address cannot be zero"
            );
            if (rewardData.curatorFeeD6 != 0) {
                require(
                    rewardData.curatorTreasury != address(0),
                    "MultiVault: curator treasury address cannot be zero when fee is set"
                );
            }
            adapterOf(rewardData.protocol).validateRewardData(rewardData.data);
        }
        _setRewardData(farmId, rewardData);
    }

    /// @inheritdoc IMultiVault
    function rebalance() external onlyRole(REBALANCE_ROLE) {
        address this_ = address(this);
        IRebalanceStrategy.RebalanceData[] memory data =
            rebalanceStrategy().calculateRebalanceAmounts(this_);
        for (uint256 i = 0; i < data.length; i++) {
            _withdraw(data[i].subvaultIndex, data[i].staked, 0, data[i].claimable, this_, this_);
        }
        IDefaultCollateral collateral = defaultCollateral();
        if (address(collateral) != address(0)) {
            uint256 balance = collateral.balanceOf(this_);
            if (balance != 0) {
                collateral.withdraw(this_, balance);
            }
        }
        for (uint256 i = 0; i < data.length; i++) {
            _deposit(data[i].subvaultIndex, data[i].deposit);
        }
        _depositIntoCollateral();
        emit Rebalance(data, block.timestamp);
    }

    /// @inheritdoc IMultiVault
    function pushRewards(uint256 farmId, bytes calldata farmData) external nonReentrant {
        require(farmIdsContains(farmId), "MultiVault: farm not found");
        IMultiVaultStorage.RewardData memory data = rewardData(farmId);
        IERC20 rewardToken = IERC20(data.token);

        address this_ = address(this);
        uint256 rewardAmount = rewardToken.balanceOf(this_);

        Address.functionDelegateCall(
            address(adapterOf(data.protocol)),
            abi.encodeCall(
                IProtocolAdapter.pushRewards, (address(rewardToken), farmData, data.data)
            )
        );

        rewardAmount = rewardToken.balanceOf(this_) - rewardAmount;
        if (rewardAmount == 0) {
            return;
        }

        uint256 curatorFee = rewardAmount.mulDiv(data.curatorFeeD6, D6);
        if (curatorFee != 0) {
            rewardToken.safeTransfer(data.curatorTreasury, curatorFee);
        }
        rewardAmount = rewardAmount - curatorFee;
        if (rewardAmount != 0) {
            rewardToken.safeTransfer(data.distributionFarm, rewardAmount);
        }
        emit RewardsPushed(farmId, rewardAmount, curatorFee, block.timestamp);
    }

    // ------------------------------- INTERNAL MUTATIVE FUNCTIONS -------------------------------

    /// @dev Deposits assets into the specified subvault
    function _deposit(uint256 subvaultIndex, uint256 assets) private {
        if (assets == 0) {
            return;
        }
        Subvault memory subvault = subvaultAt(subvaultIndex);
        Address.functionDelegateCall(
            address(adapterOf(subvault.protocol)),
            abi.encodeCall(IProtocolAdapter.deposit, (subvault.vault, assets))
        );
    }

    /// @dev Withdraws assets from the specified subvault
    function _withdraw(
        uint256 subvaultIndex,
        uint256 request,
        uint256 pending,
        uint256 claimable,
        address owner,
        address receiver
    ) private {
        Subvault memory subvault = subvaultAt(subvaultIndex);
        address this_ = address(this);
        if (claimable != 0) {
            IWithdrawalQueue(subvault.withdrawalQueue).claim(this_, receiver, claimable);
        }
        if (pending != 0) {
            IWithdrawalQueue(subvault.withdrawalQueue).transferPendingAssets(receiver, pending);
        }
        if (request != 0) {
            Address.functionDelegateCall(
                address(adapterOf(subvault.protocol)),
                abi.encodeCall(
                    IProtocolAdapter.withdraw,
                    (subvault.vault, subvault.withdrawalQueue, receiver, request, owner)
                )
            );
        }
    }

    /// @dev Deposits assets into the default collateral
    function _depositIntoCollateral() private {
        IDefaultCollateral collateral = defaultCollateral();
        if (address(collateral) == address(0)) {
            return;
        }
        uint256 limit_ = collateral.limit();
        uint256 supply_ = collateral.totalSupply();
        if (supply_ >= limit_) {
            return;
        }
        address this_ = address(this);
        IERC20 asset_ = IERC20(asset());
        uint256 assets = asset_.balanceOf(this_).min(limit_ - supply_);
        if (assets == 0) {
            return;
        }
        asset_.safeIncreaseAllowance(address(collateral), assets);
        collateral.deposit(this_, assets);
        emit DepositIntoCollateral(assets);
    }

    function _deposit(address caller, address receiver, uint256 assets, uint256 shares)
        internal
        virtual
        override
    {
        address this_ = address(this);
        IDepositStrategy.DepositData[] memory data =
            depositStrategy().calculateDepositAmounts(this_, assets);
        super._deposit(caller, receiver, assets, shares);
        for (uint256 i = 0; i < data.length; i++) {
            if (data[i].deposit != 0) {
                _deposit(data[i].subvaultIndex, data[i].deposit);
                assets -= data[i].deposit;
            }
        }

        _depositIntoCollateral();
    }

    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual override {
        address this_ = address(this);

        IWithdrawalStrategy.WithdrawalData[] memory data =
            withdrawalStrategy().calculateWithdrawalAmounts(this_, assets);

        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        _burn(owner, shares);

        uint256 liquid = assets;
        IWithdrawalStrategy.WithdrawalData memory d;
        for (uint256 i = 0; i < data.length; i++) {
            d = data[i];
            _withdraw(d.subvaultIndex, d.staked, d.pending, d.claimable, owner, receiver);
            liquid -= d.staked + d.pending + d.claimable;
        }

        if (liquid != 0) {
            IERC20 asset_ = IERC20(asset());
            uint256 balance = asset_.balanceOf(this_);
            if (balance < liquid) {
                if (balance != 0) {
                    asset_.safeTransfer(receiver, balance);
                    liquid -= balance;
                }
                defaultCollateral().withdraw(receiver, liquid);
            } else {
                asset_.safeTransfer(receiver, liquid);
            }
        }

        // emitting event with transferred + new pending assets
        emit Withdraw(caller, receiver, owner, assets, shares);
    }
}
TransparentUpgradeableProxy.sol 116 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol)

pragma solidity ^0.8.20;

import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {ProxyAdmin} from "./ProxyAdmin.sol";

/**
 * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
 * does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
 * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
 * include them in the ABI so this interface must be used to interact with it.
 */
interface ITransparentUpgradeableProxy is IERC1967 {
    function upgradeToAndCall(address, bytes calldata) external payable;
}

/**
 * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
 *
 * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
 * clashing], which can potentially be used in an attack, this contract uses the
 * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
 * things that go hand in hand:
 *
 * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
 * that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
 * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
 * the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
 * the proxy admin cannot fallback to the target implementation.
 *
 * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
 * dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
 * call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and
 * allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
 * interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
 *
 * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
 * inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
 * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
 * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
 * implementation.
 *
 * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
 * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
 *
 * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
 * immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
 * overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
 * undesirable state where the admin slot is different from the actual admin.
 *
 * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
 * compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
 * function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
 * could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
 */
contract TransparentUpgradeableProxy is ERC1967Proxy {
    // An immutable address for the admin to avoid unnecessary SLOADs before each call
    // at the expense of removing the ability to change the admin once it's set.
    // This is acceptable if the admin is always a ProxyAdmin instance or similar contract
    // with its own ability to transfer the permissions to another account.
    address private immutable _admin;

    /**
     * @dev The proxy caller is the current admin, and can't fallback to the proxy target.
     */
    error ProxyDeniedAdminAccess();

    /**
     * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
     * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
     * {ERC1967Proxy-constructor}.
     */
    constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
        _admin = address(new ProxyAdmin(initialOwner));
        // Set the storage value and emit an event for ERC-1967 compatibility
        ERC1967Utils.changeAdmin(_proxyAdmin());
    }

    /**
     * @dev Returns the admin of this proxy.
     */
    function _proxyAdmin() internal virtual returns (address) {
        return _admin;
    }

    /**
     * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
     */
    function _fallback() internal virtual override {
        if (msg.sender == _proxyAdmin()) {
            if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
                revert ProxyDeniedAdminAccess();
            } else {
                _dispatchUpgradeToAndCall();
            }
        } else {
            super._fallback();
        }
    }

    /**
     * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
     *
     * Requirements:
     *
     * - If `data` is empty, `msg.value` must be zero.
     */
    function _dispatchUpgradeToAndCall() private {
        (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
        ERC1967Utils.upgradeToAndCall(newImplementation, data);
    }
}
ISymbioticAdapter.sol 22 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {ISymbioticWithdrawalQueue} from "../queues/ISymbioticWithdrawalQueue.sol";
import {IProtocolAdapter} from "./IProtocolAdapter.sol";
import {IERC20, IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";

import {TransparentUpgradeableProxy} from
    "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IRegistry} from "@symbiotic/core/interfaces/common/IRegistry.sol";
import {IVault as ISymbioticVault} from "@symbiotic/core/interfaces/vault/IVault.sol";
import {IStakerRewards} from "@symbiotic/rewards/interfaces/stakerRewards/IStakerRewards.sol";

interface ISymbioticAdapter is IProtocolAdapter {
    function withdrawalQueues(address symbioticVault)
        external
        view
        returns (address withdrawalQueue);

    function vaultFactory() external view returns (IRegistry);
}
SymbioticWithdrawalQueue.sol 347 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/queues/ISymbioticWithdrawalQueue.sol";

contract SymbioticWithdrawalQueue is ISymbioticWithdrawalQueue, Initializable {
    using SafeERC20 for IERC20;
    using Math for uint256;

    /// @inheritdoc IWithdrawalQueue
    address public immutable claimer;
    /// @inheritdoc ISymbioticWithdrawalQueue
    address public vault;
    /// @inheritdoc ISymbioticWithdrawalQueue
    ISymbioticVault public symbioticVault;
    /// @inheritdoc ISymbioticWithdrawalQueue
    address public collateral;

    mapping(uint256 epoch => EpochData data) private _epochData;
    mapping(address account => AccountData data) private _accountData;

    constructor(address claimer_) {
        claimer = claimer_;
        _disableInitializers();
    }

    function initialize(address vault_, address symbioticVault_) external initializer {
        __init_SymbioticWithdrawalQueue(vault_, symbioticVault_);
    }

    function __init_SymbioticWithdrawalQueue(address vault_, address symbioticVault_)
        internal
        onlyInitializing
    {
        vault = vault_;
        symbioticVault = ISymbioticVault(symbioticVault_);
        collateral = ISymbioticVault(symbioticVault_).collateral();
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function getCurrentEpoch() public view returns (uint256) {
        return symbioticVault.currentEpoch();
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function getAccountData(address account)
        external
        view
        returns (
            uint256 sharesToClaimPrev,
            uint256 sharesToClaim,
            uint256 claimableAssets,
            uint256 claimEpoch
        )
    {
        AccountData storage accountData = _accountData[account];
        claimEpoch = accountData.claimEpoch;
        sharesToClaimPrev = claimEpoch == 0 ? 0 : accountData.sharesToClaim[claimEpoch - 1];
        sharesToClaim = accountData.sharesToClaim[claimEpoch];
        claimableAssets = accountData.claimableAssets;
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function getEpochData(uint256 epoch) external view returns (EpochData memory) {
        return _epochData[epoch];
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function pendingAssets() public view returns (uint256) {
        uint256 epoch = getCurrentEpoch();
        address this_ = address(this);
        ISymbioticVault symbioticVault_ = symbioticVault;
        return symbioticVault_.withdrawalsOf(epoch, this_)
            + symbioticVault_.withdrawalsOf(epoch + 1, this_);
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function pendingAssetsOf(address account) public view returns (uint256 assets) {
        uint256 epoch = getCurrentEpoch();

        AccountData storage accountData = _accountData[account];
        assets += _withdrawalsOf(epoch, accountData.sharesToClaim[epoch]);
        epoch += 1;
        assets += _withdrawalsOf(epoch, accountData.sharesToClaim[epoch]);
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function claimableAssetsOf(address account) public view returns (uint256 assets) {
        AccountData storage accountData = _accountData[account];
        assets = accountData.claimableAssets;

        uint256 currentEpoch = getCurrentEpoch();
        uint256 epoch = accountData.claimEpoch;
        if (epoch > 0 && _isClaimableInSymbiotic(epoch - 1, currentEpoch)) {
            assets += _claimable(accountData, epoch - 1);
        }

        if (_isClaimableInSymbiotic(epoch, currentEpoch)) {
            assets += _claimable(accountData, epoch);
        }
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function request(address account, uint256 amount) external {
        require(msg.sender == vault, "SymbioticWithdrawalQueue: forbidden");
        if (amount == 0) {
            return;
        }
        AccountData storage accountData = _accountData[account];

        uint256 epoch = getCurrentEpoch();
        _handlePendingEpochs(accountData, epoch);

        epoch = epoch + 1;
        EpochData storage epochData = _epochData[epoch];
        epochData.sharesToClaim += amount;

        accountData.sharesToClaim[epoch] += amount;
        accountData.claimEpoch = epoch;
        emit WithdrawalRequested(account, epoch, amount);
    }

    /// @inheritdoc IWithdrawalQueue
    function transferPendingAssets(address to, uint256 amount) external {
        address from = msg.sender;
        if (amount == 0 || from == to) {
            return;
        }

        uint256 epoch = getCurrentEpoch();
        uint256 nextEpoch = epoch + 1;
        AccountData storage fromData = _accountData[from];
        AccountData storage toData = _accountData[to];
        _handlePendingEpochs(fromData, epoch);
        _handlePendingEpochs(toData, epoch);

        uint256 nextSharesToClaim = fromData.sharesToClaim[nextEpoch];
        uint256 nextPending = _withdrawalsOf(nextEpoch, nextSharesToClaim);
        if (nextPending >= amount) {
            uint256 shares = nextSharesToClaim.mulDiv(amount, nextPending);
            fromData.sharesToClaim[nextEpoch] -= shares;
            toData.sharesToClaim[nextEpoch] += shares;
            toData.claimEpoch = nextEpoch;
            emit Transfer(from, to, nextEpoch, shares);
        } else {
            uint256 currentSharesToClaim = fromData.sharesToClaim[epoch];
            uint256 currentPending = _withdrawalsOf(epoch, currentSharesToClaim);
            require(
                nextPending + currentPending >= amount,
                "SymbioticWithdrawalQueue: insufficient pending assets"
            );

            if (nextSharesToClaim != 0) {
                toData.sharesToClaim[nextEpoch] += nextSharesToClaim;
                delete fromData.sharesToClaim[nextEpoch];
                amount -= nextPending;
                emit Transfer(from, to, nextEpoch, nextSharesToClaim);
            }

            uint256 shares = currentSharesToClaim.mulDiv(amount, currentPending);
            fromData.sharesToClaim[epoch] = currentSharesToClaim - shares;
            toData.sharesToClaim[epoch] += shares;
            toData.claimEpoch = nextEpoch;
            emit Transfer(from, to, epoch, shares);
        }
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function pull(uint256 epoch) public {
        require(
            _isClaimableInSymbiotic(epoch, getCurrentEpoch()),
            "SymbioticWithdrawalQueue: invalid epoch"
        );
        _pullFromSymbioticForEpoch(epoch);
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function claim(address account, address recipient, uint256 maxAmount)
        external
        returns (uint256 amount)
    {
        address sender = msg.sender;
        require(sender == account || sender == claimer, "SymbioticWithdrawalQueue: forbidden");
        AccountData storage accountData = _accountData[account];
        _handlePendingEpochs(accountData, getCurrentEpoch());
        amount = accountData.claimableAssets;
        if (amount == 0) {
            return 0;
        }
        if (amount <= maxAmount) {
            accountData.claimableAssets = 0;
        } else {
            amount = maxAmount;
            accountData.claimableAssets -= maxAmount;
        }
        if (amount != 0) {
            IERC20(collateral).safeTransfer(recipient, amount);
        }
        emit Claimed(account, recipient, amount);
    }

    /// @inheritdoc ISymbioticWithdrawalQueue
    function handlePendingEpochs(address account) public {
        _handlePendingEpochs(_accountData[account], getCurrentEpoch());
    }

    /**
     * @notice Calculates the amount of collateral that will be withdrawn for the given `shares` at the specified epoch.
     * @param epoch The epoch number in the Symbiotic Vault.
     * @param shares The amount of withdrawal shares.
     * @return assets The corresponding amount of collateral that will be withdrawn based on the `shares`.
     */
    function _withdrawalsOf(uint256 epoch, uint256 shares) private view returns (uint256) {
        if (shares == 0) {
            return 0;
        }
        return shares.mulDiv(
            symbioticVault.withdrawalsOf(epoch, address(this)), _epochData[epoch].sharesToClaim
        );
    }

    /**
     * @notice Claims collateral from the Symbiotic Vault for the given account at the current and previous epochs.
     * @dev This function updates the `epochData` and `accountData` mappings accordingly.
     * @param accountData The storage struct containing specific claim data for the account.
     * @param currentEpoch The current epoch in the Symbiotic Vault.
     *
     * @custom:requirements
     * - If `claimEpoch` is zero, no claims are made for `epochEpoch` - 1.
     * - If `claimEpoch` is non-zero, collateral is claimed for both `claimEpoch` and `claimEpoch`-1.
     * - Ensures that both `claimEpoch` and `claimEpoch`-1 are claimable.
     *
     * @custom:effects
     * - Emits `EpochClaimed` event when claims are successfully made.
     */
    function _handlePendingEpochs(AccountData storage accountData, uint256 currentEpoch) private {
        uint256 epoch = accountData.claimEpoch;
        if (epoch > 0) {
            _handlePendingEpoch(accountData, epoch - 1, currentEpoch);
        }
        _handlePendingEpoch(accountData, epoch, currentEpoch);
    }

    /**
     * @notice Claims collateral from the Symbiotic Vault for the given `epoch`.
     * @dev This function updates the `epochData` and `accountData` mappings.
     * @param accountData The storage struct containing specific claim data for the account.
     * @param epoch The epoch number from which the claim is made.
     * @param currentEpoch The current epoch in the Symbiotic Vault.
     *
     * @custom:requirements
     * - If `epoch` is not claimable, no claims are made.
     *
     * @custom:effects
     * - Emits `EpochClaimed` event when claims are successfully made.
     */
    function _handlePendingEpoch(
        AccountData storage accountData,
        uint256 epoch,
        uint256 currentEpoch
    ) private {
        if (!_isClaimableInSymbiotic(epoch, currentEpoch)) {
            return;
        }
        uint256 shares = accountData.sharesToClaim[epoch];
        if (shares == 0) {
            return;
        }
        _pullFromSymbioticForEpoch(epoch);

        EpochData storage epochData = _epochData[epoch];
        uint256 assets = shares.mulDiv(epochData.claimableAssets, epochData.sharesToClaim);

        epochData.sharesToClaim -= shares;
        epochData.claimableAssets -= assets;

        accountData.claimableAssets += assets;
        delete accountData.sharesToClaim[epoch];
    }

    /**
     * @notice Claims collateral from the Symbiotic Vault for the given `epoch`.
     * @dev This function ensures that the withdrawals for the specified `epoch` are not claimed from the Symbiotic Vault.
     * @param epoch The epoch number for which the collateral is being claimed.
     *
     * @custom:requirements
     * - Checks that the collateral for the given `epoch` has not already been claimed.
     * - Checks whether there are any claimable withdrawals for the given `epoch`.
     *
     * @custom:effects
     * - Emits `EpochClaimed` event when the claim is successful.
     */
    function _pullFromSymbioticForEpoch(uint256 epoch) private {
        EpochData storage epochData = _epochData[epoch];
        if (epochData.isClaimed) {
            return;
        }
        epochData.isClaimed = true;
        address this_ = address(this);
        ISymbioticVault symbioticVault_ = symbioticVault;
        if (symbioticVault_.isWithdrawalsClaimed(epoch, this_)) {
            return;
        }
        if (symbioticVault_.withdrawalsOf(epoch, this_) == 0) {
            return;
        }
        uint256 claimedAssets = symbioticVault_.claim(this_, epoch);
        epochData.claimableAssets = claimedAssets;
        emit EpochClaimed(epoch, claimedAssets);
    }

    /**
     * @notice Returns the claimable collateral amount for a given `accountData` at a specific `epoch`.
     * @param accountData The storage struct containing specific claim data for the account.
     * @param epoch The epoch number at which the claim is being checked.
     * @return The amount of claimable collateral corresponding to the given account's shares at the specified epoch.
     */
    function _claimable(AccountData storage accountData, uint256 epoch)
        private
        view
        returns (uint256)
    {
        uint256 shares = accountData.sharesToClaim[epoch];
        if (shares == 0) {
            return 0;
        }
        EpochData storage epochData = _epochData[epoch];
        if (epochData.isClaimed) {
            return shares.mulDiv(epochData.claimableAssets, epochData.sharesToClaim);
        }
        return _withdrawalsOf(epoch, shares);
    }

    /**
     * @notice Determines whether the given `epoch` is claimable based on the current epoch.
     * @param epoch The epoch number to check.
     * @param currentEpoch The current epoch in the Symbiotic Vault.
     * @return True if the epoch is claimable (i.e., it is less than the current epoch), false otherwise.
     */
    function _isClaimableInSymbiotic(uint256 epoch, uint256 currentEpoch)
        private
        pure
        returns (bool)
    {
        return epoch < currentEpoch;
    }
}
IRatiosStrategy.sol 50 lines
// // SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {IDefaultCollateral, IERC20} from "../tokens/IDefaultCollateral.sol";
import {
    IMultiVault,
    IMultiVaultStorage,
    IProtocolAdapter,
    IWithdrawalQueue
} from "../vaults/IMultiVault.sol";
import {IDepositStrategy} from "./IDepositStrategy.sol";
import {IRebalanceStrategy} from "./IRebalanceStrategy.sol";
import {IWithdrawalStrategy} from "./IWithdrawalStrategy.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

interface IRatiosStrategy is IDepositStrategy, IWithdrawalStrategy, IRebalanceStrategy {
    struct Ratio {
        uint64 minRatioD18;
        uint64 maxRatioD18;
    }

    struct Amounts {
        uint256 min;
        uint256 max;
        uint256 claimable;
        uint256 pending;
        uint256 staked;
    }

    function D18() external view returns (uint256);

    function RATIOS_STRATEGY_SET_RATIOS_ROLE() external view returns (bytes32);

    function ratios(address vault, address subvault)
        external
        view
        returns (uint64 minRatioD18, uint64 maxRatioD18);

    function setRatios(address vault, address[] calldata subvaults, Ratio[] calldata ratios)
        external;

    function calculateState(address vault, bool isDeposit, uint256 increment)
        external
        view
        returns (Amounts[] memory state, uint256 liquid);

    event RatiosSet(address indexed vault, address[] subvaults, Ratio[] ratios);
}
IMultiVault.sol 61 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "./IMultiVaultStorage.sol";

interface IMultiVault is IMultiVaultStorage {
    struct InitParams {
        address admin;
        uint256 limit;
        bool depositPause;
        bool withdrawalPause;
        bool depositWhitelist;
        address asset;
        string name;
        string symbol;
        address depositStrategy;
        address withdrawalStrategy;
        address rebalanceStrategy;
        address defaultCollateral;
        address symbioticAdapter;
        address eigenLayerAdapter;
        address erc4626Adapter;
    }

    function initialize(InitParams calldata initParams) external;

    function rebalance() external;

    function addSubvault(address vault, Protocol protocol) external;

    function removeSubvault(address subvault) external;

    function setDepositStrategy(address newDepositStrategy) external;

    function setWithdrawalStrategy(address newWithdrawalStrategy) external;

    function setRebalanceStrategy(address newRebalanceStrategy) external;

    function setRewardsData(uint256 farmId, RewardData calldata rewardData) external;

    function setDefaultCollateral(address defaultCollateral_) external;

    function setSymbioticAdapter(address adapter_) external;

    function setEigenLayerAdapter(address adapter_) external;

    function setERC4626Adapter(address adapter_) external;

    function pushRewards(uint256 farmId, bytes calldata data) external;

    event Rebalance(IRebalanceStrategy.RebalanceData[] data, uint256 timestamp);

    event DepositIntoCollateral(uint256 assets);

    event RewardsPushed(
        uint256 indexed farmId,
        uint256 indexed rewardAmount,
        uint256 indexed curatorFee,
        uint256 timestamp
    );
}
ERC4626Vault.sol 161 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/vaults/IERC4626Vault.sol";
import {VaultControl} from "./VaultControl.sol";

abstract contract ERC4626Vault is VaultControl, ERC4626Upgradeable, IERC4626Vault {
    bytes32[16] private _reserved; // Reserved storage space for backward compatibility.

    /**
     * @notice Initializes the ERC4626 vault with the provided settings, including admin, limits, pause states, and token details.
     * @param _admin The address of the admin to be granted control over the vault.
     * @param _limit The initial deposit limit for the vault.
     * @param _depositPause The initial state of the `depositPause` flag.
     * @param _withdrawalPause The initial state of the `withdrawalPause` flag.
     * @param _depositWhitelist The initial state of the `depositWhitelist` flag.
     * @param _asset The address of the underlying ERC20 asset for the ERC4626 vault.
     * @param _name The name of the ERC20 token representing shares of the vault.
     * @param _symbol The symbol of the ERC20 token representing shares of the vault.
     *
     * @custom:effects
     * - Initializes the vault control settings, including admin, limits, and pause states, via `__initializeVaultControl`.
     * - Initializes the ERC20 token properties with the provided `_name` and `_symbol`.
     * - Initializes the ERC4626 vault with the provided underlying asset (`_asset`).
     *
     * @dev This function is protected by the `onlyInitializing` modifier, ensuring it is only called during the initialization phase of the contract.
     */
    function __initializeERC4626(
        address _admin,
        uint256 _limit,
        bool _depositPause,
        bool _withdrawalPause,
        bool _depositWhitelist,
        address _asset,
        string memory _name,
        string memory _symbol
    ) internal onlyInitializing {
        __initializeVaultControl(_admin, _limit, _depositPause, _withdrawalPause, _depositWhitelist);
        __ERC20_init(_name, _symbol);
        __ERC4626_init(IERC20(_asset));
    }

    /// @inheritdoc IERC4626
    function maxMint(address account)
        public
        view
        virtual
        override(ERC4626Upgradeable, IERC4626)
        returns (uint256)
    {
        uint256 assets = maxDeposit(account);
        if (assets == type(uint256).max) {
            return type(uint256).max;
        }
        return convertToShares(assets);
    }

    /// @inheritdoc IERC4626
    function maxDeposit(address account)
        public
        view
        virtual
        override(ERC4626Upgradeable, IERC4626)
        returns (uint256)
    {
        if (depositPause()) {
            return 0;
        }
        if (depositWhitelist() && !isDepositorWhitelisted(account)) {
            return 0;
        }
        uint256 limit_ = limit();
        if (limit_ == type(uint256).max) {
            return type(uint256).max;
        }
        uint256 assets_ = totalAssets();
        return limit_ >= assets_ ? limit_ - assets_ : 0;
    }

    /// @inheritdoc IERC4626
    function maxWithdraw(address account)
        public
        view
        virtual
        override(ERC4626Upgradeable, IERC4626)
        returns (uint256)
    {
        if (withdrawalPause()) {
            return 0;
        }
        return super.maxWithdraw(account);
    }

    /// @inheritdoc IERC4626
    function maxRedeem(address account)
        public
        view
        virtual
        override(ERC4626Upgradeable, IERC4626)
        returns (uint256)
    {
        if (withdrawalPause()) {
            return 0;
        }
        return super.maxRedeem(account);
    }

    /// @inheritdoc IERC4626Vault
    function deposit(uint256 assets, address receiver, address referral)
        public
        virtual
        returns (uint256 shares)
    {
        shares = deposit(assets, receiver);
        emit ReferralDeposit(assets, receiver, referral);
    }

    /// @inheritdoc IERC4626
    function deposit(uint256 assets, address receiver)
        public
        virtual
        override(ERC4626Upgradeable, IERC4626)
        nonReentrant
        returns (uint256)
    {
        return super.deposit(assets, receiver);
    }

    /// @inheritdoc IERC4626
    function mint(uint256 shares, address receiver)
        public
        virtual
        override(ERC4626Upgradeable, IERC4626)
        nonReentrant
        returns (uint256)
    {
        return super.mint(shares, receiver);
    }

    /// @inheritdoc IERC4626
    function withdraw(uint256 assets, address receiver, address owner)
        public
        virtual
        override(ERC4626Upgradeable, IERC4626)
        nonReentrant
        returns (uint256)
    {
        return super.withdraw(assets, receiver, owner);
    }

    /// @inheritdoc IERC4626
    function redeem(uint256 shares, address receiver, address owner)
        public
        virtual
        override(ERC4626Upgradeable, IERC4626)
        nonReentrant
        returns (uint256)
    {
        return super.redeem(shares, receiver, owner);
    }
}
MultiVaultStorage.sol 215 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/vaults/IMultiVaultStorage.sol";

contract MultiVaultStorage is IMultiVaultStorage, Initializable {
    using EnumerableSet for EnumerableSet.UintSet;

    bytes32 private immutable storageSlotRef;

    constructor(bytes32 name_, uint256 version_) {
        storageSlotRef = keccak256(
            abi.encode(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            "mellow.simple-lrt.storage.MultiVaultStorage", name_, version_
                        )
                    )
                ) - 1
            )
        ) & ~bytes32(uint256(0xff));
    }

    function _multiStorage() private view returns (MultiStorage storage $) {
        bytes32 slot = storageSlotRef;
        assembly {
            $.slot := slot
        }
    }

    /// ------------------------------- EXTERNAL VIEW FUNCTIONS -------------------------------

    /// @inheritdoc IMultiVaultStorage
    function subvaultsCount() public view returns (uint256) {
        return _multiStorage().subvaults.length;
    }

    /// @inheritdoc IMultiVaultStorage
    function subvaultAt(uint256 index) public view returns (Subvault memory) {
        return _multiStorage().subvaults[index];
    }

    /// @inheritdoc IMultiVaultStorage
    function indexOfSubvault(address subvault) public view returns (uint256) {
        return _multiStorage().indexOfSubvault[subvault];
    }

    /// @inheritdoc IMultiVaultStorage
    function defaultCollateral() public view returns (IDefaultCollateral) {
        return IDefaultCollateral(_multiStorage().defaultCollateral);
    }

    /// @inheritdoc IMultiVaultStorage
    function depositStrategy() public view returns (IDepositStrategy) {
        return IDepositStrategy(_multiStorage().depositStrategy);
    }

    /// @inheritdoc IMultiVaultStorage
    function withdrawalStrategy() public view returns (IWithdrawalStrategy) {
        return IWithdrawalStrategy(_multiStorage().withdrawalStrategy);
    }

    /// @inheritdoc IMultiVaultStorage
    function rebalanceStrategy() public view returns (IRebalanceStrategy) {
        return IRebalanceStrategy(_multiStorage().rebalanceStrategy);
    }

    /// @inheritdoc IMultiVaultStorage
    function symbioticAdapter() public view returns (IProtocolAdapter) {
        return IProtocolAdapter(_multiStorage().symbioticAdapter);
    }

    /// @inheritdoc IMultiVaultStorage
    function eigenLayerAdapter() public view returns (IProtocolAdapter) {
        return IProtocolAdapter(_multiStorage().eigenLayerAdapter);
    }

    /// @inheritdoc IMultiVaultStorage
    function erc4626Adapter() public view returns (IProtocolAdapter) {
        return IProtocolAdapter(_multiStorage().erc4626Adapter);
    }

    /// @inheritdoc IMultiVaultStorage
    function rewardData(uint256 farmId) public view returns (RewardData memory) {
        return _multiStorage().rewardData[farmId];
    }

    /// @inheritdoc IMultiVaultStorage
    function farmIds() public view returns (uint256[] memory) {
        return _multiStorage().farmIds.values();
    }

    /// @inheritdoc IMultiVaultStorage
    function farmCount() public view returns (uint256) {
        return _multiStorage().farmIds.length();
    }

    /// @inheritdoc IMultiVaultStorage
    function farmIdAt(uint256 index) public view returns (uint256) {
        return _multiStorage().farmIds.at(index);
    }

    /// @inheritdoc IMultiVaultStorage
    function farmIdsContains(uint256 farmId) public view returns (bool) {
        return _multiStorage().farmIds.contains(farmId);
    }

    /// @inheritdoc IMultiVaultStorage
    function adapterOf(Protocol protocol) public view returns (IProtocolAdapter adapter) {
        if (protocol == Protocol.SYMBIOTIC) {
            adapter = symbioticAdapter();
        } else if (protocol == Protocol.EIGEN_LAYER) {
            adapter = eigenLayerAdapter();
        } else if (protocol == Protocol.ERC4626) {
            adapter = erc4626Adapter();
        }
        require(address(adapter) != address(0), "MultiVault: unsupported protocol");
    }

    /// ------------------------------- INTERNAL MUTATIVE FUNCTIONS -------------------------------

    function __initializeMultiVaultStorage(
        address depositStrategy_,
        address withdrawalStrategy_,
        address rebalanceStrategy_,
        address defaultCollateral_,
        address symbioticAdapter_,
        address eigenLayerAdapter_,
        address erc4626Adapter_
    ) internal onlyInitializing {
        _setDepositStrategy(depositStrategy_);
        _setWithdrawalStrategy(withdrawalStrategy_);
        _setRebalanceStrategy(rebalanceStrategy_);
        _setDefaultCollateral(defaultCollateral_);
        _setSymbioticAdapter(symbioticAdapter_);
        _setEigenLayerAdapter(eigenLayerAdapter_);
        _setERC4626Adapter(erc4626Adapter_);
    }

    function _setSymbioticAdapter(address symbioticAdapter_) internal {
        _multiStorage().symbioticAdapter = symbioticAdapter_;
        emit SymbioticAdapterSet(symbioticAdapter_);
    }

    function _setEigenLayerAdapter(address eigenLayerAdapter_) internal {
        _multiStorage().eigenLayerAdapter = eigenLayerAdapter_;
        emit EigenLayerAdapterSet(eigenLayerAdapter_);
    }

    function _setERC4626Adapter(address erc4626Adapter_) internal {
        _multiStorage().erc4626Adapter = erc4626Adapter_;
        emit ERC4626AdapterSet(erc4626Adapter_);
    }

    function _setDepositStrategy(address newDepositStrategy) internal {
        _multiStorage().depositStrategy = newDepositStrategy;
        emit DepositStrategySet(newDepositStrategy);
    }

    function _setWithdrawalStrategy(address newWithdrawalStrategy) internal {
        _multiStorage().withdrawalStrategy = newWithdrawalStrategy;
        emit WithdrawalStrategySet(newWithdrawalStrategy);
    }

    function _setRebalanceStrategy(address newRebalanceStrategy) internal {
        _multiStorage().rebalanceStrategy = newRebalanceStrategy;
        emit RebalanceStrategySet(newRebalanceStrategy);
    }

    function _setDefaultCollateral(address defaultCollateral_) internal {
        _multiStorage().defaultCollateral = defaultCollateral_;
        emit DefaultCollateralSet(defaultCollateral_);
    }

    function _addSubvault(address vault, address withdrawalQueue, Protocol protocol) internal {
        MultiStorage storage $ = _multiStorage();
        require($.indexOfSubvault[vault] == 0, "MultiVaultStorage: subvault already exists");
        $.subvaults.push(Subvault(protocol, vault, withdrawalQueue));
        uint256 index = $.subvaults.length;
        $.indexOfSubvault[vault] = index;
        emit SubvaultAdded(vault, withdrawalQueue, protocol, index - 1);
    }

    function _removeSubvault(address vault) internal {
        MultiStorage storage $ = _multiStorage();
        uint256 index = $.indexOfSubvault[vault];
        require(index != 0, "MultiVaultStorage: subvault not found");
        index--;
        uint256 last = $.subvaults.length - 1;
        emit SubvaultRemoved(vault, index);
        if (index < last) {
            Subvault memory lastSubvault = $.subvaults[last];
            $.subvaults[index] = lastSubvault;
            $.indexOfSubvault[lastSubvault.vault] = index + 1;
            emit SubvaultIndexChanged(lastSubvault.vault, last, index);
        }
        $.subvaults.pop();
        delete $.indexOfSubvault[vault];
    }

    function _setRewardData(uint256 farmId, RewardData memory data) internal {
        MultiStorage storage $ = _multiStorage();
        if (data.token == address(0)) {
            if ($.farmIds.remove(farmId)) {
                delete $.rewardData[farmId];
                emit RewardDataRemoved(farmId);
            }
        } else {
            $.rewardData[farmId] = data;
            $.farmIds.add(farmId);
            emit RewardDataSet(farmId, data);
        }
    }
}
VaultControlStorage.sol 162 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/vaults/IVaultControlStorage.sol";

abstract contract VaultControlStorage is IVaultControlStorage, Initializable, ContextUpgradeable {
    bytes32 private immutable storageSlotRef;

    constructor(bytes32 name_, uint256 version_) {
        storageSlotRef = keccak256(
            abi.encode(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            "mellow.simple-lrt.storage.VaultControlStorage", name_, version_
                        )
                    )
                ) - 1
            )
        ) & ~bytes32(uint256(0xff));
    }

    /**
     * @notice Initializes the Vault storage with the provided settings for limit, pause states, and whitelist.
     * @param _limit The initial value for the Vault's deposit limit.
     * @param _depositPause The initial state for the `depositPause` flag.
     * @param _withdrawalPause The initial state for the `withdrawalPause` flag.
     * @param _depositWhitelist The initial state for the `depositWhitelist` flag.
     *
     * @custom:requirements
     * - This function MUST not be called more than once; it is intended for one-time initialization.
     *
     * @custom:effects
     * - Sets the provided limit, pause states, and whitelist state in the Vault's storage.
     * - Emits the `LimitSet` event after the limit is set.
     * - Emits the `DepositPauseSet` event after the deposit pause state is set.
     * - Emits the `WithdrawalPauseSet` event after the withdrawal pause state is set.
     * - Emits the `DepositWhitelistSet` event after the deposit whitelist state is set.
     *
     * @dev This function is protected by the `onlyInitializing` modifier to ensure it is only called during the contract's initialization phase.
     */
    function __initializeVaultControlStorage(
        uint256 _limit,
        bool _depositPause,
        bool _withdrawalPause,
        bool _depositWhitelist
    ) internal onlyInitializing {
        _setLimit(_limit);
        _setDepositPause(_depositPause);
        _setWithdrawalPause(_withdrawalPause);
        _setDepositWhitelist(_depositWhitelist);
    }
    /// @inheritdoc IVaultControlStorage

    function depositPause() public view returns (bool) {
        return _vaultStorage().depositPause;
    }

    /// @inheritdoc IVaultControlStorage
    function withdrawalPause() public view returns (bool) {
        return _vaultStorage().withdrawalPause;
    }

    /// @inheritdoc IVaultControlStorage
    function limit() public view returns (uint256) {
        return _vaultStorage().limit;
    }

    /// @inheritdoc IVaultControlStorage
    function depositWhitelist() public view returns (bool) {
        return _vaultStorage().depositWhitelist;
    }

    /// @inheritdoc IVaultControlStorage
    function isDepositorWhitelisted(address account) public view returns (bool) {
        return _vaultStorage().isDepositorWhitelisted[account];
    }

    /**
     * @notice Sets a new `limit` for the Vault.
     * @param _limit The new limit for the Vault.
     *
     * @custom:effects
     * - Updates the Vault's `limit` in storage.
     * - Emits the `LimitSet` event with the new limit, current timestamp, and the caller's address.
     */
    function _setLimit(uint256 _limit) internal {
        Storage storage s = _vaultStorage();
        s.limit = _limit;
        emit LimitSet(_limit, block.timestamp, _msgSender());
    }

    /**
     * @notice Sets a new `depositPause` state for the Vault.
     * @param _paused The new value for the `depositPause` state.
     *
     * @custom:effects
     * - Updates the Vault's `depositPause` state in storage.
     * - Emits the `DepositPauseSet` event with the new pause state, current timestamp, and the caller's address.
     */
    function _setDepositPause(bool _paused) internal {
        Storage storage s = _vaultStorage();
        s.depositPause = _paused;
        emit DepositPauseSet(_paused, block.timestamp, _msgSender());
    }

    /**
     * @notice Sets a new `withdrawalPause` state for the Vault.
     * @param _paused The new value for the `withdrawalPause` state.
     *
     * @custom:effects
     * - Updates the Vault's `withdrawalPause` state in storage.
     * - Emits the `WithdrawalPauseSet` event with the new pause state, current timestamp, and the caller's address.
     */
    function _setWithdrawalPause(bool _paused) internal {
        Storage storage s = _vaultStorage();
        s.withdrawalPause = _paused;
        emit WithdrawalPauseSet(_paused, block.timestamp, _msgSender());
    }

    /**
     * @notice Sets a new `depositWhitelist` state for the Vault.
     * @param _status The new value for the `depositWhitelist` state.
     *
     * @custom:effects
     * - Updates the Vault's `depositWhitelist` state in storage.
     * - Emits the `DepositWhitelistSet` event with the new whitelist status, current timestamp, and the caller's address.
     */
    function _setDepositWhitelist(bool _status) internal {
        Storage storage s = _vaultStorage();
        s.depositWhitelist = _status;
        emit DepositWhitelistSet(_status, block.timestamp, _msgSender());
    }

    /**
     * @notice Sets a new whitelist `status` for the given `account`.
     * @param account The address of the account to update.
     * @param status The new whitelist status for the account.
     *
     * @custom:effects
     * - Updates the whitelist status of the `account` in storage.
     * - Emits the `DepositorWhitelistStatusSet` event with the account, new status, current timestamp, and the caller's address.
     */
    function _setDepositorWhitelistStatus(address account, bool status) internal {
        Storage storage s = _vaultStorage();
        s.isDepositorWhitelisted[account] = status;
        emit DepositorWhitelistStatusSet(account, status, block.timestamp, _msgSender());
    }

    /**
     * @notice Accesses the storage slot for the Vault's data.
     * @return $ A reference to the `Storage` struct for the Vault.
     *
     * @dev This function uses inline assembly to access a predefined storage slot.
     */
    function _vaultStorage() private view returns (Storage storage $) {
        bytes32 slot = storageSlotRef;
        assembly {
            $.slot := slot
        }
    }
}
ERC1967Utils.sol 193 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)

pragma solidity ^0.8.20;

import {IBeacon} from "../beacon/IBeacon.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 */
library ERC1967Utils {
    // We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
    // This will be fixed in Solidity 0.8.21. At that point we should remove these events.
    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Emitted when the beacon is changed.
     */
    event BeaconUpgraded(address indexed beacon);

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
     */
    // solhint-disable-next-line private-vars-leading-underscore
    bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev The `implementation` of the proxy is invalid.
     */
    error ERC1967InvalidImplementation(address implementation);

    /**
     * @dev The `admin` of the proxy is invalid.
     */
    error ERC1967InvalidAdmin(address admin);

    /**
     * @dev The `beacon` of the proxy is invalid.
     */
    error ERC1967InvalidBeacon(address beacon);

    /**
     * @dev An upgrade function sees `msg.value > 0` that may be lost.
     */
    error ERC1967NonPayable();

    /**
     * @dev Returns the current implementation address.
     */
    function getImplementation() internal view returns (address) {
        return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        if (newImplementation.code.length == 0) {
            revert ERC1967InvalidImplementation(newImplementation);
        }
        StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Performs implementation upgrade with additional setup call if data is nonempty.
     * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
     * to avoid stuck value in the contract.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);

        if (data.length > 0) {
            Address.functionDelegateCall(newImplementation, data);
        } else {
            _checkNonPayable();
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
     */
    // solhint-disable-next-line private-vars-leading-underscore
    bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Returns the current admin.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
     * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
     */
    function getAdmin() internal view returns (address) {
        return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        if (newAdmin == address(0)) {
            revert ERC1967InvalidAdmin(address(0));
        }
        StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {IERC1967-AdminChanged} event.
     */
    function changeAdmin(address newAdmin) internal {
        emit AdminChanged(getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
     */
    // solhint-disable-next-line private-vars-leading-underscore
    bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Returns the current beacon.
     */
    function getBeacon() internal view returns (address) {
        return StorageSlot.getAddressSlot(BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        if (newBeacon.code.length == 0) {
            revert ERC1967InvalidBeacon(newBeacon);
        }

        StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;

        address beaconImplementation = IBeacon(newBeacon).implementation();
        if (beaconImplementation.code.length == 0) {
            revert ERC1967InvalidImplementation(beaconImplementation);
        }
    }

    /**
     * @dev Change the beacon and trigger a setup call if data is nonempty.
     * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
     * to avoid stuck value in the contract.
     *
     * Emits an {IERC1967-BeaconUpgraded} event.
     *
     * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
     * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
     * efficiency.
     */
    function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);

        if (data.length > 0) {
            Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
        } else {
            _checkNonPayable();
        }
    }

    /**
     * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
     * if an upgrade doesn't perform an initialization call.
     */
    function _checkNonPayable() private {
        if (msg.value > 0) {
            revert ERC1967NonPayable();
        }
    }
}
ERC1967Proxy.sol 40 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol)

pragma solidity ^0.8.20;

import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "./ERC1967Utils.sol";

/**
 * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
 * implementation address that can be changed. This address is stored in storage in the location specified by
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
 * implementation behind the proxy.
 */
contract ERC1967Proxy is Proxy {
    /**
     * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
     *
     * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
     * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
     *
     * Requirements:
     *
     * - If `data` is empty, `msg.value` must be zero.
     */
    constructor(address implementation, bytes memory _data) payable {
        ERC1967Utils.upgradeToAndCall(implementation, _data);
    }

    /**
     * @dev Returns the current implementation address.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
     * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
     */
    function _implementation() internal view virtual override returns (address) {
        return ERC1967Utils.getImplementation();
    }
}
IERC1967.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)

pragma solidity ^0.8.20;

/**
 * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
 */
interface IERC1967 {
    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Emitted when the beacon is changed.
     */
    event BeaconUpgraded(address indexed beacon);
}
ProxyAdmin.sol 45 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol)

pragma solidity ^0.8.20;

import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol";
import {Ownable} from "../../access/Ownable.sol";

/**
 * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
 * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
 */
contract ProxyAdmin is Ownable {
    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)`
     * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev Sets the initial owner who can perform upgrades.
     */
    constructor(address initialOwner) Ownable(initialOwner) {}

    /**
     * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
     * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
     *
     * Requirements:
     *
     * - This contract must be the admin of `proxy`.
     * - If `data` is empty, `msg.value` must be zero.
     */
    function upgradeAndCall(
        ITransparentUpgradeableProxy proxy,
        address implementation,
        bytes memory data
    ) public payable virtual onlyOwner {
        proxy.upgradeToAndCall{value: msg.value}(implementation, data);
    }
}
ISymbioticWithdrawalQueue.sol 162 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {IWithdrawalQueue} from "./IWithdrawalQueue.sol";

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IVault as ISymbioticVault} from "@symbiotic/core/interfaces/vault/IVault.sol";

/**
 * @title ISymbioticWithdrawalQueue
 * @notice Interface to handle the withdrawal process from the Symbiotic Vault.
 * @dev This interface is an extension of IWithdrawalQueue for interacting specifically with the Symbiotic Vault.
 */
interface ISymbioticWithdrawalQueue is IWithdrawalQueue {
    /**
     * @notice Struct to hold epoch-related data.
     * @param isClaimed Indicates whether the epoch has been claimed.
     * @param sharesToClaim The amount of shares to be claimed.
     * @param claimableAssets The amount of assets that can be claimed.
     */
    struct EpochData {
        bool isClaimed;
        uint256 sharesToClaim;
        uint256 claimableAssets;
    }

    /**
     * @notice Struct to store account-related data for withdrawals.
     * @param sharesToClaim A mapping of epochs to shares to be claimed for a given epoch.
     * @param claimableAssets The total amount of assets that can be claimed.
     * @param claimEpoch The most recent epoch requested for withdrawal.
     */
    struct AccountData {
        mapping(uint256 => uint256) sharesToClaim;
        uint256 claimableAssets;
        uint256 claimEpoch;
    }

    /**
     * @notice Returns the address of the associated MellowSymbioticVault.
     * @return vault The address of the MellowSymbioticVault.
     */
    function vault() external view returns (address);

    /**
     * @notice Returns the address of the associated Symbiotic Vault.
     * @return symbioticVault The address of the Symbiotic Vault.
     */
    function symbioticVault() external view returns (ISymbioticVault);

    /**
     * @notice Returns the address of the collateral token used by the vault.
     * @dev The collateral token is the same as the vault's asset.
     * @return collateralAddress The address of the collateral token.
     */
    function collateral() external view returns (address);

    /**
     * @notice Returns the current epoch of the Symbiotic Vault.
     * @return currentEpoch The current epoch of the Symbiotic Vault.
     */
    function getCurrentEpoch() external view returns (uint256);

    /**
     * @notice Returns the data for a specific account.
     * @param account The address of the account to retrieve data for.
     * @return sharesToClaimPrev The amount of shares to claim for the epoch before the last requested epoch.
     * @return sharesToClaim The amount of shares to claim for the last requested epoch.
     * @return claimableAssets The total amount of assets that can be claimed.
     * @return claimEpoch The most recent epoch requested for withdrawal.
     */
    function getAccountData(address account)
        external
        view
        returns (
            uint256 sharesToClaimPrev,
            uint256 sharesToClaim,
            uint256 claimableAssets,
            uint256 claimEpoch
        );

    /**
     * @notice Returns the data for a specific epoch.
     * @param epoch The epoch number to retrieve data for.
     * @return epochData The data for the specified epoch.
     */
    function getEpochData(uint256 epoch) external view returns (EpochData memory);

    /**
     * @notice Returns the total amount of pending assets awaiting withdrawal.
     * @dev This amount may decrease due to slashing events in the Symbiotic Vault.
     * @return assets The total amount of assets pending withdrawal.
     */
    function pendingAssets() external view returns (uint256);

    /**
     * @notice Returns the amount of assets in the withdrawal queue for a specific account that cannot be claimed yet.
     * @param account The address of the account.
     * @return assets The amount of pending assets in the withdrawal queue for the account.
     */
    function pendingAssetsOf(address account) external view returns (uint256 assets);

    /**
     * @notice Returns the amount of assets that can be claimed by an account.
     * @param account The address of the account.
     * @return assets The amount of assets claimable by the account.
     */
    function claimableAssetsOf(address account) external view returns (uint256 assets);

    /**
     * @notice Requests the withdrawal of a specified amount of collateral for a given account.
     * @param account The address of the account requesting the withdrawal.
     * @param amount The amount of collateral to withdraw.
     *
     * @custom:requirements
     * - `msg.sender` MUST be the vault.
     * - `amount` MUST be greater than zero.
     *
     * @custom:effects
     * - Emits a `WithdrawalRequested` event.
     */
    function request(address account, uint256 amount) external;

    /**
     * @notice Claims assets from the Symbiotic Vault for a specified epoch to the Withdrawal Queue address.
     * @param epoch The epoch number.
     * @dev Emits an EpochClaimed event.
     */
    function pull(uint256 epoch) external;

    /**
     * @notice Finalizes the withdrawal process for a specific account and transfers assets to the recipient.
     * @param account The address of the account requesting the withdrawal.
     * @param recipient The address of the recipient receiving the withdrawn assets.
     * @param maxAmount The maximum amount of assets to withdraw.
     * @return amount The actual amount of assets withdrawn.
     */
    function claim(address account, address recipient, uint256 maxAmount)
        external
        returns (uint256 amount);

    /**
     * @notice Handles the pending epochs for a specific account and makes assets claimable for recent epochs.
     * @param account The address of the account.
     * @dev Emits an EpochClaimed event.
     */
    function handlePendingEpochs(address account) external;

    /// @notice Emitted when a withdrawal request is created.
    event WithdrawalRequested(address indexed account, uint256 indexed epoch, uint256 amount);

    /// @notice Emitted when assets are successfully claimed for a specific epoch.
    event EpochClaimed(uint256 indexed epoch, uint256 claimedAssets);

    /// @notice Emitted when assets are successfully withdrawn and transferred to a recipient.
    event Claimed(address indexed account, address indexed recipient, uint256 amount);

    /// @notice Emitted when pending assets are successfully transferred from one account to another.
    event Transfer(address indexed from, address indexed to, uint256 indexed epoch, uint256 amount);
}
IProtocolAdapter.sol 31 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IProtocolAdapter {
    function vault() external view returns (address);

    function maxDeposit(address subvault) external view returns (uint256);

    function stakedAt(address subvault) external view returns (uint256);

    function assetOf(address subvault) external view returns (address);

    function validateRewardData(bytes calldata data) external view;

    function pushRewards(address rewardToken, bytes calldata farmData, bytes memory rewardData)
        external;

    function withdraw(
        address subvault,
        address withdrawalQueue,
        address receiver,
        uint256 request,
        address owner
    ) external;

    function deposit(address subvault, uint256 assets) external;

    function handleVault(address subvault) external returns (address withdrawalQueue);

    function areWithdrawalsPaused(address subvault, address account) external view returns (bool);
}
IERC4626.sol 230 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface of the ERC4626 "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 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);
}
SafeERC20.sol 118 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 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 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 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 Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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.
     */
    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.
     */
    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 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).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            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 silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
IRegistry.sol 36 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IRegistry {
    error EntityNotExist();

    /**
     * @notice Emitted when an entity is added.
     * @param entity address of the added entity
     */
    event AddEntity(address indexed entity);

    /**
     * @notice Get if a given address is an entity.
     * @param account address to check
     * @return if the given address is an entity
     */
    function isEntity(
        address account
    ) external view returns (bool);

    /**
     * @notice Get a total number of entities.
     * @return total number of entities added
     */
    function totalEntities() external view returns (uint256);

    /**
     * @notice Get an entity given its index.
     * @param index index of the entity to get
     * @return address of the entity
     */
    function entity(
        uint256 index
    ) external view returns (address);
}
IVault.sol 319 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IMigratableEntity} from "../common/IMigratableEntity.sol";
import {IVaultStorage} from "./IVaultStorage.sol";

interface IVault is IMigratableEntity, IVaultStorage {
    error AlreadyClaimed();
    error AlreadySet();
    error DelegatorAlreadyInitialized();
    error DepositLimitReached();
    error InsufficientClaim();
    error InsufficientDeposit();
    error InsufficientRedemption();
    error InsufficientWithdrawal();
    error InvalidAccount();
    error InvalidCaptureEpoch();
    error InvalidClaimer();
    error InvalidCollateral();
    error InvalidDelegator();
    error InvalidEpoch();
    error InvalidEpochDuration();
    error InvalidLengthEpochs();
    error InvalidOnBehalfOf();
    error InvalidRecipient();
    error InvalidSlasher();
    error MissingRoles();
    error NotDelegator();
    error NotSlasher();
    error NotWhitelistedDepositor();
    error SlasherAlreadyInitialized();
    error TooMuchRedeem();
    error TooMuchWithdraw();

    /**
     * @notice Initial parameters needed for a vault deployment.
     * @param collateral vault's underlying collateral
     * @param burner vault's burner to issue debt to (e.g., 0xdEaD or some unwrapper contract)
     * @param epochDuration duration of the vault epoch (it determines sync points for withdrawals)
     * @param depositWhitelist if enabling deposit whitelist
     * @param isDepositLimit if enabling deposit limit
     * @param depositLimit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
     * @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder
     * @param depositWhitelistSetRoleHolder address of the initial DEPOSIT_WHITELIST_SET_ROLE holder
     * @param depositorWhitelistRoleHolder address of the initial DEPOSITOR_WHITELIST_ROLE holder
     * @param isDepositLimitSetRoleHolder address of the initial IS_DEPOSIT_LIMIT_SET_ROLE holder
     * @param depositLimitSetRoleHolder address of the initial DEPOSIT_LIMIT_SET_ROLE holder
     */
    struct InitParams {
        address collateral;
        address burner;
        uint48 epochDuration;
        bool depositWhitelist;
        bool isDepositLimit;
        uint256 depositLimit;
        address defaultAdminRoleHolder;
        address depositWhitelistSetRoleHolder;
        address depositorWhitelistRoleHolder;
        address isDepositLimitSetRoleHolder;
        address depositLimitSetRoleHolder;
    }

    /**
     * @notice Hints for an active balance.
     * @param activeSharesOfHint hint for the active shares of checkpoint
     * @param activeStakeHint hint for the active stake checkpoint
     * @param activeSharesHint hint for the active shares checkpoint
     */
    struct ActiveBalanceOfHints {
        bytes activeSharesOfHint;
        bytes activeStakeHint;
        bytes activeSharesHint;
    }

    /**
     * @notice Emitted when a deposit is made.
     * @param depositor account that made the deposit
     * @param onBehalfOf account the deposit was made on behalf of
     * @param amount amount of the collateral deposited
     * @param shares amount of the active shares minted
     */
    event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount, uint256 shares);

    /**
     * @notice Emitted when a withdrawal is made.
     * @param withdrawer account that made the withdrawal
     * @param claimer account that needs to claim the withdrawal
     * @param amount amount of the collateral withdrawn
     * @param burnedShares amount of the active shares burned
     * @param mintedShares amount of the epoch withdrawal shares minted
     */
    event Withdraw(
        address indexed withdrawer, address indexed claimer, uint256 amount, uint256 burnedShares, uint256 mintedShares
    );

    /**
     * @notice Emitted when a claim is made.
     * @param claimer account that claimed
     * @param recipient account that received the collateral
     * @param epoch epoch the collateral was claimed for
     * @param amount amount of the collateral claimed
     */
    event Claim(address indexed claimer, address indexed recipient, uint256 epoch, uint256 amount);

    /**
     * @notice Emitted when a batch claim is made.
     * @param claimer account that claimed
     * @param recipient account that received the collateral
     * @param epochs epochs the collateral was claimed for
     * @param amount amount of the collateral claimed
     */
    event ClaimBatch(address indexed claimer, address indexed recipient, uint256[] epochs, uint256 amount);

    /**
     * @notice Emitted when a slash happens.
     * @param amount amount of the collateral to slash
     * @param captureTimestamp time point when the stake was captured
     * @param slashedAmount real amount of the collateral slashed
     */
    event OnSlash(uint256 amount, uint48 captureTimestamp, uint256 slashedAmount);

    /**
     * @notice Emitted when a deposit whitelist status is enabled/disabled.
     * @param status if enabled deposit whitelist
     */
    event SetDepositWhitelist(bool status);

    /**
     * @notice Emitted when a depositor whitelist status is set.
     * @param account account for which the whitelist status is set
     * @param status if whitelisted the account
     */
    event SetDepositorWhitelistStatus(address indexed account, bool status);

    /**
     * @notice Emitted when a deposit limit status is enabled/disabled.
     * @param status if enabled deposit limit
     */
    event SetIsDepositLimit(bool status);

    /**
     * @notice Emitted when a deposit limit is set.
     * @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
     */
    event SetDepositLimit(uint256 limit);

    /**
     * @notice Emitted when a delegator is set.
     * @param delegator vault's delegator to delegate the stake to networks and operators
     * @dev Can be set only once.
     */
    event SetDelegator(address indexed delegator);

    /**
     * @notice Emitted when a slasher is set.
     * @param slasher vault's slasher to provide a slashing mechanism to networks
     * @dev Can be set only once.
     */
    event SetSlasher(address indexed slasher);

    /**
     * @notice Check if the vault is fully initialized (a delegator and a slasher are set).
     * @return if the vault is fully initialized
     */
    function isInitialized() external view returns (bool);

    /**
     * @notice Get a total amount of the collateral that can be slashed.
     * @return total amount of the slashable collateral
     */
    function totalStake() external view returns (uint256);

    /**
     * @notice Get an active balance for a particular account at a given timestamp using hints.
     * @param account account to get the active balance for
     * @param timestamp time point to get the active balance for the account at
     * @param hints hints for checkpoints' indexes
     * @return active balance for the account at the timestamp
     */
    function activeBalanceOfAt(
        address account,
        uint48 timestamp,
        bytes calldata hints
    ) external view returns (uint256);

    /**
     * @notice Get an active balance for a particular account.
     * @param account account to get the active balance for
     * @return active balance for the account
     */
    function activeBalanceOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Get withdrawals for a particular account at a given epoch (zero if claimed).
     * @param epoch epoch to get the withdrawals for the account at
     * @param account account to get the withdrawals for
     * @return withdrawals for the account at the epoch
     */
    function withdrawalsOf(uint256 epoch, address account) external view returns (uint256);

    /**
     * @notice Get a total amount of the collateral that can be slashed for a given account.
     * @param account account to get the slashable collateral for
     * @return total amount of the account's slashable collateral
     */
    function slashableBalanceOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Deposit collateral into the vault.
     * @param onBehalfOf account the deposit is made on behalf of
     * @param amount amount of the collateral to deposit
     * @return depositedAmount real amount of the collateral deposited
     * @return mintedShares amount of the active shares minted
     */
    function deposit(
        address onBehalfOf,
        uint256 amount
    ) external returns (uint256 depositedAmount, uint256 mintedShares);

    /**
     * @notice Withdraw collateral from the vault (it will be claimable after the next epoch).
     * @param claimer account that needs to claim the withdrawal
     * @param amount amount of the collateral to withdraw
     * @return burnedShares amount of the active shares burned
     * @return mintedShares amount of the epoch withdrawal shares minted
     */
    function withdraw(address claimer, uint256 amount) external returns (uint256 burnedShares, uint256 mintedShares);

    /**
     * @notice Redeem collateral from the vault (it will be claimable after the next epoch).
     * @param claimer account that needs to claim the withdrawal
     * @param shares amount of the active shares to redeem
     * @return withdrawnAssets amount of the collateral withdrawn
     * @return mintedShares amount of the epoch withdrawal shares minted
     */
    function redeem(address claimer, uint256 shares) external returns (uint256 withdrawnAssets, uint256 mintedShares);

    /**
     * @notice Claim collateral from the vault.
     * @param recipient account that receives the collateral
     * @param epoch epoch to claim the collateral for
     * @return amount amount of the collateral claimed
     */
    function claim(address recipient, uint256 epoch) external returns (uint256 amount);

    /**
     * @notice Claim collateral from the vault for multiple epochs.
     * @param recipient account that receives the collateral
     * @param epochs epochs to claim the collateral for
     * @return amount amount of the collateral claimed
     */
    function claimBatch(address recipient, uint256[] calldata epochs) external returns (uint256 amount);

    /**
     * @notice Slash callback for burning collateral.
     * @param amount amount to slash
     * @param captureTimestamp time point when the stake was captured
     * @return slashedAmount real amount of the collateral slashed
     * @dev Only the slasher can call this function.
     */
    function onSlash(uint256 amount, uint48 captureTimestamp) external returns (uint256 slashedAmount);

    /**
     * @notice Enable/disable deposit whitelist.
     * @param status if enabling deposit whitelist
     * @dev Only a DEPOSIT_WHITELIST_SET_ROLE holder can call this function.
     */
    function setDepositWhitelist(
        bool status
    ) external;

    /**
     * @notice Set a depositor whitelist status.
     * @param account account for which the whitelist status is set
     * @param status if whitelisting the account
     * @dev Only a DEPOSITOR_WHITELIST_ROLE holder can call this function.
     */
    function setDepositorWhitelistStatus(address account, bool status) external;

    /**
     * @notice Enable/disable deposit limit.
     * @param status if enabling deposit limit
     * @dev Only a IS_DEPOSIT_LIMIT_SET_ROLE holder can call this function.
     */
    function setIsDepositLimit(
        bool status
    ) external;

    /**
     * @notice Set a deposit limit.
     * @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously)
     * @dev Only a DEPOSIT_LIMIT_SET_ROLE holder can call this function.
     */
    function setDepositLimit(
        uint256 limit
    ) external;

    /**
     * @notice Set a delegator.
     * @param delegator vault's delegator to delegate the stake to networks and operators
     * @dev Can be set only once.
     */
    function setDelegator(
        address delegator
    ) external;

    /**
     * @notice Set a slasher.
     * @param slasher vault's slasher to provide a slashing mechanism to networks
     * @dev Can be set only once.
     */
    function setSlasher(
        address slasher
    ) external;
}
IStakerRewards.sol 46 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IStakerRewards {
    /**
     * @notice Emitted when a reward is distributed.
     * @param network network on behalf of which the reward is distributed
     * @param token address of the token
     * @param amount amount of tokens
     * @param data some used data
     */
    event DistributeRewards(address indexed network, address indexed token, uint256 amount, bytes data);

    /**
     * @notice Get a version of the staker rewards contract (different versions mean different interfaces).
     * @return version of the staker rewards contract
     * @dev Must return 1 for this one.
     */
    function version() external view returns (uint64);

    /**
     * @notice Get an amount of rewards claimable by a particular account of a given token.
     * @param token address of the token
     * @param account address of the claimer
     * @param data some data to use
     * @return amount of claimable tokens
     */
    function claimable(address token, address account, bytes calldata data) external view returns (uint256);

    /**
     * @notice Distribute rewards on behalf of a particular network using a given token.
     * @param network network on behalf of which the reward to distribute
     * @param token address of the token
     * @param amount amount of tokens
     * @param data some data to use
     */
    function distributeRewards(address network, address token, uint256 amount, bytes calldata data) external;

    /**
     * @notice Claim rewards using a given token.
     * @param recipient address of the tokens' recipient
     * @param token address of the token
     * @param data some data to use
     */
    function claimRewards(address recipient, address token, bytes calldata data) external;
}
IDefaultCollateral.sol 185 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IDefaultCollateral is IERC20 {
    /**
     * @notice Emitted when debt is issued.
     * @param issuer address of the debt's issuer
     * @param recipient address that should receive the underlying asset
     * @param debtIssued amount of the debt issued
     */
    event IssueDebt(address indexed issuer, address indexed recipient, uint256 debtIssued);

    /**
     * @notice Emitted when debt is repaid.
     * @param issuer address of the debt's issuer
     * @param recipient address that received the underlying asset
     * @param debtRepaid amount of the debt repaid
     */
    event RepayDebt(address indexed issuer, address indexed recipient, uint256 debtRepaid);

    /**
     * @notice Get the collateral's underlying asset.
     * @return asset address of the underlying asset
     */
    function asset() external view returns (address);

    /**
     * @notice Get a total amount of repaid debt.
     * @return total repaid debt
     */
    function totalRepaidDebt() external view returns (uint256);

    /**
     * @notice Get an amount of repaid debt created by a particular issuer.
     * @param issuer address of the debt's issuer
     * @return particular issuer's repaid debt
     */
    function issuerRepaidDebt(address issuer) external view returns (uint256);

    /**
     * @notice Get an amount of repaid debt to a particular recipient.
     * @param recipient address that received the underlying asset
     * @return particular recipient's repaid debt
     */
    function recipientRepaidDebt(address recipient) external view returns (uint256);

    /**
     * @notice Get an amount of repaid debt for a particular issuer-recipient pair.
     * @param issuer address of the debt's issuer
     * @param recipient address that received the underlying asset
     * @return particular pair's repaid debt
     */
    function repaidDebt(address issuer, address recipient) external view returns (uint256);

    /**
     * @notice Get a total amount of debt.
     * @return total debt
     */
    function totalDebt() external view returns (uint256);

    /**
     * @notice Get a current debt created by a particular issuer.
     * @param issuer address of the debt's issuer
     * @return particular issuer's debt
     */
    function issuerDebt(address issuer) external view returns (uint256);

    /**
     * @notice Get a current debt to a particular recipient.
     * @param recipient address that should receive the underlying asset
     * @return particular recipient's debt
     */
    function recipientDebt(address recipient) external view returns (uint256);

    /**
     * @notice Get a current debt for a particular issuer-recipient pair.
     * @param issuer address of the debt's issuer
     * @param recipient address that should receive the underlying asset
     * @return particular pair's debt
     */
    function debt(address issuer, address recipient) external view returns (uint256);

    /**
     * @notice Burn a given amount of the collateral, and increase a debt of the underlying asset for the caller.
     * @param recipient address that should receive the underlying asset
     * @param amount amount of the collateral
     */
    function issueDebt(address recipient, uint256 amount) external;

    error NotLimitIncreaser();
    error InsufficientDeposit();
    error ExceedsLimit();
    error InsufficientWithdraw();
    error InsufficientIssueDebt();

    /**
     * @notice Emmited when deposit happens.
     * @param depositor address of the depositor
     * @param recipient address of the collateral's recipient
     * @param amount amount of the collateral minted
     */
    event Deposit(address indexed depositor, address indexed recipient, uint256 amount);

    /**
     * @notice Emmited when withdrawal happens.
     * @param withdrawer address of the withdrawer
     * @param recipient address of the underlying asset's recipient
     * @param amount amount of the collateral burned
     */
    event Withdraw(address indexed withdrawer, address indexed recipient, uint256 amount);

    /**
     * @notice Emmited when limit is increased.
     * @param amount amount to increase the limit by
     */
    event IncreaseLimit(uint256 amount);

    /**
     * @notice Emmited when new limit increaser is set.
     * @param limitIncreaser address of the new limit increaser
     */
    event SetLimitIncreaser(address indexed limitIncreaser);

    /**
     * @notice Get a maximum possible collateral total supply.
     * @return maximum collateral total supply
     */
    function limit() external view returns (uint256);

    /**
     * @notice Get an address of the limit increaser.
     * @return address of the limit increaser
     */
    function limitIncreaser() external view returns (address);

    /**
     * @notice Deposit a given amount of the underlying asset, and mint the collateral to a particular recipient.
     * @param recipient address of the collateral's recipient
     * @param amount amount of the underlying asset
     * @return amount of the collateral minted
     */
    function deposit(address recipient, uint256 amount) external returns (uint256);

    /**
     * @notice Deposit a given amount of the underlying asset using a permit functionality, and mint the collateral to a particular recipient.
     * @param recipient address of the collateral's recipient
     * @param amount amount of the underlying asset
     * @param deadline timestamp of the signature's deadline
     * @param v v component of the signature
     * @param r r component of the signature
     * @param s s component of the signature
     * @return amount of the collateral minted
     */
    function deposit(
        address recipient,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256);

    /**
     * @notice Withdraw a given amount of the underlying asset, and transfer it to a particular recipient.
     * @param recipient address of the underlying asset's recipient
     * @param amount amount of the underlying asset
     */
    function withdraw(address recipient, uint256 amount) external;

    /**
     * @notice Increase a limit of maximum collateral total supply.
     * @param amount amount to increase the limit by
     * @dev Called only by limitIncreaser.
     */
    function increaseLimit(uint256 amount) external;

    /**
     * @notice Set a new limit increaser.
     * @param limitIncreaser address of the new limit increaser
     * @dev Called only by limitIncreaser.
     */
    function setLimitIncreaser(address limitIncreaser) external;
}
IDepositStrategy.sol 14 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IDepositStrategy {
    struct DepositData {
        uint256 subvaultIndex;
        uint256 deposit;
    }

    function calculateDepositAmounts(address vault, uint256 assets)
        external
        view
        returns (DepositData[] memory subvaultsData);
}
IRebalanceStrategy.sol 16 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IRebalanceStrategy {
    struct RebalanceData {
        uint256 subvaultIndex;
        uint256 deposit;
        uint256 claimable;
        uint256 staked;
    }

    function calculateRebalanceAmounts(address vault)
        external
        view
        returns (RebalanceData[] memory subvaultsData);
}
IWithdrawalStrategy.sol 16 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IWithdrawalStrategy {
    struct WithdrawalData {
        uint256 subvaultIndex;
        uint256 claimable;
        uint256 pending;
        uint256 staked;
    }

    function calculateWithdrawalAmounts(address vault, uint256 amount)
        external
        view
        returns (WithdrawalData[] memory subvaultsData);
}
IAccessControl.sol 98 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)

pragma solidity ^0.8.20;

/**
 * @dev External interface of AccessControl declared to support ERC165 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 signaling 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, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    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;
}
Math.sol 415 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return 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.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev 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 {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 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 prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, 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 {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            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^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // 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^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice 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) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @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 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @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;
    }
}
IMultiVaultStorage.sol 114 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {IProtocolAdapter} from "../adapters/IProtocolAdapter.sol";
import {IWithdrawalQueue} from "../queues/IWithdrawalQueue.sol";
import {IDepositStrategy} from "../strategies/IDepositStrategy.sol";
import {IRebalanceStrategy} from "../strategies/IRebalanceStrategy.sol";
import {IWithdrawalStrategy} from "../strategies/IWithdrawalStrategy.sol";
import {IDefaultCollateral} from "../tokens/IDefaultCollateral.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {
    ERC4626Upgradeable,
    IERC4626
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

interface IMultiVaultStorage {
    enum Protocol {
        SYMBIOTIC,
        EIGEN_LAYER,
        ERC4626
    }

    struct Subvault {
        Protocol protocol;
        address vault;
        address withdrawalQueue;
    }

    struct RewardData {
        address distributionFarm;
        address curatorTreasury;
        address token;
        uint256 curatorFeeD6;
        Protocol protocol;
        bytes data;
    }

    struct MultiStorage {
        address depositStrategy;
        address withdrawalStrategy;
        address rebalanceStrategy;
        Subvault[] subvaults;
        mapping(address subvault => uint256 index) indexOfSubvault;
        mapping(uint256 id => RewardData) rewardData;
        EnumerableSet.UintSet farmIds;
        address defaultCollateral;
        address symbioticAdapter;
        address eigenLayerAdapter;
        address erc4626Adapter;
        bytes32[16] _gap;
    }

    function subvaultsCount() external view returns (uint256);

    function subvaultAt(uint256 index) external view returns (Subvault memory);

    function indexOfSubvault(address subvault) external view returns (uint256);

    function farmIds() external view returns (uint256[] memory);

    function farmCount() external view returns (uint256);

    function farmIdAt(uint256 index) external view returns (uint256);

    function farmIdsContains(uint256 farmId) external view returns (bool);

    function defaultCollateral() external view returns (IDefaultCollateral);

    function depositStrategy() external view returns (IDepositStrategy);

    function withdrawalStrategy() external view returns (IWithdrawalStrategy);

    function rebalanceStrategy() external view returns (IRebalanceStrategy);

    function eigenLayerAdapter() external view returns (IProtocolAdapter);

    function symbioticAdapter() external view returns (IProtocolAdapter);

    function erc4626Adapter() external view returns (IProtocolAdapter);

    function adapterOf(Protocol protocol) external view returns (IProtocolAdapter);

    function rewardData(uint256 farmId) external view returns (RewardData memory);

    event SubvaultAdded(
        address indexed subvault, address withdrawalQueue, Protocol protocol, uint256 subvaultIndex
    );

    event SubvaultRemoved(address indexed subvault, uint256 subvaultIndex);

    event SubvaultIndexChanged(address indexed subvault, uint256 oldIndex, uint256 newIndex);

    event RewardDataRemoved(uint256 indexed farmId);

    event RewardDataSet(uint256 indexed farmId, RewardData data);

    event DefaultCollateralSet(address indexed defaultCollateral);

    event DepositStrategySet(address indexed depositStrategy);

    event WithdrawalStrategySet(address indexed withdrawalStrategy);

    event RebalanceStrategySet(address indexed rebalanceStrategy);

    event SymbioticAdapterSet(address indexed symbioticAdapter);

    event EigenLayerAdapterSet(address indexed eigenLayerAdapter);

    event ERC4626AdapterSet(address indexed erc4626Adapter);
}
IERC4626Vault.sol 44 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {
    ERC4626Upgradeable,
    IERC4626
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title IERC4626Vault
 * @notice Extension of the IERC4626 interface that introduces a `deposit` method with an additional referral address parameter.
 * @dev This interface enhances the standard ERC4626 vault by adding referral-based deposits.
 * @dev Also extends the VaultControl interface for managing deposit limits, deposit pause and withdrawal pause.
 */
interface IERC4626Vault is IERC4626 {
    /**
     * @notice Emitted when a deposit is made through a referral.
     * @param assets The amount of underlying tokens deposited.
     * @param receiver The address receiving the vault shares.
     * @param referral The address of the referral involved in the deposit.
     */
    event ReferralDeposit(uint256 assets, address receiver, address referral);

    /**
     * @notice Mints vault shares to the `receiver` by depositing a specified amount of `assets` with an associated `referral`.
     * @param assets The amount of underlying tokens to be deposited.
     * @param receiver The address that will receive the vault shares.
     * @param referral The address of the referral associated with the deposit.
     * @return shares The amount of vault shares minted to the `receiver`.
     *
     * @custom:requirements
     * - The `assets` to be deposited MUST be greater than 0.
     *
     * @custom:effects
     * - Transfers the underlying tokens (`assets`) from the sender to the vault.
     * - Mints the corresponding `shares` to the `receiver`.
     * - Deposits the `assets` into the underlying bond.
     * - Emits a `ReferralDeposit` event.
     */
    function deposit(uint256 assets, address receiver, address referral)
        external
        returns (uint256 shares);
}
VaultControl.sol 90 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/vaults/IVaultControl.sol";
import {VaultControlStorage} from "./VaultControlStorage.sol";

abstract contract VaultControl is
    IVaultControl,
    VaultControlStorage,
    ReentrancyGuardUpgradeable,
    AccessControlEnumerableUpgradeable
{
    bytes32 private constant SET_LIMIT_ROLE = keccak256("SET_LIMIT_ROLE");
    bytes32 private constant PAUSE_WITHDRAWALS_ROLE = keccak256("PAUSE_WITHDRAWALS_ROLE");
    bytes32 private constant UNPAUSE_WITHDRAWALS_ROLE = keccak256("UNPAUSE_WITHDRAWALS_ROLE");
    bytes32 private constant PAUSE_DEPOSITS_ROLE = keccak256("PAUSE_DEPOSITS_ROLE");
    bytes32 private constant UNPAUSE_DEPOSITS_ROLE = keccak256("UNPAUSE_DEPOSITS_ROLE");
    bytes32 private constant SET_DEPOSIT_WHITELIST_ROLE = keccak256("SET_DEPOSIT_WHITELIST_ROLE");
    bytes32 private constant SET_DEPOSITOR_WHITELIST_STATUS_ROLE =
        keccak256("SET_DEPOSITOR_WHITELIST_STATUS_ROLE");

    /**
     * @notice Initializes the vault control settings, including roles, limits, and pause states.
     * @param _admin The address of the admin who will be granted the `DEFAULT_ADMIN_ROLE`.
     * @param _limit The initial limit on deposits for the vault.
     * @param _depositPause A boolean indicating whether deposits should be paused initially.
     * @param _withdrawalPause A boolean indicating whether withdrawals should be paused initially.
     * @param _depositWhitelist A boolean indicating whether a deposit whitelist should be enabled initially.
     *
     * @dev This function performs the following steps:
     * - Initializes the reentrancy guard to prevent reentrancy attacks.
     * - Initializes the access control system, setting up roles and permissions.
     * - Grants the `DEFAULT_ADMIN_ROLE` to the specified `_admin` address.
     * - Initializes the vault control storage with the specified limits, pause states, and whitelist configuration.
     * - This function is intended to be called during the initialization phase and is protected by the `onlyInitializing` modifier.
     */
    function __initializeVaultControl(
        address _admin,
        uint256 _limit,
        bool _depositPause,
        bool _withdrawalPause,
        bool _depositWhitelist
    ) internal onlyInitializing {
        __ReentrancyGuard_init();
        __AccessControlEnumerable_init();
        _grantRole(DEFAULT_ADMIN_ROLE, _admin);

        __initializeVaultControlStorage(_limit, _depositPause, _withdrawalPause, _depositWhitelist);
    }

    /// @inheritdoc IVaultControl
    function setLimit(uint256 _limit) external onlyRole(SET_LIMIT_ROLE) {
        _setLimit(_limit);
    }

    /// @inheritdoc IVaultControl
    function pauseWithdrawals() external onlyRole(PAUSE_WITHDRAWALS_ROLE) {
        _setWithdrawalPause(true);
        _revokeRole(PAUSE_WITHDRAWALS_ROLE, _msgSender());
    }

    /// @inheritdoc IVaultControl
    function unpauseWithdrawals() external onlyRole(UNPAUSE_WITHDRAWALS_ROLE) {
        _setWithdrawalPause(false);
    }

    /// @inheritdoc IVaultControl
    function pauseDeposits() external onlyRole(PAUSE_DEPOSITS_ROLE) {
        _setDepositPause(true);
        _revokeRole(PAUSE_DEPOSITS_ROLE, _msgSender());
    }

    /// @inheritdoc IVaultControl
    function unpauseDeposits() external onlyRole(UNPAUSE_DEPOSITS_ROLE) {
        _setDepositPause(false);
    }

    /// @inheritdoc IVaultControl
    function setDepositWhitelist(bool status) external onlyRole(SET_DEPOSIT_WHITELIST_ROLE) {
        _setDepositWhitelist(status);
    }

    /// @inheritdoc IVaultControl
    function setDepositorWhitelistStatus(address account, bool status)
        external
        onlyRole(SET_DEPOSITOR_WHITELIST_STATUS_ROLE)
    {
        _setDepositorWhitelistStatus(account, status);
    }
}
IVaultControlStorage.sol 107 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

/**
 * @title IVaultControlStorage
 * @notice Interface for interacting with the storage and control states of a vault.
 * @dev Provides functions to manage deposit and withdrawal controls, limits, and whitelisting of depositors.
 */
interface IVaultControlStorage {
    /**
     * @notice Storage structure for vault control data.
     * @dev Used to manage vault settings such as deposit and withdrawal states, limits, and whitelist functionality.
     * @param depositPause Indicates if deposits are currently paused.
     * @param withdrawalPause Indicates if withdrawals are currently paused.
     * @param limit The current limit on deposits.
     * @param depositWhitelist Indicates if the deposit whitelist is enabled.
     * @param isDepositorWhitelisted Mapping to track the whitelist status of each depositor by address.
     */
    struct Storage {
        bool depositPause;
        bool withdrawalPause;
        uint256 limit;
        bool depositWhitelist;
        mapping(address => bool) isDepositorWhitelisted;
    }

    /**
     * @notice Returns the current value of the `depositPause` state.
     * @dev When `true`, deposits into the vault are paused.
     * @return paused The current state of the deposit pause.
     */
    function depositPause() external view returns (bool);

    /**
     * @notice Returns the current value of the `withdrawalPause` state.
     * @dev When `true`, withdrawals from the vault are paused.
     * @return paused The current state of the withdrawal pause.
     */
    function withdrawalPause() external view returns (bool);

    /**
     * @notice Returns the current deposit limit.
     * @dev This limit can be applied to control the maximum allowed deposits.
     * @return limit The current limit value.
     */
    function limit() external view returns (uint256);

    /**
     * @notice Returns the current value of the `depositWhitelist` state.
     * @dev When `true`, only whitelisted addresses are allowed to deposit into the vault.
     * @return whitelistEnabled The current state of the deposit whitelist.
     */
    function depositWhitelist() external view returns (bool);

    /**
     * @notice Checks whether a given account is whitelisted for deposits.
     * @param account The address of the account to check.
     * @return isWhitelisted `true` if the account is whitelisted, `false` otherwise.
     */
    function isDepositorWhitelisted(address account) external view returns (bool);

    /**
     * @notice Emitted when the vault's deposit limit is updated.
     * @param limit The new limit value.
     * @param timestamp The time at which the limit was set.
     * @param sender The address of the account that set the new limit.
     */
    event LimitSet(uint256 limit, uint256 timestamp, address sender);

    /**
     * @notice Emitted when the deposit pause state is updated.
     * @param paused The new state of the deposit pause (`true` for paused, `false` for unpaused).
     * @param timestamp The time at which the pause state was set.
     * @param sender The address of the account that set the new state.
     */
    event DepositPauseSet(bool paused, uint256 timestamp, address sender);

    /**
     * @notice Emitted when the withdrawal pause state is updated.
     * @param paused The new state of the withdrawal pause (`true` for paused, `false` for unpaused).
     * @param timestamp The time at which the pause state was set.
     * @param sender The address of the account that set the new state.
     */
    event WithdrawalPauseSet(bool paused, uint256 timestamp, address sender);

    /**
     * @notice Emitted when the deposit whitelist state is updated.
     * @param status The new state of the deposit whitelist (`true` for enabled, `false` for disabled).
     * @param timestamp The time at which the whitelist state was set.
     * @param sender The address of the account that set the new state.
     */
    event DepositWhitelistSet(bool status, uint256 timestamp, address sender);

    /**
     * @notice Emitted when a depositor's whitelist status is updated.
     * @param account The address of the account whose whitelist status was updated.
     * @param status The new whitelist status (`true` for whitelisted, `false` for not whitelisted).
     * @param timestamp The time at which the whitelist status was set.
     * @param sender The address of the account that set the new status.
     */
    event DepositorWhitelistStatusSet(
        address account, bool status, uint256 timestamp, address sender
    );
}
IBeacon.sol 16 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeacon {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {UpgradeableBeacon} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}
Address.sol 159 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}
StorageSlot.sol 135 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     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;
 *     }
 * }
 * ```
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 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) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            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) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            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) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}
Proxy.sol 69 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)

pragma solidity ^0.8.20;

/**
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
 * be specified by overriding the virtual {_implementation} function.
 *
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
 * different contract through the {_delegate} function.
 *
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
 */
abstract contract Proxy {
    /**
     * @dev Delegates the current call to `implementation`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _delegate(address implementation) internal virtual {
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    /**
     * @dev This is a virtual function that should be overridden so it returns the address to which the fallback
     * function and {_fallback} should delegate.
     */
    function _implementation() internal view virtual returns (address);

    /**
     * @dev Delegates the current call to the address returned by `_implementation()`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _fallback() internal virtual {
        _delegate(_implementation());
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
     * function in the contract matches the call data.
     */
    fallback() external payable virtual {
        _fallback();
    }
}
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
IWithdrawalQueue.sol 60 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

/**
 * @title IWithdrawalQueue
 * @notice Interface to handle the withdrawal process from the underlying vault.
 * @dev Provides functions to manage withdrawals, claimable assets, and interactions with vault epochs.
 */
interface IWithdrawalQueue {
    /**
     * @notice Returns the pending collateral amount for a specific account.
     * @param account The address of the account.
     * @return pendingAssets The total amount of pending collateral for the account.
     */
    function pendingAssetsOf(address account) external view returns (uint256);

    /**
     * @notice Returns the claimable collateral amount for a specific account.
     * @param account The address of the account.
     * @return claimableAssets The total amount of claimable collateral for the account.
     */
    function claimableAssetsOf(address account) external view returns (uint256);

    /**
     * @notice Claims collateral from the External Vault for a specified epoch.
     * @param epoch The epoch number from which to claim collateral.
     *
     * @custom:requirements
     * - The epoch MUST be claimable.
     * - There MUST be claimable withdrawals for the given epoch.
     *
     * @custom:effects
     * - Emits an `EpochClaimed` event.
     */
    function pull(uint256 epoch) external;

    /**
     * @notice Finalizes the collateral claim process for a specific account and transfers it to the recipient.
     * @dev Transfers the lesser of the claimable amount or the specified maximum amount to the recipient.
     * @param account The address of the account to claim collateral from.
     * @param recipient The address of the recipient receiving the collateral.
     * @param maxAmount The maximum amount of collateral to be claimed.
     * @return amount The actual amount of collateral claimed and transferred.
     *
     * @custom:requirements
     * - `msg.sender` MUST be the vault or the account itself.
     * - The claimable amount MUST be greater than zero.
     * - There MUST be claimable withdrawals for the given account.
     *
     * @custom:effects
     * - Emits a `Claimed` event.
     */
    function claim(address account, address recipient, uint256 maxAmount)
        external
        returns (uint256 amount);

    function transferPendingAssets(address recipient, uint256 assets) external;

    function claimer() external view returns (address);
}
Initializable.sol 228 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 reininitialization) 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 Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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);
}
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 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);
}
IERC20Permit.sol 90 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
IMigratableEntity.sol 36 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMigratableEntity {
    error AlreadyInitialized();
    error NotFactory();
    error NotInitialized();

    /**
     * @notice Get the factory's address.
     * @return address of the factory
     */
    function FACTORY() external view returns (address);

    /**
     * @notice Get the entity's version.
     * @return version of the entity
     * @dev Starts from 1.
     */
    function version() external view returns (uint64);

    /**
     * @notice Initialize this entity contract by using a given data and setting a particular version and owner.
     * @param initialVersion initial version of the entity
     * @param owner initial owner of the entity
     * @param data some data to use
     */
    function initialize(uint64 initialVersion, address owner, bytes calldata data) external;

    /**
     * @notice Migrate this entity to a particular newer version using a given data.
     * @param newVersion new version of the entity
     * @param data some data to use
     */
    function migrate(uint64 newVersion, bytes calldata data) external;
}
IVaultStorage.sol 233 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVaultStorage {
    error InvalidTimestamp();
    error NoPreviousEpoch();

    /**
     * @notice Get a deposit whitelist enabler/disabler's role.
     * @return identifier of the whitelist enabler/disabler role
     */
    function DEPOSIT_WHITELIST_SET_ROLE() external view returns (bytes32);

    /**
     * @notice Get a depositor whitelist status setter's role.
     * @return identifier of the depositor whitelist status setter role
     */
    function DEPOSITOR_WHITELIST_ROLE() external view returns (bytes32);

    /**
     * @notice Get a deposit limit enabler/disabler's role.
     * @return identifier of the deposit limit enabler/disabler role
     */
    function IS_DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32);

    /**
     * @notice Get a deposit limit setter's role.
     * @return identifier of the deposit limit setter role
     */
    function DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32);

    /**
     * @notice Get the delegator factory's address.
     * @return address of the delegator factory
     */
    function DELEGATOR_FACTORY() external view returns (address);

    /**
     * @notice Get the slasher factory's address.
     * @return address of the slasher factory
     */
    function SLASHER_FACTORY() external view returns (address);

    /**
     * @notice Get a vault collateral.
     * @return address of the underlying collateral
     */
    function collateral() external view returns (address);

    /**
     * @notice Get a burner to issue debt to (e.g., 0xdEaD or some unwrapper contract).
     * @return address of the burner
     */
    function burner() external view returns (address);

    /**
     * @notice Get a delegator (it delegates the vault's stake to networks and operators).
     * @return address of the delegator
     */
    function delegator() external view returns (address);

    /**
     * @notice Get if the delegator is initialized.
     * @return if the delegator is initialized
     */
    function isDelegatorInitialized() external view returns (bool);

    /**
     * @notice Get a slasher (it provides networks a slashing mechanism).
     * @return address of the slasher
     */
    function slasher() external view returns (address);

    /**
     * @notice Get if the slasher is initialized.
     * @return if the slasher is initialized
     */
    function isSlasherInitialized() external view returns (bool);

    /**
     * @notice Get a time point of the epoch duration set.
     * @return time point of the epoch duration set
     */
    function epochDurationInit() external view returns (uint48);

    /**
     * @notice Get a duration of the vault epoch.
     * @return duration of the epoch
     */
    function epochDuration() external view returns (uint48);

    /**
     * @notice Get an epoch at a given timestamp.
     * @param timestamp time point to get the epoch at
     * @return epoch at the timestamp
     * @dev Reverts if the timestamp is less than the start of the epoch 0.
     */
    function epochAt(
        uint48 timestamp
    ) external view returns (uint256);

    /**
     * @notice Get a current vault epoch.
     * @return current epoch
     */
    function currentEpoch() external view returns (uint256);

    /**
     * @notice Get a start of the current vault epoch.
     * @return start of the current epoch
     */
    function currentEpochStart() external view returns (uint48);

    /**
     * @notice Get a start of the previous vault epoch.
     * @return start of the previous epoch
     * @dev Reverts if the current epoch is 0.
     */
    function previousEpochStart() external view returns (uint48);

    /**
     * @notice Get a start of the next vault epoch.
     * @return start of the next epoch
     */
    function nextEpochStart() external view returns (uint48);

    /**
     * @notice Get if the deposit whitelist is enabled.
     * @return if the deposit whitelist is enabled
     */
    function depositWhitelist() external view returns (bool);

    /**
     * @notice Get if a given account is whitelisted as a depositor.
     * @param account address to check
     * @return if the account is whitelisted as a depositor
     */
    function isDepositorWhitelisted(
        address account
    ) external view returns (bool);

    /**
     * @notice Get if the deposit limit is set.
     * @return if the deposit limit is set
     */
    function isDepositLimit() external view returns (bool);

    /**
     * @notice Get a deposit limit (maximum amount of the active stake that can be in the vault simultaneously).
     * @return deposit limit
     */
    function depositLimit() external view returns (uint256);

    /**
     * @notice Get a total number of active shares in the vault at a given timestamp using a hint.
     * @param timestamp time point to get the total number of active shares at
     * @param hint hint for the checkpoint index
     * @return total number of active shares at the timestamp
     */
    function activeSharesAt(uint48 timestamp, bytes memory hint) external view returns (uint256);

    /**
     * @notice Get a total number of active shares in the vault.
     * @return total number of active shares
     */
    function activeShares() external view returns (uint256);

    /**
     * @notice Get a total amount of active stake in the vault at a given timestamp using a hint.
     * @param timestamp time point to get the total active stake at
     * @param hint hint for the checkpoint index
     * @return total amount of active stake at the timestamp
     */
    function activeStakeAt(uint48 timestamp, bytes memory hint) external view returns (uint256);

    /**
     * @notice Get a total amount of active stake in the vault.
     * @return total amount of active stake
     */
    function activeStake() external view returns (uint256);

    /**
     * @notice Get a total number of active shares for a particular account at a given timestamp using a hint.
     * @param account account to get the number of active shares for
     * @param timestamp time point to get the number of active shares for the account at
     * @param hint hint for the checkpoint index
     * @return number of active shares for the account at the timestamp
     */
    function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) external view returns (uint256);

    /**
     * @notice Get a number of active shares for a particular account.
     * @param account account to get the number of active shares for
     * @return number of active shares for the account
     */
    function activeSharesOf(
        address account
    ) external view returns (uint256);

    /**
     * @notice Get a total amount of the withdrawals at a given epoch.
     * @param epoch epoch to get the total amount of the withdrawals at
     * @return total amount of the withdrawals at the epoch
     */
    function withdrawals(
        uint256 epoch
    ) external view returns (uint256);

    /**
     * @notice Get a total number of withdrawal shares at a given epoch.
     * @param epoch epoch to get the total number of withdrawal shares at
     * @return total number of withdrawal shares at the epoch
     */
    function withdrawalShares(
        uint256 epoch
    ) external view returns (uint256);

    /**
     * @notice Get a number of withdrawal shares for a particular account at a given epoch (zero if claimed).
     * @param epoch epoch to get the number of withdrawal shares for the account at
     * @param account account to get the number of withdrawal shares for
     * @return number of withdrawal shares for the account at the epoch
     */
    function withdrawalSharesOf(uint256 epoch, address account) external view returns (uint256);

    /**
     * @notice Get if the withdrawals are claimed for a particular account at a given epoch.
     * @param epoch epoch to check the withdrawals for the account at
     * @param account account to check the withdrawals for
     * @return if the withdrawals are claimed for the account at the epoch
     */
    function isWithdrawalsClaimed(uint256 epoch, address account) external view returns (bool);
}
ERC4626Upgradeable.sol 311 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC4626.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 {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626].
 *
 * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for
 * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
 * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this
 * contract and not the "assets" token which is an independent contract.
 *
 * [CAUTION]
 * ====
 * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
 * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
 * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
 * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
 * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
 * verifying the amount received is as expected, using a wrapper that performs these checks such as
 * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
 *
 * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()`
 * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault
 * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself
 * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset
 * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's
 * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more
 * expensive than it is profitable. More details about the underlying math can be found
 * xref:erc4626.adoc#inflation-attack[here].
 *
 * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
 * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
 * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
 * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
 * `_convertToShares` and `_convertToAssets` functions.
 *
 * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
 * ====
 */
abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626 {
    using Math for uint256;

    /// @custom:storage-location erc7201:openzeppelin.storage.ERC4626
    struct ERC4626Storage {
        IERC20 _asset;
        uint8 _underlyingDecimals;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC4626")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ERC4626StorageLocation = 0x0773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00;

    function _getERC4626Storage() private pure returns (ERC4626Storage storage $) {
        assembly {
            $.slot := ERC4626StorageLocation
        }
    }

    /**
     * @dev Attempted to deposit more assets than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);

    /**
     * @dev Attempted to mint more shares than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);

    /**
     * @dev Attempted to withdraw more assets than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);

    /**
     * @dev Attempted to redeem more shares than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);

    /**
     * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
     */
    function __ERC4626_init(IERC20 asset_) internal onlyInitializing {
        __ERC4626_init_unchained(asset_);
    }

    function __ERC4626_init_unchained(IERC20 asset_) internal onlyInitializing {
        ERC4626Storage storage $ = _getERC4626Storage();
        (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
        $._underlyingDecimals = success ? assetDecimals : 18;
        $._asset = asset_;
    }

    /**
     * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
     */
    function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) {
        (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) {
                return (true, uint8(returnedDecimals));
            }
        }
        return (false, 0);
    }

    /**
     * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
     * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
     * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
     *
     * See {IERC20Metadata-decimals}.
     */
    function decimals() public view virtual override(IERC20Metadata, ERC20Upgradeable) returns (uint8) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return $._underlyingDecimals + _decimalsOffset();
    }

    /** @dev See {IERC4626-asset}. */
    function asset() public view virtual returns (address) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return address($._asset);
    }

    /** @dev See {IERC4626-totalAssets}. */
    function totalAssets() public view virtual returns (uint256) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return $._asset.balanceOf(address(this));
    }

    /** @dev See {IERC4626-convertToShares}. */
    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-convertToAssets}. */
    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-maxDeposit}. */
    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxMint}. */
    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxWithdraw}. */
    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return _convertToAssets(balanceOf(owner), Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-maxRedeem}. */
    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf(owner);
    }

    /** @dev See {IERC4626-previewDeposit}. */
    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-previewMint}. */
    function previewMint(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Ceil);
    }

    /** @dev See {IERC4626-previewWithdraw}. */
    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Ceil);
    }

    /** @dev See {IERC4626-previewRedeem}. */
    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-deposit}. */
    function deposit(uint256 assets, address receiver) public virtual returns (uint256) {
        uint256 maxAssets = maxDeposit(receiver);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
        }

        uint256 shares = previewDeposit(assets);
        _deposit(_msgSender(), receiver, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-mint}.
     *
     * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero.
     * In this case, the shares will be minted without requiring any assets to be deposited.
     */
    function mint(uint256 shares, address receiver) public virtual returns (uint256) {
        uint256 maxShares = maxMint(receiver);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
        }

        uint256 assets = previewMint(shares);
        _deposit(_msgSender(), receiver, assets, shares);

        return assets;
    }

    /** @dev See {IERC4626-withdraw}. */
    function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) {
        uint256 maxAssets = maxWithdraw(owner);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
        }

        uint256 shares = previewWithdraw(assets);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-redeem}. */
    function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) {
        uint256 maxShares = maxRedeem(owner);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        uint256 assets = previewRedeem(shares);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return assets;
    }

    /**
     * @dev Internal conversion function (from assets to shares) with support for rounding direction.
     */
    function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
        return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
    }

    /**
     * @dev Internal conversion function (from shares to assets) with support for rounding direction.
     */
    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
        return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
    }

    /**
     * @dev Deposit/mint common workflow.
     */
    function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
        ERC4626Storage storage $ = _getERC4626Storage();
        // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
        // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
        // assets are transferred and before the shares are minted, which is a valid state.
        // slither-disable-next-line reentrancy-no-eth
        SafeERC20.safeTransferFrom($._asset, caller, address(this), assets);
        _mint(receiver, shares);

        emit Deposit(caller, receiver, assets, shares);
    }

    /**
     * @dev Withdraw/redeem common workflow.
     */
    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual {
        ERC4626Storage storage $ = _getERC4626Storage();
        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
        // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
        // shares are burned and after the assets are transferred, which is a valid state.
        _burn(owner, shares);
        SafeERC20.safeTransfer($._asset, receiver, assets);

        emit Withdraw(caller, receiver, owner, assets, shares);
    }

    function _decimalsOffset() internal view virtual returns (uint8) {
        return 0;
    }
}
EnumerableSet.sol 378 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @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.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [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 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;
    }

    // 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 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;

        /// @solidity memory-safe-assembly
        assembly {
            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 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;

        /// @solidity memory-safe-assembly
        assembly {
            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 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;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}
IVaultControl.sol 84 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "./IVaultControlStorage.sol";
import {AccessControlEnumerableUpgradeable} from
    "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {ERC4626Upgradeable} from
    "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {ReentrancyGuardUpgradeable} from
    "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title IVaultControl
 * @notice Interface for controlling the operational state of a vault, including deposits, withdrawals, limits, and whitelisting.
 * @dev Extends IVaultControlStorage for managing storage and settings related to vault operations.
 */
interface IVaultControl is IVaultControlStorage {
    /**
     * @notice Sets a new limit for the vault to restrict the total value of assets held.
     * @dev Can only be called by an address with the `SET_LIMIT_ROLE`.
     * @param _limit The new limit value to be set.
     */
    function setLimit(uint256 _limit) external;

    /**
     * @notice Pauses withdrawals from the vault.
     * @dev Once paused, no withdrawals can be processed until unpaused.
     * @dev Can only be called by an address with the `PAUSE_WITHDRAWALS_ROLE`.
     * @custom:effects
     * - Emits a `WithdrawalPauseSet` event with `paused` set to `true`.
     * - Revokes the `PAUSE_WITHDRAWALS_ROLE` from `msg.sender`
     */
    function pauseWithdrawals() external;

    /**
     * @notice Unpauses withdrawals from the vault.
     * @dev Once unpaused, withdrawals can be processed again.
     * @dev Can only be called by an address with the `UNPAUSE_WITHDRAWALS_ROLE`.
     * @custom:effects
     * - Emits a `WithdrawalPauseSet` event with `paused` set to `false`.
     */
    function unpauseWithdrawals() external;

    /**
     * @notice Pauses deposits into the vault.
     * @dev Once paused, no deposits can be made until unpaused.
     * @dev Can only be called by an address with the `PAUSE_DEPOSITS_ROLE`.
     * @custom:effects
     * - Emits a `DepositPauseSet` event with `paused` set to `true`.
     * - Revokes the `PAUSE_DEPOSITS_ROLE` from `msg.sender`
     */
    function pauseDeposits() external;

    /**
     * @notice Unpauses deposits into the vault.
     * @dev Once unpaused, deposits can be made again.
     * @dev Can only be called by an address with the `UNPAUSE_DEPOSITS_ROLE`.
     * @custom:effects
     * - Emits a `DepositPauseSet` event with `paused` set to `false`.
     */
    function unpauseDeposits() external;

    /**
     * @notice Sets the deposit whitelist status for the vault.
     * @dev When the whitelist is enabled, only addresses on the whitelist can deposit into the vault.
     * @dev Can only be called by an address with the `SET_DEPOSIT_WHITELIST_ROLE`.
     * @param status The new whitelist status (`true` to enable, `false` to disable).
     * @custom:effects
     * - Emits a `DepositWhitelistSet` event indicating the new whitelist status.
     */
    function setDepositWhitelist(bool status) external;

    /**
     * @notice Updates the whitelist status of a specific account.
     * @dev Allows the contract to grant or revoke the ability of an account to make deposits based on the whitelist.
     * @dev Can only be called by an address with the `SET_DEPOSITOR_WHITELIST_STATUS_ROLE`.
     * @param account The address of the account to be updated.
     * @param status The new whitelist status for the account (`true` for whitelisted, `false` for not whitelisted).
     * @custom:effects
     * - Emits a `DepositorWhitelistStatusSet` event indicating the updated whitelist status for the account.
     */
    function setDepositorWhitelistStatus(address account, bool status) external;
}
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;
    }
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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 Context {
    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;
    }
}
ERC20Upgradeable.sol 341 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
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}.
     *
     * All two of these 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;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        ERC20Storage storage $ = _getERC20Storage();
        return $._totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    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;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    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}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * 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:
     * ```
     * 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);
            }
        }
    }
}
AccessControlEnumerableUpgradeable.sol 92 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlEnumerable.sol)

pragma solidity ^0.8.20;

import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Extension of {AccessControl} that allows enumerating the members of each role.
 */
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
    using EnumerableSet for EnumerableSet.AddressSet;

    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
    struct AccessControlEnumerableStorage {
        mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;

    function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
        assembly {
            $.slot := AccessControlEnumerableStorageLocation
        }
    }

    function __AccessControlEnumerable_init() internal onlyInitializing {
    }

    function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].at(index);
    }

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        return $._roleMembers[role].length();
    }

    /**
     * @dev Overload {AccessControl-_grantRole} to track enumerable memberships
     */
    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        bool granted = super._grantRole(role, account);
        if (granted) {
            $._roleMembers[role].add(account);
        }
        return granted;
    }

    /**
     * @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
     */
    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
        AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
        bool revoked = super._revokeRole(role, account);
        if (revoked) {
            $._roleMembers[role].remove(account);
        }
        return revoked;
    }
}
ReentrancyGuardUpgradeable.sol 105 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 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;
    }
}
draft-IERC6093.sol 161 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 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 ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-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 ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 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);
}
IAccessControlEnumerable.sol 31 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "../IAccessControl.sol";

/**
 * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
 */
interface IAccessControlEnumerable is IAccessControl {
    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) external view returns (address);

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
AccessControlUpgradeable.sol 233 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;


    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
    struct AccessControlStorage {
        mapping(bytes32 role => RoleData) _roles;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;

    function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
        assembly {
            $.slot := AccessControlStorageLocation
        }
    }

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    function __AccessControl_init() internal onlyInitializing {
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        AccessControlStorage storage $ = _getAccessControlStorage();
        bytes32 previousAdminRole = getRoleAdmin(role);
        $._roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (!hasRole(role, account)) {
            $._roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (hasRole(role, account)) {
            $._roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}
ERC165Upgradeable.sol 33 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165Upgradeable is Initializable, IERC165 {
    function __ERC165_init() internal onlyInitializing {
    }

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @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 ({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[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);
}

Read Contract

calculateSalt 0xebfa3fdb → bytes32
deployments 0x37404017 → tuple
multiVaultImplementation 0x187a4ad9 → address
strategy 0xa8c62e76 → address
symbioticVaultFactory 0x23ee74fb → address
symbioticWithdrawalQueueImplementation 0x08e85a78 → address

Write Contract 1 functions

These functions modify contract state and require a wallet transaction to execute.

deploy 0xd741974a
tuple params
returns: address, address, tuple

Recent Transactions

No transactions found for this address