Address Contract Partially Verified
Address
0x6a0D30cC6e52ee99DCF5b1F623afdb9D3C5E4fE6
Balance
0 ETH
Nonce
1
Code Size
21808 bytes
Creator
0xA1D94F74...0405 at tx 0xde9f6792...94aa05
Indexed Transactions
0
Contract Bytecode
21808 bytes
0x60806040526004361015610011575f80fd5b5f3560e01c806301e1d1141461374b57806304dbf0ce146136c457806306fdde031461361f57806307a2d13a14612daf578063095ea7b31461359a5780630a28a4771461357c5780630b467b9b146134705780630d326b181461344d57806313af4035146133b057806318160ddd1461339357806320fe8d581461337057806323b872dd146132055780632438525b146130ef5780632cb19f98146130695780632e0292281461304e578063313ce567146130115780633644e51514612ff757806338d52e0f14612fa75780633e9d2ac714612efb578063402d267d14610bd55780634796629114612e175780634b219d1614612dcd5780634cdad50614612daf5780634dedf20e14612d655780634ef501ac14612d1757806350b5c16a14612ce457806354cde13e14612cb15780635797527014612b9a578063585cd34b146129c55780635aa22bc8146129a85780635b34b823146128735780635c1a1a4f146127675780635c9ce04d1461273a57806360a38ac11461270257806360d54d411461251957806368c18beb146124cf5780636a5f1aa2146124055780636d9a3010146123e55780636e553f65146123ac57806370897b23146122f857806370a08231146122b35780637e729ac4146122805780637ecebe001461223b5780637fb6caad14611fe85780638639fb0714611f92578063871c979c14611f0b5780638778878214611ee05780638c54519b14611dbe5780638da5cb5b14611d8c5780638e511e4d14611d695780638eede80114611d36578063920ed70614611c8f57806393ab2ab714611c5c57806394bf804d14611c2357806395d89b4114611b3757806398c9b49c14611b0a57806399e9918314611ac55780639faae464146119fb578063a68bafa3146119cb578063a6afed95146119b3578063a6f7f5d614611988578063a9059cbb14611851578063aa4abe7f14611777578063ac9650d814611686578063ad468d1114611653578063b192a84a146115b7578063b2e3284814611519578063b3d7f6b9146114fb578063b460af94146114cc578063b84c8246146112c4578063ba03a75f1461127a578063ba0876521461124b578063bc0dd3741461120c578063c0463711146111e2578063c21ad0281461115b578063c47f002714610f09578063c63d75b614610bd5578063c69507dd14610edf578063c6e6f5921461065b578063c719946c14610ea5578063ce04bebb14610e76578063ce96cb7714610bd5578063d505accf14610bda578063d905777e14610bd5578063dd62ed3e14610b67578063e470b8bc14610b11578063e4d38cd8146109b9578063e66f53b714610986578063e78ab14e14610935578063e90956cf14610898578063ece1d6e514610878578063ed27f7c914610858578063ef7fa71b14610681578063ef8b30f71461065b578063f6f98fd514610564578063f73f8f31146105485763fe56e23214610444575f80fd5b3461054457602060031936011261054457600435610460614633565b635e80a6bf811161051c57601a5460601c15801590610514575b156104ec5760207fd87632b1c6ebfa21acbca0e3279b3cf6385a377cb8fda51e5b866baa6e6012ab916104ab6143f2565b6bffffffffffffffffffffffff81167fffffffffffffffffffffffffffffffffffffffff000000000000000000000000601a541617601a55604051908152a1005b7fda9e0fa0000000000000000000000000000000000000000000000000000000005f5260045ffd5b50801561047a565b7fcd4e6167000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b34610544575f6003193601126105445760205f5c604051908152f35b346105445761057236613969565b9061057b614633565b8051602082012091825f5260126020526fffffffffffffffffffffffffffffffff600160405f200154168110610633577f7368d59ed82f6a538f6deef9baa54623fe3699ce07b19031f762f740c8c34b03916105d682614f21565b845f5260126020526fffffffffffffffffffffffffffffffff600160405f200191167fffffffffffffffffffffffffffffffff0000000000000000000000000000000082541617905561062e60405192839283613ea9565b0390a2005b7fa844d937000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610544576020600319360112610544576020610679600435614609565b604051908152f35b346105445761068f3661388a565b73ffffffffffffffffffffffffffffffffffffffff6001541633036108305760405181838237602081838101601881520301902054610808577fffffffff000000000000000000000000000000000000000000000000000000006106f38284613c91565b16917f5c1a1a4f0000000000000000000000000000000000000000000000000000000083036107d15781600811610544577f8b18afeb361b83b025999ed5b42f1d90c68aaa5a0fd49c015f04c3b8b81e80eb917fffffffff000000000000000000000000000000000000000000000000000000006004830135165f52601660205261078360405f20545b42613c28565b60405182848237602081848101601881520301902055604051818382376020818381016018815203019020546107c6604051938493604085526040850191613cf7565b9060208301520390a2005b7f8b18afeb361b83b025999ed5b42f1d90c68aaa5a0fd49c015f04c3b8b81e80eb91835f52601660205261078360405f205461077d565b7fd91ff208000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f82b42900000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610544575f60031936011261054457602060195460601c604051908152f35b34610544575f600319360112610544576020600f5460c01c604051908152f35b34610544576020600319360112610544576108b161376f565b73ffffffffffffffffffffffffffffffffffffffff5f541633036108305773ffffffffffffffffffffffffffffffffffffffff16807fffffffffffffffffffffffff000000000000000000000000000000000000000060015416176001557fbd0a63c12948fbc9194a5839019f99c9d71db924e5c70018265bc778b8f1a5065f80a2005b34610544576020600319360112610544577fffffffff0000000000000000000000000000000000000000000000000000000061096f613a94565b165f526016602052602060405f2054604051908152f35b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b34610544576080600319360112610544576109d261376f565b60243567ffffffffffffffff8111610544576109f290369060040161394b565b604435906064359173ffffffffffffffffffffffffffffffffffffffff83169182840361054457610a398273ffffffffffffffffffffffffffffffffffffffff928761495a565b941693845f526013602052610a5260405f205483613f68565b94670de0b6b3a763ffff8601809611610ae457610ad07fb98216be0267fa550428a584fe6ac1ef0f39788e0198372100e813444afecd2992670de0b6b3a76400006020980494610aa06143f2565b610ab5610aac87613c35565b98308a89614fbf565b604051938452888401526080604084015260808301906145d6565b9260608201528033930390a3604051908152f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b34610544576020600319360112610544577fffffffff00000000000000000000000000000000000000000000000000000000610b4b613a94565b165f526017602052602060ff60405f2054166040519015158152f35b3461054457604060031936011261054457610b8061376f565b73ffffffffffffffffffffffffffffffffffffffff610b9d613792565b91165f52600d60205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060405f2054604051908152f35b613a70565b346105445760e060031936011261054457610bf361376f565b610bfb613792565b9060443590606435906084359060ff821680920361054457428310610e4e5773ffffffffffffffffffffffffffffffffffffffff1692835f52600e60205260405f208054927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8414610ae45760805f916020936001870190556040519873ffffffffffffffffffffffffffffffffffffffff858b01917f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c983528a60408d015216998a606082015286848201528760a08201528860c082015260c08152610ce260e082613806565b519020610ced613ec5565b9060405190868201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152610d32606282613806565b519020906040519182528482015260a435604082015260c435606082015282805260015afa15610e435773ffffffffffffffffffffffffffffffffffffffff5f5116848115159182610e39575b505015610e1157610e0c7f40336af4b170f3dad5ddfb59897a8289adc3bed01417ca7e2bd77f85bc52641393855f52600d60205260405f20875f526020528260405f205586867f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020604051878152a3604051938493846040919493926060820195825260208201520152565b0390a3005b7f815e1d64000000000000000000000000000000000000000000000000000000005f5260045ffd5b1490508487610d7f565b6040513d5f823e3d90fd5b7f05787bdf000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610544575f6003193601126105445760206fffffffffffffffffffffffffffffffff600f5416604051908152f35b34610544575f6003193601126105445760206040517f000000000000000000000000000000000000000000000000000000e8d4a510008152f35b34610544576020600319360112610544576004355f526012602052602060405f2054604051908152f35b3461054457610f1736613bec565b73ffffffffffffffffffffffffffffffffffffffff5f5416330361083057805167ffffffffffffffff811161112e57610f516009546137b5565b601f81116110d5575b506020601f8211600114610ff35791610fcb82610fe3937f4df9dcd34ae35f40f2c756fd8ac83210ed0b76d065543ee73d868aec7c7fcf02955f91610fe8575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6009555b604051918291602083526020830190613847565b0390a1005b905083015186610f9a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169060095f527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af915f5b8181106110bd5750927f4df9dcd34ae35f40f2c756fd8ac83210ed0b76d065543ee73d868aec7c7fcf02949260019282610fe39610611086575b5050811b01600955610fcf565b8401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558580611079565b9192602060018192868901518155019401920161103f565b60095f5261111e907f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510611124575b601f0160051c01906142cc565b82610f5a565b9091508190611111565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff61118961376f565b611191614633565b16807fffffffffffffffffffffffff000000000000000000000000000000000000000060035416176003557f43a3573cf238e4b97d4ffec8e9b8a69cd276b47cf68230dbdbec3a7a98b287235f80a2005b34610544575f60031936011261054457602067ffffffffffffffff600f5460801c16604051908152f35b34610544576020600319360112610544576004355f52601260205260206fffffffffffffffffffffffffffffffff600160405f20015416604051908152f35b3461054457602061067961125e36613b98565b9061126a9392936143f2565b61127384613f11565b9384614fbf565b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff6112a861376f565b165f526007602052602060ff60405f2054166040519015158152f35b34610544576112d236613bec565b73ffffffffffffffffffffffffffffffffffffffff5f5416330361083057805167ffffffffffffffff811161112e5761130c600a546137b5565b601f811161147e575b506020601f821160011461139c579161138582610fe3937fadf3ae8bd543b3007d464f15cb8ea1db3f44e84d41d203164f40b95e27558ac6955f91610fe857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b600a55604051918291602083526020830190613847565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821690600a5f527fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8915f5b8181106114665750927fadf3ae8bd543b3007d464f15cb8ea1db3f44e84d41d203164f40b95e27558ac6949260019282610fe3961061142f575b5050811b01600a55610fcf565b8401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558580611422565b919260206001819286890151815501940192016113e8565b600a5f526114c6907fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8601f840160051c8101916020851061112457601f0160051c01906142cc565b82611315565b346105445760206106796114df36613b98565b906114eb9392936143f2565b6114f484613c35565b8094614fbf565b34610544576020600319360112610544576020610679600435614587565b34610544576020600319360112610544577fffffffff00000000000000000000000000000000000000000000000000000000611553613a94565b61155b614633565b16805f52601760205260405f2060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790557fa8480b135a28741e8c059528bf8f82de70278485fb86bfc816a183801f0a85705f80a2005b346105445773ffffffffffffffffffffffffffffffffffffffff7f74dc60cbc81a9472d04ad1d20e151d369c41104d655ed3f2f3091166a502cd8d60206115fd36613b5c565b9390611607614633565b1692835f52600882526116488160405f209060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b6040519015158152a2005b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60145416604051908152f35b346105445760206003193601126105445760043567ffffffffffffffff8111610544573660238201121561054457806004013567ffffffffffffffff8111610544573660248260051b8401011161054457905f907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbd81360301915b838110156117755760248160051b830101358381121561054457820160248101359067ffffffffffffffff821161054457604401813603811361054457815f92918392604051928392833781018381520390305af461175e614558565b901561176d5750600101611701565b805190602001fd5b005b3461054457602060031936011261054457600435335f52600860205260ff60405f2054161561083057640ec41a0ddf81116118295760207f75fef0e2a5e934789b0723129a0d1bbfd4a50f39c5af9919626e4f3603aef5ff916117d86143f2565b600f5477ffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffff0000000000000000000000000000000000000000000000008360c01b16911617600f55604051908152a1005b7f0d54247f000000000000000000000000000000000000000000000000000000005f5260045ffd5b346105445760406003193601126105445761186a61376f565b60243573ffffffffffffffffffffffffffffffffffffffff821691821561196057611894336142e2565b15611938576118a29061436a565b1561191057335f52600c60205260405f206118be828254613e9c565b9055815f52600c60205260405f206118d7828254613c28565b90556040519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203392a3602060405160018152f35b7f861a96d6000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f876736d1000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd92e233d000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610544575f6003193601126105445760206bffffffffffffffffffffffff601a5416604051908152f35b34610544575f600319360112610544576117756143f2565b34610544576020600319360112610544576004355f5260126020526020600160405f20015460801c604051908152f35b3461054457602060031936011261054457611a1461376f565b611a1c614633565b73ffffffffffffffffffffffffffffffffffffffff8116908115801590611aad575b156104ec57611a4b6143f2565b6bffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000601a549260601b16911617601a557f7f1dd9b78dfeeb8c452333915b2f077f4920fefc4ec7f146767e9ba40303ef1f5f80a2005b506bffffffffffffffffffffffff601a541615611a3e565b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff611af361376f565b165f526013602052602060405f2054604051908152f35b34610544576020600319360112610544576020611b2d611b2861376f565b61436a565b6040519015158152f35b34610544575f600319360112610544576040515f600a54611b57816137b5565b8084529060018116908115611be15750600114611b83575b611b7f83610fcf81850382613806565b0390f35b600a5f9081527fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8939250905b808210611bc757509091508101602001610fcf611b6f565b919260018160209254838588010152019101909291611baf565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208086019190915291151560051b84019091019150610fcf9050611b6f565b34610544576040600319360112610544576020600435610679611c44613792565b611c4c6143f2565b611c5583614587565b9283614da2565b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b3461054457611c9d36613b5c565b9073ffffffffffffffffffffffffffffffffffffffff5f5416330361083057602073ffffffffffffffffffffffffffffffffffffffff7f8da69db4003c01b5c7be66a7bb5953423222e5118b6af0aadf54e75feef5bc50921692835f52600782526116488160405f209060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60055416604051908152f35b34610544576020600319360112610544576020611b2d611d8761376f565b6142e2565b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff5f5416604051908152f35b3461054457611dcc36613969565b90805160208201209173ffffffffffffffffffffffffffffffffffffffff6001541633148015611eca575b1561083057825f5260126020526fffffffffffffffffffffffffffffffff600160405f200154168111611ea257610e0c7fbc2ffe81312f53db4f327eca188ebdc13df66a8ce25f7dec6f5b4495fe27b37191845f526012602052600160405f20016fffffffffffffffffffffffffffffffff808316167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055604051918291339583613ea9565b7fbdeafe07000000000000000000000000000000000000000000000000000000005f5260045ffd5b50335f52600760205260ff60405f205416611df7565b34610544575f6003193601126105445760206bffffffffffffffffffffffff60195416604051908152f35b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff611f3961376f565b611f41614633565b16807fffffffffffffffffffffffff000000000000000000000000000000000000000060055416176005557f34c1885f043c6c77736fbc402c63a383472347b85868dab73d42b69596a98e945f80a2005b346105445760206003193601126105445760043567ffffffffffffffff811161054457602080611fc78193369060040161394b565b604051928184925191829101835e8101601881520301902054604051908152f35b346105445760406003193601126105445761200161376f565b60243567ffffffffffffffff81116105445761202190369060040161394b565b90335f52600860205260ff60405f205416156108305773ffffffffffffffffffffffffffffffffffffffff16807fffffffffffffffffffffffff0000000000000000000000000000000000000000601454161760145581519167ffffffffffffffff831161112e576120946015546137b5565b601f81116121ed575b50602092601f811160011461212e57806120ec91602094955f91610fe857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6015555b604051918183925191829101835e81015f815203902090337f9deb43d71422af41853c3921fb364b7647f9a9b136e46d66d45c1bf707af706c5f80a4005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0811660155f527f55f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec475905f5b8181106121d55750906020949583600194931061219e575b5050811b016015556120f0565b8401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558580612191565b84870151835560209687019660019093019201612179565b60155f52612235907f55f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec475601f860160051c8101916020871061112457601f0160051c01906142cc565b8361209d565b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff61226961376f565b165f52600e602052602060405f2054604051908152f35b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff6122e161376f565b165f52600c602052602060405f2054604051908152f35b3461054457602060031936011261054457600435612314614633565b6706f05b59d3b20000811161051c5760195460601c158015906123a4575b156104ec5760207f8b940a95968ad5b511f89b01075446a4fe9f614f2dc5fbb9e9a6b227d6d4fd70916123636143f2565b6bffffffffffffffffffffffff81167fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006019541617601955604051908152a1005b508015612332565b346105445760406003193601126105445760206004356106796123cd613792565b6123d56143f2565b6123de83614609565b8093614da2565b34610544575f600319360112610544576020601a5460601c604051908152f35b346105445760206003193601126105445761241e61376f565b612426614633565b73ffffffffffffffffffffffffffffffffffffffff81169081158015906124b7575b156104ec576124556143f2565b6bffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006019549260601b169116176019557fe59cc3d47f72dbd81f7d5fc40b9b05d9c4374a54addea771648ae24943c26e765f80a2005b506bffffffffffffffffffffffff6019541615612448565b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff6124fd61376f565b165f526010602052602060ff60405f2054166040519015158152f35b346105445760206003193601126105445761253261376f565b61253a614633565b73ffffffffffffffffffffffffffffffffffffffff60065416801590811561266f575b50156126475773ffffffffffffffffffffffffffffffffffffffff811690815f52601060205260ff60405f205416156125b8575b507f8f125a24838c4c23e893904b255b5c672d43d4cb8af7e3d15841eaeabc1e68aa5f80a2005b601154906801000000000000000082101561112e576125e082600161260c9401601155613b17565b90919073ffffffffffffffffffffffffffffffffffffffff8084549260031b9316831b921b1916179055565b805f52601060205260405f2060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541617905581612591565b7f133c5cc8000000000000000000000000000000000000000000000000000000005f5260045ffd5b60249150602090604051928380927fe4bb26f200000000000000000000000000000000000000000000000000000000825273ffffffffffffffffffffffffffffffffffffffff871660048301525afa908115610e43575f916126d3575b508261255d565b6126f5915060203d6020116126fb575b6126ed8183613806565b810190613d35565b826126cc565b503d6126e3565b34610544575f60031936011261054457611b7f61271d613f7b565b604080519384526020840192909252908201529081906060820190565b346105445761274836613ac3565b90335f52600860205260ff60405f205416156108305761177592614b52565b3461054457604060031936011261054457612780613a94565b7fffffffff00000000000000000000000000000000000000000000000000000000602435916127ad614633565b16907f5c1a1a4f00000000000000000000000000000000000000000000000000000000821461284b57815f52601660205260405f205481116128235760207f488e0bfafe0a9580c2271bee1f563a47d309f0105ca950ea525d909b43b36daf91835f52601682528060405f2055604051908152a2005b7f17e0a21c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07e895b1000000000000000000000000000000000000000000000000000000005f5260045ffd5b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff6128a161376f565b6128a9614633565b1680612900575b807fffffffffffffffffffffffff000000000000000000000000000000000000000060065416176006557f7c5f71ef3a94efd2804ad9a6eb74b723dae0d26ef7d2a031f7986c96ae55afda5f80a2005b6011545f5b8181106129135750506128b0565b73ffffffffffffffffffffffffffffffffffffffff61293182613b17565b90549060031b1c16604051907fe4bb26f20000000000000000000000000000000000000000000000000000000082526004820152602081602481875afa908115610e43575f9161298a575b501561264757600101612905565b6129a2915060203d81116126fb576126ed8183613806565b8461297c565b34610544575f600319360112610544576020601154604051908152f35b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff6129f361376f565b6129fb614633565b16805f52601060205260ff60405f205416612a37575b7f34f33faa2592bc1f615ec3e91b55b7784665ce46461403c294824d85f9f664585f80a2005b5f5b6011549081811015612b93578273ffffffffffffffffffffffffffffffffffffffff612a6483613b17565b90549060031b1c1614612a7b576001915001612a39565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211610ae4576125e073ffffffffffffffffffffffffffffffffffffffff612ac9612ad794613b17565b90549060031b1c1691613b17565b6011548015612b66577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01612b0b81613b17565b73ffffffffffffffffffffffffffffffffffffffff82549160031b1b191690556011555b805f52601060205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008154169055612a11565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b5050612b2f565b3461054457612ba836613969565b90805160208201209173ffffffffffffffffffffffffffffffffffffffff6001541633148015612c9b575b1561083057825f526012602052600160405f20015460801c8111612c73575f83815260126020526040902060010180546fffffffffffffffffffffffffffffffff16608083901b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000161790557fcedce6ffe8b7f89de49bd4f955667ca0a963a8059264196807b240ed470b25ce90610e0c90604051918291339583613ea9565b7f04c27fbf000000000000000000000000000000000000000000000000000000005f5260045ffd5b50335f52600760205260ff60405f205416612bd3565b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60045416604051908152f35b34610544575f60031936011261054457602073ffffffffffffffffffffffffffffffffffffffff60065416604051908152f35b34610544576020600319360112610544576004356011548110156105445773ffffffffffffffffffffffffffffffffffffffff612d55602092613b17565b90549060031b1c16604051908152f35b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff612d9361376f565b165f526008602052602060ff60405f2054166040519015158152f35b34610544576020600319360112610544576020610679600435613f11565b3461054457612ddb36613ac3565b90335f52600860205260ff60405f2054168015612e01575b15610830576117759261495a565b50335f52600760205260ff60405f205416612df3565b3461054457604060031936011261054457612e30613a94565b7fffffffff0000000000000000000000000000000000000000000000000000000060243591612e5d614633565b16907f5c1a1a4f00000000000000000000000000000000000000000000000000000000821461284b57815f52601660205260405f20548110612ed35760207fed380a7a1f0afc99b313ac7d9f1e52e44430d32b4639c7993bd0021974e30c0e91835f52601682528060405f2055604051908152a2005b7f470f70c2000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461054457604060031936011261054457612f1461376f565b60243590612f20614633565b66470de4df8200008211612f7f57602073ffffffffffffffffffffffffffffffffffffffff7f1e29a0bf54cf1e929f17cc97a198ff5cf7f8da1df1cb8ff67f7184ce380f3924921692835f52601382528060405f2055604051908152a2005b7f516e7f38000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610544575f60031936011261054457602060405173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48168152f35b34610544575f600319360112610544576020610679613ec5565b34610544575f60031936011261054457602060405160ff7f0000000000000000000000000000000000000000000000000000000000000012168152f35b34610544575f60031936011261054457611b7f610fcf61399b565b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff61309761376f565b61309f614633565b16807fffffffffffffffffffffffff000000000000000000000000000000000000000060025416176002557e4237ebb79833e3e0bc633281452dc9e5c76b92b603140b2a53b0bceb441cae5f80a2005b34610544576130fd36613969565b90613106614633565b8051602082012091670de0b6b3a764000081116131dd57825f526012602052600160405f20015460801c81106131b5575f83815260126020526040902060010180546fffffffffffffffffffffffffffffffff16608083901b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000161790557f2a343b9a1ceba40853d01c6adeea53f5c0e4b95b4eb870ed4af309a0ead7e3999161062e60405192839283613ea9565b7f8433449d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f0d6108b6000000000000000000000000000000000000000000000000000000005f5260045ffd5b346105445760606003193601126105445761321e61376f565b613226613792565b60443573ffffffffffffffffffffffffffffffffffffffff83169182156119605773ffffffffffffffffffffffffffffffffffffffff81169384156119605761326e906142e2565b156119385761327c9061436a565b156119105760207fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef918333036132ef575b835f52600c825260405f206132c3828254613e9c565b9055845f52600c825260405f206132db828254613c28565b9055604051908152a3602060405160018152f35b5f848152600d8352604080822033835284529020548160018201613315575b50506132ad565b816133238261333d94613e9c565b5f888152600d875260408082203383528852902055613e9c565b604051908152847fc1886047cf852ffdaeda16dafd86e28507781ffba1faafef1d536d4abaa260b0843393a3858161330e565b34610544576020600319360112610544576020611b2d61338e61376f565b613e14565b34610544575f600319360112610544576020600b54604051908152f35b34610544576020600319360112610544576133c961376f565b5f5473ffffffffffffffffffffffffffffffffffffffff811633036108305773ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f557f167d3e9c1016ab80e58802ca9da10ce5c6a0f4debc46a2e7a2cd9e56899a4fb55f80a2005b34610544576020600319360112610544576020611b2d61346b61376f565b613d4d565b346105445761347e3661388a565b73ffffffffffffffffffffffffffffffffffffffff6001541633148015613566575b1561083057604051818382376020818381016018815203019020541561353e575f604051828482376020818481016018815203019020557f70521cf6a7c2458c5ad406081ad7b3bc4afd31b01b586a157a9780fcee6a77cb610e0c7fffffffff000000000000000000000000000000000000000000000000000000006135268486613c91565b16936040519182916020835233956020840191613cf7565b7f1ea942a8000000000000000000000000000000000000000000000000000000005f5260045ffd5b50335f52600760205260ff60405f2054166134a0565b34610544576020600319360112610544576020610679600435613c35565b34610544576040600319360112610544576135b361376f565b73ffffffffffffffffffffffffffffffffffffffff60243591335f52600d60205260405f208282165f526020528260405f205560405192835216907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b34610544575f600319360112610544576040515f60095461363f816137b5565b8084529060018116908115611be1575060011461366657611b7f83610fcf81850382613806565b60095f9081527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af939250905b8082106136aa57509091508101602001610fcf611b6f565b919260018160209254838588010152019101909291613692565b346105445760206003193601126105445773ffffffffffffffffffffffffffffffffffffffff6136f261376f565b6136fa614633565b16807fffffffffffffffffffffffff000000000000000000000000000000000000000060045416176004557f6b530281a3f54e1c3e430297307183ba1bafca3cdf775404395c208e6cf3e15f5f80a2005b34610544575f600319360112610544576020613765613f7b565b5050604051908152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361054457565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361054457565b90600182811c921680156137fc575b60208310146137cf57565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916137c4565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761112e57604052565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b9060206003198301126105445760043567ffffffffffffffff811161054457826023820112156105445780600401359267ffffffffffffffff84116105445760248483010111610544576024019190565b67ffffffffffffffff811161112e57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b929192613921826138db565b9161392f6040519384613806565b829481845281830111610544578281602093845f960137010152565b9080601f830112156105445781602061396693359101613915565b90565b6040600319820112610544576004359067ffffffffffffffff8211610544576139949160040161394b565b9060243590565b604051905f82601554916139ae836137b5565b8083529260018116908115613a3357506001146139d4575b6139d292500383613806565b565b5060155f90815290917f55f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec4755b818310613a175750509060206139d2928201016139c6565b60209193508060019154838589010152019101909184926139ff565b602092506139d29491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016139c6565b3461054457602060031936011261054457613a8961376f565b5060206040515f8152f35b600435907fffffffff000000000000000000000000000000000000000000000000000000008216820361054457565b9060606003198301126105445760043573ffffffffffffffffffffffffffffffffffffffff8116810361054457916024359067ffffffffffffffff821161054457613b109160040161394b565b9060443590565b601154811015613b2f5760115f5260205f2001905f90565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b60031960409101126105445760043573ffffffffffffffffffffffffffffffffffffffff81168103610544579060243580151581036105445790565b6003196060910112610544576004359060243573ffffffffffffffffffffffffffffffffffffffff81168103610544579060443573ffffffffffffffffffffffffffffffffffffffff811681036105445790565b6020600319820112610544576004359067ffffffffffffffff821161054457806023830112156105445781602461396693600401359101613915565b91908201809211610ae457565b613c51613c7d613c56613c46613f7b565b919490600b54613c28565b613c28565b7f000000000000000000000000000000000000000000000000000000e8d4a5100090613c28565b60018201809211610ae457613966926147dd565b919091357fffffffff0000000000000000000000000000000000000000000000000000000081169260048110613cc5575050565b7fffffffff00000000000000000000000000000000000000000000000000000000929350829060040360031b1b161690565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b90816020910312610544575180151581036105445790565b73ffffffffffffffffffffffffffffffffffffffff16308114908115613df3575b8115613d78575090565b9050602073ffffffffffffffffffffffffffffffffffffffff60045416916024604051809481937f0d326b1800000000000000000000000000000000000000000000000000000000835260048301525afa908115610e43575f91613dda575090565b613966915060203d6020116126fb576126ed8183613806565b60045473ffffffffffffffffffffffffffffffffffffffff16159150613d6e565b73ffffffffffffffffffffffffffffffffffffffff600554168015918215613e3b57505090565b6020919250602473ffffffffffffffffffffffffffffffffffffffff9160405194859384927f20fe8d580000000000000000000000000000000000000000000000000000000084521660048301525afa908115610e43575f91613dda575090565b91908203918211610ae457565b929190613ec0602091604086526040860190613847565b930152565b60405160208101907f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218825246604082015230606082015260608152613f0b608082613806565b51902090565b613c5190613f20613c46613f7b565b60018301809311610ae45761396692613f5d613f63927f000000000000000000000000000000000000000000000000000000e8d4a5100090613c28565b92613f68565b614521565b81810292918115918404141715610ae457565b5f5c6142b057600f54613f9b67ffffffffffffffff8260801c1642613e9c565b916040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48165afa908115610e43575f9161427e575b505f6011545b8082106141d1575050614062670de0b6b3a764000061405b6fffffffffffffffffffffffffffffffff8616956140518888613f68565b9060c01c90613f68565b0484613c28565b81811090821802189180830390831102801515806141b8575b806141a4575b1561419c576140aa670de0b6b3a7640000916bffffffffffffffffffffffff6019541690613f68565b04925b80151580614183575b8061416f575b15614168576140ee6140d7670de0b6b3a76400009285613f68565b6bffffffffffffffffffffffff601a541690613f68565b045b614103816140fe8686613e9c565b613e9c565b600b54947f000000000000000000000000000000000000000000000000000000e8d4a51000926141338488613c28565b60018401809411610ae4576141629461415685613f6361415c94613f6397613f68565b98613c28565b90613f68565b91929190565b505f6140f0565b5061417e601a5460601c61436a565b6140bc565b506bffffffffffffffffffffffff601a541615156140b6565b505f926140ad565b506141b360195460601c61436a565b614081565b506bffffffffffffffffffffffff60195416151561407b565b90916004602073ffffffffffffffffffffffffffffffffffffffff6141f586613b17565b90549060031b1c16604051928380927f56c075730000000000000000000000000000000000000000000000000000000082525afa908115610e43575f9161424c575b5061424490600192613c28565b92019061401b565b90506020813d8211614276575b8161426660209383613806565b8101031261054457516001614237565b3d9150614259565b90506020813d6020116142a8575b8161429960209383613806565b8101031261054457515f614015565b3d915061428c565b6fffffffffffffffffffffffffffffffff600f5416905f905f90565b8181106142d7575050565b5f81556001016142cc565b73ffffffffffffffffffffffffffffffffffffffff60035416801591821561430957505090565b6020919250602473ffffffffffffffffffffffffffffffffffffffff9160405194859384927f8e511e4d0000000000000000000000000000000000000000000000000000000084521660048301525afa908115610e43575f91613dda575090565b73ffffffffffffffffffffffffffffffffffffffff60025416801591821561439157505090565b6020919250602473ffffffffffffffffffffffffffffffffffffffff9160405194859384927f98c9b49c0000000000000000000000000000000000000000000000000000000084521660048301525afa908115610e43575f91613dda575090565b6143fa613f7b565b91600f547f4dec04e750ca11537cabcd8a9eab06494de08da3735bc8871cd41250e190bc0460806040516fffffffffffffffffffffffffffffffff84168152846020820152856040820152866060820152a17fffffffffffffffffffffffffffffffff000000000000000000000000000000006fffffffffffffffffffffffffffffffff61448784614f21565b16911617600f555f5c15614519575b5080614504575b50806144ef575b50600f547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff77ffffffffffffffff000000000000000000000000000000004260801b16911617600f55565b6144fe90601a5460601c614f4d565b5f6144a4565b6145139060195460601c614f4d565b5f61449d565b5f5d5f614496565b811561452b570490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b3d15614582573d90614569826138db565b916145776040519384613806565b82523d5f602084013e565b606090565b613c5190614596613c46613f7b565b9160018101809111610ae4576145d0613966937f000000000000000000000000000000000000000000000000000000e8d4a5100090613c28565b916147dd565b90602080835192838152019201905f5b8181106145f35750505090565b82518452602093840193909201916001016145e6565b613c5161461a613c56613c46613f7b565b9160018201809211610ae45761396692613f6391613f68565b5f357fffffffff00000000000000000000000000000000000000000000000000000000811690600436106147a9575b50604051365f82376020813681016018815203019020541561353e57604051365f82376020813681016018815203019020544210614781577fffffffff0000000000000000000000000000000000000000000000000000000016805f52601760205260ff60405f205416614759575f604051368282376020813681016018815203019020557f29aa42fc192ff77ef42105abba283197ac841341e196e417a7fc2784cdc4e5fb60405160208152366020820152365f60408301375f602080368401010152602081817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f360116820101030190a2565b7f281df4aa000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f621e25c3000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fffffffff000000000000000000000000000000000000000000000000000000008092503660040360031b1b16165f614662565b906147e791613f68565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810191818311610ae45761396692613f6391613c28565b919060408382031261054457825167ffffffffffffffff81116105445783019080601f830112156105445781519167ffffffffffffffff831161112e578260051b90604051936148736020840186613806565b845260208085019282010192831161054457602001905b82821061489d5750505060209092015190565b815181526020918201910161488a565b92937fffffffff0000000000000000000000000000000000000000000000000000000060609396956148fc73ffffffffffffffffffffffffffffffffffffffff94608088526080880190613847565b97602087015216604085015216910152565b8051821015613b2f5760209160051b010190565b9190915f8382019384129112908015821691151617610ae457565b939291613ec09060409286526060602087015260608601906145d6565b919273ffffffffffffffffffffffffffffffffffffffff8316805f52601060205260ff60405f20541615614b2a575f6149e893604051809581927f4e45f1ff00000000000000000000000000000000000000000000000000000000835233908a7fffffffff0000000000000000000000000000000000000000000000000000000087351691600486016148ad565b038183855af1948515610e43575f935f96614b00575b505f5b8451811015614a9457614a14818661490e565b515f52601260205260405f209081548015614a6c5788614a3391614922565b5f8112614a44576001925501614a01565b7fe58d4718000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fba0d87b5000000000000000000000000000000000000000000000000000000005f5260045ffd5b509194614afa90614aeb847fd602b36fb24934aef1bc2a658de029b486fa4c664a6e45de1f48e3fd1be25dd994969730907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486152fd565b6040519182918733968461493d565b0390a390565b909550614b209193503d805f833e614b188183613806565b810190614820565b929092945f6149fe565b7ff521d159000000000000000000000000000000000000000000000000000000005f5260045ffd5b919273ffffffffffffffffffffffffffffffffffffffff831692835f52601060205260ff60405f20541615614b2a57614c1492614bb9865f93614b936143f2565b7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48615427565b604051809481927f1eadd7780000000000000000000000000000000000000000000000000000000083523390897fffffffff0000000000000000000000000000000000000000000000000000000087351691600486016148ad565b038183875af1928315610e43575f925f94614d80575b505f5b8351811015614d4457614c40818561490e565b515f52601260205260405f20614c57868254614922565b905f8212614a4457818155600101546fffffffffffffffffffffffffffffffff81168015614d1c578211614cf45760801c90670de0b6b3a76400008214918215614cd3575b505015614cab57600101614c2d565b7f44e1772c000000000000000000000000000000000000000000000000000000005f5260045ffd5b670de0b6b3a7640000919250614cea905f5c613f68565b0410155f80614c9c565b7f4616e4af000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fbb1a23b9000000000000000000000000000000000000000000000000000000005f5260045ffd5b50927f2bc7948a96a066968d2a58aaf46eb0b305aa166b1d1951d2f7ef0919746b8c2a919492614d7b60405192839233968461493d565b0390a3565b909350614d989192503d805f833e614b188183613806565b919091925f614c2a565b91614dac8161436a565b1561191057614dba33613e14565b15614ef957614deb8330337f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486152fd565b614df58282614f4d565b614dfe83614f21565b906fffffffffffffffffffffffffffffffff600f5492166fffffffffffffffffffffffffffffffff831601916fffffffffffffffffffffffffffffffff8311610ae4577fffffffffffffffffffffffffffffffff000000000000000000000000000000006fffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff9416911617600f5560405192848452602084015216907fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d760403392a373ffffffffffffffffffffffffffffffffffffffff601454169081614ee7575050565b6139d291614ef361399b565b90614b52565b7f515b7cd9000000000000000000000000000000000000000000000000000000005f5260045ffd5b6fffffffffffffffffffffffffffffffff8111614a44576fffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff16908115611960577fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205f92848452600c825260408420614fa5828254613c28565b9055614fb381600b54613c28565b600b55604051908152a3565b91929092614fcc826142e2565b1561193857614fda81613d4d565b156152d5576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb489260208260248173ffffffffffffffffffffffffffffffffffffffff88165afa8015610e43575f9061528f575b73ffffffffffffffffffffffffffffffffffffffff925080861180615282575b615259575b5016938433036151bf575b841561196057845f52600c60205260405f206150a6828254613e9c565b90556150b481600b54613e9c565b600b555f857fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020604051858152a36150ec84614f21565b926fffffffffffffffffffffffffffffffff600f5494166fffffffffffffffffffffffffffffffff851603936fffffffffffffffffffffffffffffffff8511610ae4578361518a9287927fffffffffffffffffffffffffffffffff000000000000000000000000000000006fffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff9916911617600f55615427565b604051938452602084015216907ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db60403392a4565b845f52600d60205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260405f2054817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361521c575b5050615089565b61522591613e9c565b855f52600d60205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260405f20555f81615215565b61527b9061526c84601454169188613e9c565b9061527561399b565b9061495a565b505f61507e565b5082601454161515615079565b506020823d6020116152cd575b816152a960209383613806565b810103126105445773ffffffffffffffffffffffffffffffffffffffff9151615059565b3d915061529c565b7f8181c5ea000000000000000000000000000000000000000000000000000000005f5260045ffd5b90813b156153ff575f809493819473ffffffffffffffffffffffffffffffffffffffff604051928160208501967f23b872dd00000000000000000000000000000000000000000000000000000000885216602485015216604483015260648201526064815261536d608482613806565b51925af1615379614558565b90156153d75780519081159182156153bd575b50501561539557565b7f1eded19c000000000000000000000000000000000000000000000000000000005f5260045ffd5b6153d09250602080918301019101613d35565b5f8061538c565b7fe65b7a77000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff046a714000000000000000000000000000000000000000000000000000000005f5260045ffd5b919091803b156153ff575f928380936040519073ffffffffffffffffffffffffffffffffffffffff60208301947fa9059cbb000000000000000000000000000000000000000000000000000000008652166024830152604482015260448152615491606482613806565b51925af161549d614558565b90156154fb5780519081159182156154e1575b5050156154b957565b7f2f0470fc000000000000000000000000000000000000000000000000000000005f5260045ffd5b6154f49250602080918301019101613d35565b5f806154b0565b7face2a47e000000000000000000000000000000000000000000000000000000005f5260045ffdfea164736f6c634300081c000a
Verified Source Code Partial Match
Compiler: v0.8.28+commit.7893614a
EVM: cancun
Optimization: Yes (100000 runs)
VaultV2.sol 926 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity 0.8.28;
import {IVaultV2, IERC20, Caps} from "./interfaces/IVaultV2.sol";
import {IAdapter} from "./interfaces/IAdapter.sol";
import {IAdapterRegistry} from "./interfaces/IAdapterRegistry.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {EventsLib} from "./libraries/EventsLib.sol";
import "./libraries/ConstantsLib.sol";
import {MathLib} from "./libraries/MathLib.sol";
import {SafeERC20Lib} from "./libraries/SafeERC20Lib.sol";
import {IReceiveSharesGate, ISendSharesGate, IReceiveAssetsGate, ISendAssetsGate} from "./interfaces/IGate.sol";
/// ERC4626
/// @dev The vault is compliant with ERC-4626 and with ERC-2612 (permit extension). Though the vault has a
/// non-conventional behaviour on max functions: they always return zero.
/// @dev totalSupply is not updated to include shares minted to fee recipients. One can call accrueInterestView to
/// compute the updated totalSupply.
///
/// TOTAL ASSETS
/// @dev Adapters are responsible for reporting to the vault how much their investments are worth at any time, so that
/// the vault can accrue interest or realize losses.
/// @dev _totalAssets stores the last recorded total assets. Use totalAssets() for the updated total assets.
/// @dev Upon interest accrual, the vault loops through adapters' realAssets(). If there are too many adapters and/or
/// they consume too much gas on realAssets(), it could cause issues such as expensive interactions, even DOS.
///
/// LOSS REALIZATION
/// @dev Loss realization occurs in accrueInterest and decreases the total assets, causing shares to lose value.
/// @dev Vault shares should not be loanable to prevent shares shorting on loss realization. Shares can be flashloanable
/// because flashloan-based shorting is prevented as interests and losses are only accounted once per transaction.
///
/// SHARE PRICE
/// @dev The share price can go down if the vault incurs some losses. Users might want to perform slippage checks upon
/// withdraw/redeem via an other contract.
/// @dev Interest/loss are accounted only once per transaction (at the first interaction with the vault).
/// @dev Donations increase the share price but not faster than the maxRate.
/// @dev The vault has 1 virtual asset and a decimal offset of max(0, 18 - assetDecimals). In order to protect against
/// inflation attacks, the vault might need to be seeded with an initial deposit. See
/// https://docs.openzeppelin.com/contracts/5.x/erc4626#inflation-attack
/// @dev If they make the rate increase by a large factor, donations and forceDeallocate penalties can be in part stolen
/// by opportunistic depositors. Setting a low maxRate prevents that by making the donation/penalty distributed over a
/// long period.
///
/// CAPS
/// @dev Ids have an asset allocation, and can be absolutely capped and/or relatively capped.
/// @dev The allocation is not always up to date, because interest and losses are accounted only when (de)allocating in
/// the corresponding markets.
/// @dev The caps are checked on allocate (where allocations can increase) for the ids returned by the adapter.
/// @dev Relative caps are "soft" in the sense that they are not checked on exit.
/// @dev Caps can be exceeded because of interest.
/// @dev The relative cap is relative to firstTotalAssets, not realAssets.
/// @dev The relative cap unit is WAD.
/// @dev To track allocations using events, use the Allocate and Deallocate events only.
///
/// FIRST TOTAL ASSETS
/// @dev The variable firstTotalAssets tracks the total assets after the first interest accrual of the transaction.
/// @dev Used to implement a mechanism that prevents bypassing relative caps with flashloans. This mechanism makes the
/// caps conservative and can generate false positives, notably for big deposits that go through the liquidity adapter.
/// @dev Also used to accrue interest only once per transaction (see the "share price" section).
/// @dev Relative caps can still be manipulated by allocators (with short-term deposits), but it requires capital.
/// @dev The behavior of firstTotalAssets is different when the vault has totalAssets=0, but it does not matter
/// internally because in this case there are no investments to cap.
///
/// ADAPTERS
/// @dev Loose specification of adapters:
/// - They must enforce that only the vault can call allocate/deallocate.
/// - They must enter/exit markets only in allocate/deallocate.
/// - They must return the right ids on allocate/deallocate. Returned ids must not repeat.
/// - After a call to deallocate, the vault must have an approval to transfer at least `assets` from the adapter.
/// - They must make it possible to make deallocate possible (for in-kind redemptions).
/// - The totalAssets() calculation ignores markets for which the vault has no allocation.
/// - They must not re-enter (directly or indirectly) the vault. They might not statically prevent it, but the curator
/// must not interact with markets that can re-enter the vault.
/// - After an update, the sum of the changes returned after interactions with a given market must be exactly the
/// current estimated position.
/// @dev Ids being reused are useful to cap multiple investments that have a common property.
/// @dev Allocating is prevented if one of the ids' absolute cap is zero and deallocating is prevented if the id's
/// allocation is zero. This prevents interactions with zero assets with unknown markets. For markets that share all
/// their ids, it will be impossible to "disable" them (preventing any interaction) without disabling the others using
/// the same ids.
/// @dev On allocate or deallocate, the adapters might lose some assets (total realAssets decreases), for instance due
/// to roundings or entry/exit fees. This loss should stay negligible compared to gas. Adapters might not statically
/// ensure this, but the curators should not interact with markets that can create big entry/exit losses.
/// @dev Except particular scenarios, adapters should be removed only if they have no assets. In order to ensure no
/// allocator can allocate some assets in an adapter being removed, there should be an id exclusive to the adapter with
/// its cap set to zero.
///
/// ADAPTER REGISTRY
/// @dev An adapter registry can be added to restrict the adapters. This is useful to commit to using only a certain
/// type of adapters for example.
/// @dev If adapterRegistry is set to address(0), the vault can have any adapters.
/// @dev When an adapterRegistry is set, it retroactively checks already added adapters.
/// @dev If the adapterRegistry now returns false for an already added adapter, it doesn't impact the vault's
/// functioning.
/// @dev The invariant that adapters of the vault are all in the registry holds only if the registry cannot remove
/// adapters (is "add only").
///
/// LIQUIDITY ADAPTER
/// @dev Liquidity is allocated to the liquidityAdapter on deposit/mint, and deallocated from the liquidityAdapter on
/// withdraw/redeem if idle assets don't cover the withdrawal.
/// @dev The liquidity adapter is useful on exit, so that exit liquidity is available in addition to the idle assets. But
/// the same adapter/data is used for both entry and exit to have the property that in the general case looping
/// supply-withdraw or withdraw-supply should not change the allocation.
/// @dev If a cap (absolute or relative) associated with the ids returned by the liquidity adapter on the liquidity data
/// is reached, deposit/mint will revert. In particular, when the vault is empty or almost empty, the relative cap check
/// is likely to make deposits revert.
///
/// TOKEN REQUIREMENTS
/// @dev List of assumptions on the token that guarantees that the vault behaves as expected:
/// - It should be ERC-20 compliant, except that it can omit return values on transfer and transferFrom.
/// - The balance of the vault should only decrease on transfer and transferFrom.
/// - It should not re-enter the vault on transfer or transferFrom.
/// - The balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount on
/// transfer and transferFrom. In particular, tokens with fees on transfer are not supported.
///
/// LIVENESS REQUIREMENTS
/// @dev List of assumptions that guarantees the vault's liveness properties:
/// - Adapters should not revert on realAssets.
/// - The token should not revert on transfer and transferFrom if balances and approvals are right.
/// - The token should not revert on transfer to self.
/// - totalAssets and totalSupply must stay below ~10^35. Initially there are min(1, 10^(18-decimals)) shares per asset.
/// - The vault is pinged at least every 10 years.
/// - Adapters must not revert on deallocate if the underlying markets are liquid.
///
/// TIMELOCKS
/// @dev The timelock duration of decreaseTimelock is the timelock duration of the function whose timelock is being
/// decreased (e.g. the timelock of decreaseTimelock(addAdapter, ...) is timelock[addAdapter]).
/// @dev Multiple clashing data can be pending, for example increaseCap and decreaseCap, which can make so accepted
/// timelocked data can potentially be changed shortly afterwards.
/// @dev If a function is abdicated, it cannot be called no matter its timelock and what executableAt[data] contains.
/// Otherwise, the minimum time in which a function can be called is the following:
/// min(
/// timelock[selector],
/// executableAt[selector::_],
/// executableAt[decreaseTimelock::selector::newTimelock] + newTimelock
/// ).
/// @dev Nothing is checked on the timelocked data, so it could be not executable (function does not exist, argument
/// encoding is wrong, function' conditions are not met, etc.).
///
/// ABDICATION
/// @dev When a timelocked function is abdicated, it can't be called anymore.
/// @dev It is still possible to submit data for it or change its timelock, but it will not be executable / effective.
///
/// GATES
/// @dev Set to 0 to disable a gate.
/// @dev Gates must never revert, nor consume too much gas.
/// @dev receiveSharesGate:
/// - Gates receiving shares.
/// - Can lock users out of getting back their shares deposited on an other contract.
/// @dev sendSharesGate:
/// - Gates sending shares.
/// - Can lock users out of exiting the vault.
/// @dev receiveAssetsGate:
/// - Gates withdrawing assets from the vault.
/// - The vault itself (address(this)) is always allowed to receive assets, regardless of the gate configuration.
/// - Can lock users out of exiting the vault.
/// @dev sendAssetsGate:
/// - Gates depositing assets to the vault.
/// - This gate is not critical (cannot block users' funds), while still being able to gate supplies.
///
/// FEES
/// @dev Fees unit is WAD.
/// @dev This invariant holds for both fees: fee != 0 => recipient != address(0).
///
/// ROLES
/// @dev The owner cannot do actions that can directly hurt depositors. Though it can set the curator and sentinels.
/// @dev The curator cannot do actions that can directly hurt depositors without going through a timelock.
/// @dev Allocators can move funds between markets in the boundaries set by caps without going through timelocks. They
/// can also set the liquidity adapter and data, which can prevent deposits and/or withdrawals (it cannot prevent
/// "in-kind redemptions" with forceDeallocate though). Allocators also set the maxRate.
/// @dev Warning: if setIsAllocator is timelocked, removing an allocator will take time.
/// @dev Roles are not "two-step", so anyone can give a role to anyone, but it does not mean that they will exercise it.
///
/// MISC
/// @dev Zero checks are not systematically performed.
/// @dev No-ops are allowed.
/// @dev NatSpec comments are included only when they bring clarity.
/// @dev The contract uses transient storage.
/// @dev At creation, all settings are set to their default values. Notably, timelocks are zero which is useful to set
/// up the vault quickly. Also, there are no gates so anybody can interact with the vault. To prevent that, the gates
/// configuration can be batched with the vault creation.
contract VaultV2 is IVaultV2 {
using MathLib for uint256;
using MathLib for uint128;
using MathLib for int256;
/* IMMUTABLE */
address public immutable asset;
uint8 public immutable decimals;
uint256 public immutable virtualShares;
/* ROLES STORAGE */
address public owner;
address public curator;
address public receiveSharesGate;
address public sendSharesGate;
address public receiveAssetsGate;
address public sendAssetsGate;
address public adapterRegistry;
mapping(address account => bool) public isSentinel;
mapping(address account => bool) public isAllocator;
/* TOKEN STORAGE */
string public name;
string public symbol;
uint256 public totalSupply;
mapping(address account => uint256) public balanceOf;
mapping(address owner => mapping(address spender => uint256)) public allowance;
mapping(address account => uint256) public nonces;
/* INTEREST STORAGE */
uint256 public transient firstTotalAssets;
uint128 public _totalAssets;
uint64 public lastUpdate;
uint64 public maxRate;
/* CURATION STORAGE */
mapping(address account => bool) public isAdapter;
address[] public adapters;
mapping(bytes32 id => Caps) internal caps;
mapping(address adapter => uint256) public forceDeallocatePenalty;
/* LIQUIDITY ADAPTER STORAGE */
address public liquidityAdapter;
bytes public liquidityData;
/* TIMELOCKS STORAGE */
mapping(bytes4 selector => uint256) public timelock;
mapping(bytes4 selector => bool) public abdicated;
mapping(bytes data => uint256) public executableAt;
/* FEES STORAGE */
uint96 public performanceFee;
address public performanceFeeRecipient;
uint96 public managementFee;
address public managementFeeRecipient;
/* GETTERS */
function adaptersLength() external view returns (uint256) {
return adapters.length;
}
function totalAssets() external view returns (uint256) {
(uint256 newTotalAssets,,) = accrueInterestView();
return newTotalAssets;
}
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
}
function absoluteCap(bytes32 id) external view returns (uint256) {
return caps[id].absoluteCap;
}
function relativeCap(bytes32 id) external view returns (uint256) {
return caps[id].relativeCap;
}
function allocation(bytes32 id) external view returns (uint256) {
return caps[id].allocation;
}
/* MULTICALL */
/// @dev Useful for EOAs to batch admin calls.
/// @dev Does not return anything, because accounts who would use the return data would be contracts, which can do
/// the multicall themselves.
function multicall(bytes[] calldata data) external {
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory returnData) = address(this).delegatecall(data[i]);
if (!success) {
assembly ("memory-safe") {
revert(add(32, returnData), mload(returnData))
}
}
}
}
/* CONSTRUCTOR */
constructor(address _owner, address _asset) {
asset = _asset;
owner = _owner;
lastUpdate = uint64(block.timestamp);
uint256 assetDecimals = IERC20(_asset).decimals();
uint256 decimalOffset = uint256(18).zeroFloorSub(assetDecimals);
decimals = uint8(assetDecimals + decimalOffset);
virtualShares = 10 ** decimalOffset;
emit EventsLib.Constructor(_owner, _asset);
}
/* OWNER FUNCTIONS */
function setOwner(address newOwner) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
owner = newOwner;
emit EventsLib.SetOwner(newOwner);
}
function setCurator(address newCurator) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
curator = newCurator;
emit EventsLib.SetCurator(newCurator);
}
function setIsSentinel(address account, bool newIsSentinel) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
isSentinel[account] = newIsSentinel;
emit EventsLib.SetIsSentinel(account, newIsSentinel);
}
function setName(string memory newName) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
name = newName;
emit EventsLib.SetName(newName);
}
function setSymbol(string memory newSymbol) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
symbol = newSymbol;
emit EventsLib.SetSymbol(newSymbol);
}
/* TIMELOCKS FOR CURATOR FUNCTIONS */
/// @dev Will revert if the timelock value is type(uint256).max or any value that overflows when added to the block
/// timestamp.
function submit(bytes calldata data) external {
require(msg.sender == curator, ErrorsLib.Unauthorized());
require(executableAt[data] == 0, ErrorsLib.DataAlreadyPending());
bytes4 selector = bytes4(data);
uint256 _timelock =
selector == IVaultV2.decreaseTimelock.selector ? timelock[bytes4(data[4:8])] : timelock[selector];
executableAt[data] = block.timestamp + _timelock;
emit EventsLib.Submit(selector, data, executableAt[data]);
}
function timelocked() internal {
bytes4 selector = bytes4(msg.data);
require(executableAt[msg.data] != 0, ErrorsLib.DataNotTimelocked());
require(block.timestamp >= executableAt[msg.data], ErrorsLib.TimelockNotExpired());
require(!abdicated[selector], ErrorsLib.Abdicated());
executableAt[msg.data] = 0;
emit EventsLib.Accept(selector, msg.data);
}
function revoke(bytes calldata data) external {
require(msg.sender == curator || isSentinel[msg.sender], ErrorsLib.Unauthorized());
require(executableAt[data] != 0, ErrorsLib.DataNotTimelocked());
executableAt[data] = 0;
bytes4 selector = bytes4(data);
emit EventsLib.Revoke(msg.sender, selector, data);
}
/* CURATOR FUNCTIONS */
function setIsAllocator(address account, bool newIsAllocator) external {
timelocked();
isAllocator[account] = newIsAllocator;
emit EventsLib.SetIsAllocator(account, newIsAllocator);
}
function setReceiveSharesGate(address newReceiveSharesGate) external {
timelocked();
receiveSharesGate = newReceiveSharesGate;
emit EventsLib.SetReceiveSharesGate(newReceiveSharesGate);
}
function setSendSharesGate(address newSendSharesGate) external {
timelocked();
sendSharesGate = newSendSharesGate;
emit EventsLib.SetSendSharesGate(newSendSharesGate);
}
function setReceiveAssetsGate(address newReceiveAssetsGate) external {
timelocked();
receiveAssetsGate = newReceiveAssetsGate;
emit EventsLib.SetReceiveAssetsGate(newReceiveAssetsGate);
}
function setSendAssetsGate(address newSendAssetsGate) external {
timelocked();
sendAssetsGate = newSendAssetsGate;
emit EventsLib.SetSendAssetsGate(newSendAssetsGate);
}
/// @dev The no-op will revert if the registry now returns false for an already added adapter.
function setAdapterRegistry(address newAdapterRegistry) external {
timelocked();
if (newAdapterRegistry != address(0)) {
for (uint256 i = 0; i < adapters.length; i++) {
require(
IAdapterRegistry(newAdapterRegistry).isInRegistry(adapters[i]), ErrorsLib.NotInAdapterRegistry()
);
}
}
adapterRegistry = newAdapterRegistry;
emit EventsLib.SetAdapterRegistry(newAdapterRegistry);
}
function addAdapter(address account) external {
timelocked();
require(
adapterRegistry == address(0) || IAdapterRegistry(adapterRegistry).isInRegistry(account),
ErrorsLib.NotInAdapterRegistry()
);
if (!isAdapter[account]) {
adapters.push(account);
isAdapter[account] = true;
}
emit EventsLib.AddAdapter(account);
}
function removeAdapter(address account) external {
timelocked();
if (isAdapter[account]) {
for (uint256 i = 0; i < adapters.length; i++) {
if (adapters[i] == account) {
adapters[i] = adapters[adapters.length - 1];
adapters.pop();
break;
}
}
isAdapter[account] = false;
}
emit EventsLib.RemoveAdapter(account);
}
/// @dev This function requires great caution because it can irreversibly disable submit for a selector.
/// @dev Existing pending operations submitted before increasing a timelock can still be executed at the initial
/// executableAt.
function increaseTimelock(bytes4 selector, uint256 newDuration) external {
timelocked();
require(selector != IVaultV2.decreaseTimelock.selector, ErrorsLib.AutomaticallyTimelocked());
require(newDuration >= timelock[selector], ErrorsLib.TimelockNotIncreasing());
timelock[selector] = newDuration;
emit EventsLib.IncreaseTimelock(selector, newDuration);
}
function decreaseTimelock(bytes4 selector, uint256 newDuration) external {
timelocked();
require(selector != IVaultV2.decreaseTimelock.selector, ErrorsLib.AutomaticallyTimelocked());
require(newDuration <= timelock[selector], ErrorsLib.TimelockNotDecreasing());
timelock[selector] = newDuration;
emit EventsLib.DecreaseTimelock(selector, newDuration);
}
function abdicate(bytes4 selector) external {
timelocked();
abdicated[selector] = true;
emit EventsLib.Abdicate(selector);
}
function setPerformanceFee(uint256 newPerformanceFee) external {
timelocked();
require(newPerformanceFee <= MAX_PERFORMANCE_FEE, ErrorsLib.FeeTooHigh());
require(performanceFeeRecipient != address(0) || newPerformanceFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
// Safe because 2**96 > MAX_PERFORMANCE_FEE.
performanceFee = uint96(newPerformanceFee);
emit EventsLib.SetPerformanceFee(newPerformanceFee);
}
function setManagementFee(uint256 newManagementFee) external {
timelocked();
require(newManagementFee <= MAX_MANAGEMENT_FEE, ErrorsLib.FeeTooHigh());
require(managementFeeRecipient != address(0) || newManagementFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
// Safe because 2**96 > MAX_MANAGEMENT_FEE.
managementFee = uint96(newManagementFee);
emit EventsLib.SetManagementFee(newManagementFee);
}
function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external {
timelocked();
require(newPerformanceFeeRecipient != address(0) || performanceFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
performanceFeeRecipient = newPerformanceFeeRecipient;
emit EventsLib.SetPerformanceFeeRecipient(newPerformanceFeeRecipient);
}
function setManagementFeeRecipient(address newManagementFeeRecipient) external {
timelocked();
require(newManagementFeeRecipient != address(0) || managementFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
managementFeeRecipient = newManagementFeeRecipient;
emit EventsLib.SetManagementFeeRecipient(newManagementFeeRecipient);
}
function increaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external {
timelocked();
bytes32 id = keccak256(idData);
require(newAbsoluteCap >= caps[id].absoluteCap, ErrorsLib.AbsoluteCapNotIncreasing());
caps[id].absoluteCap = newAbsoluteCap.toUint128();
emit EventsLib.IncreaseAbsoluteCap(id, idData, newAbsoluteCap);
}
function decreaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external {
bytes32 id = keccak256(idData);
require(msg.sender == curator || isSentinel[msg.sender], ErrorsLib.Unauthorized());
require(newAbsoluteCap <= caps[id].absoluteCap, ErrorsLib.AbsoluteCapNotDecreasing());
// Safe because newAbsoluteCap <= absoluteCap < 2**128.
caps[id].absoluteCap = uint128(newAbsoluteCap);
emit EventsLib.DecreaseAbsoluteCap(msg.sender, id, idData, newAbsoluteCap);
}
function increaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external {
timelocked();
bytes32 id = keccak256(idData);
require(newRelativeCap <= WAD, ErrorsLib.RelativeCapAboveOne());
require(newRelativeCap >= caps[id].relativeCap, ErrorsLib.RelativeCapNotIncreasing());
// Safe because WAD < 2**128.
caps[id].relativeCap = uint128(newRelativeCap);
emit EventsLib.IncreaseRelativeCap(id, idData, newRelativeCap);
}
function decreaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external {
bytes32 id = keccak256(idData);
require(msg.sender == curator || isSentinel[msg.sender], ErrorsLib.Unauthorized());
require(newRelativeCap <= caps[id].relativeCap, ErrorsLib.RelativeCapNotDecreasing());
// Safe because WAD < 2**128.
caps[id].relativeCap = uint128(newRelativeCap);
emit EventsLib.DecreaseRelativeCap(msg.sender, id, idData, newRelativeCap);
}
function setForceDeallocatePenalty(address adapter, uint256 newForceDeallocatePenalty) external {
timelocked();
require(newForceDeallocatePenalty <= MAX_FORCE_DEALLOCATE_PENALTY, ErrorsLib.PenaltyTooHigh());
forceDeallocatePenalty[adapter] = newForceDeallocatePenalty;
emit EventsLib.SetForceDeallocatePenalty(adapter, newForceDeallocatePenalty);
}
/* ALLOCATOR FUNCTIONS */
function allocate(address adapter, bytes memory data, uint256 assets) external {
require(isAllocator[msg.sender], ErrorsLib.Unauthorized());
allocateInternal(adapter, data, assets);
}
function allocateInternal(address adapter, bytes memory data, uint256 assets) internal {
require(isAdapter[adapter], ErrorsLib.NotAdapter());
accrueInterest();
SafeERC20Lib.safeTransfer(asset, adapter, assets);
(bytes32[] memory ids, int256 change) = IAdapter(adapter).allocate(data, assets, msg.sig, msg.sender);
for (uint256 i; i < ids.length; i++) {
Caps storage _caps = caps[ids[i]];
_caps.allocation = (int256(_caps.allocation) + change).toUint256();
require(_caps.absoluteCap > 0, ErrorsLib.ZeroAbsoluteCap());
require(_caps.allocation <= _caps.absoluteCap, ErrorsLib.AbsoluteCapExceeded());
require(
_caps.relativeCap == WAD || _caps.allocation <= firstTotalAssets.mulDivDown(_caps.relativeCap, WAD),
ErrorsLib.RelativeCapExceeded()
);
}
emit EventsLib.Allocate(msg.sender, adapter, assets, ids, change);
}
function deallocate(address adapter, bytes memory data, uint256 assets) external {
require(isAllocator[msg.sender] || isSentinel[msg.sender], ErrorsLib.Unauthorized());
deallocateInternal(adapter, data, assets);
}
function deallocateInternal(address adapter, bytes memory data, uint256 assets)
internal
returns (bytes32[] memory)
{
require(isAdapter[adapter], ErrorsLib.NotAdapter());
(bytes32[] memory ids, int256 change) = IAdapter(adapter).deallocate(data, assets, msg.sig, msg.sender);
for (uint256 i; i < ids.length; i++) {
Caps storage _caps = caps[ids[i]];
require(_caps.allocation > 0, ErrorsLib.ZeroAllocation());
_caps.allocation = (int256(_caps.allocation) + change).toUint256();
}
SafeERC20Lib.safeTransferFrom(asset, adapter, address(this), assets);
emit EventsLib.Deallocate(msg.sender, adapter, assets, ids, change);
return ids;
}
/// @dev Whether newLiquidityAdapter is an adapter is checked in allocate/deallocate.
function setLiquidityAdapterAndData(address newLiquidityAdapter, bytes memory newLiquidityData) external {
require(isAllocator[msg.sender], ErrorsLib.Unauthorized());
liquidityAdapter = newLiquidityAdapter;
liquidityData = newLiquidityData;
emit EventsLib.SetLiquidityAdapterAndData(msg.sender, newLiquidityAdapter, newLiquidityData);
}
function setMaxRate(uint256 newMaxRate) external {
require(isAllocator[msg.sender], ErrorsLib.Unauthorized());
require(newMaxRate <= MAX_MAX_RATE, ErrorsLib.MaxRateTooHigh());
accrueInterest();
// Safe because newMaxRate <= MAX_MAX_RATE < 2**64-1.
maxRate = uint64(newMaxRate);
emit EventsLib.SetMaxRate(newMaxRate);
}
/* EXCHANGE RATE FUNCTIONS */
function accrueInterest() public {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
emit EventsLib.AccrueInterest(_totalAssets, newTotalAssets, performanceFeeShares, managementFeeShares);
_totalAssets = newTotalAssets.toUint128();
if (firstTotalAssets == 0) firstTotalAssets = newTotalAssets;
if (performanceFeeShares != 0) createShares(performanceFeeRecipient, performanceFeeShares);
if (managementFeeShares != 0) createShares(managementFeeRecipient, managementFeeShares);
lastUpdate = uint64(block.timestamp);
}
/// @dev Returns newTotalAssets, performanceFeeShares, managementFeeShares.
/// @dev The management fee is not bound to the interest, so it can make the share price go down.
/// @dev The management fees is taken even if the vault incurs some losses.
/// @dev Both fees are rounded down, so fee recipients could receive less than expected.
/// @dev The performance fee is taken on the "distributed interest" (which differs from the "real interest" because
/// of the max rate).
function accrueInterestView() public view returns (uint256, uint256, uint256) {
if (firstTotalAssets != 0) return (_totalAssets, 0, 0);
uint256 elapsed = block.timestamp - lastUpdate;
uint256 realAssets = IERC20(asset).balanceOf(address(this));
for (uint256 i = 0; i < adapters.length; i++) {
realAssets += IAdapter(adapters[i]).realAssets();
}
uint256 maxTotalAssets = _totalAssets + (_totalAssets * elapsed).mulDivDown(maxRate, WAD);
uint256 newTotalAssets = MathLib.min(realAssets, maxTotalAssets);
uint256 interest = newTotalAssets.zeroFloorSub(_totalAssets);
// The performance fee assets may be rounded down to 0 if interest * fee < WAD.
uint256 performanceFeeAssets = interest > 0 && performanceFee > 0 && canReceiveShares(performanceFeeRecipient)
? interest.mulDivDown(performanceFee, WAD)
: 0;
// The management fee is taken on newTotalAssets to make all approximations consistent (interacting less
// increases fees).
uint256 managementFeeAssets = elapsed > 0 && managementFee > 0 && canReceiveShares(managementFeeRecipient)
? (newTotalAssets * elapsed).mulDivDown(managementFee, WAD)
: 0;
// Interest should be accrued at least every 10 years to avoid fees exceeding total assets.
uint256 newTotalAssetsWithoutFees = newTotalAssets - performanceFeeAssets - managementFeeAssets;
uint256 performanceFeeShares =
performanceFeeAssets.mulDivDown(totalSupply + virtualShares, newTotalAssetsWithoutFees + 1);
uint256 managementFeeShares =
managementFeeAssets.mulDivDown(totalSupply + virtualShares, newTotalAssetsWithoutFees + 1);
return (newTotalAssets, performanceFeeShares, managementFeeShares);
}
/// @dev Returns previewed minted shares.
function previewDeposit(uint256 assets) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return assets.mulDivDown(newTotalSupply + virtualShares, newTotalAssets + 1);
}
/// @dev Returns previewed deposited assets.
function previewMint(uint256 shares) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return shares.mulDivUp(newTotalAssets + 1, newTotalSupply + virtualShares);
}
/// @dev Returns previewed redeemed shares.
function previewWithdraw(uint256 assets) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return assets.mulDivUp(newTotalSupply + virtualShares, newTotalAssets + 1);
}
/// @dev Returns previewed withdrawn assets.
function previewRedeem(uint256 shares) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return shares.mulDivDown(newTotalAssets + 1, newTotalSupply + virtualShares);
}
/// @dev Returns corresponding shares (rounded down).
/// @dev Takes into account performance and management fees.
function convertToShares(uint256 assets) external view returns (uint256) {
return previewDeposit(assets);
}
/// @dev Returns corresponding assets (rounded down).
/// @dev Takes into account performance and management fees.
function convertToAssets(uint256 shares) external view returns (uint256) {
return previewRedeem(shares);
}
/* MAX FUNCTIONS */
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxDeposit(address) external pure returns (uint256) {
return 0;
}
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxMint(address) external pure returns (uint256) {
return 0;
}
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxWithdraw(address) external pure returns (uint256) {
return 0;
}
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxRedeem(address) external pure returns (uint256) {
return 0;
}
/* USER MAIN FUNCTIONS */
/// @dev Returns minted shares.
function deposit(uint256 assets, address onBehalf) external returns (uint256) {
accrueInterest();
uint256 shares = previewDeposit(assets);
enter(assets, shares, onBehalf);
return shares;
}
/// @dev Returns deposited assets.
function mint(uint256 shares, address onBehalf) external returns (uint256) {
accrueInterest();
uint256 assets = previewMint(shares);
enter(assets, shares, onBehalf);
return assets;
}
/// @dev Internal function for deposit and mint.
function enter(uint256 assets, uint256 shares, address onBehalf) internal {
require(canReceiveShares(onBehalf), ErrorsLib.CannotReceiveShares());
require(canSendAssets(msg.sender), ErrorsLib.CannotSendAssets());
SafeERC20Lib.safeTransferFrom(asset, msg.sender, address(this), assets);
createShares(onBehalf, shares);
_totalAssets += assets.toUint128();
emit EventsLib.Deposit(msg.sender, onBehalf, assets, shares);
if (liquidityAdapter != address(0)) allocateInternal(liquidityAdapter, liquidityData, assets);
}
/// @dev Returns redeemed shares.
function withdraw(uint256 assets, address receiver, address onBehalf) public returns (uint256) {
accrueInterest();
uint256 shares = previewWithdraw(assets);
exit(assets, shares, receiver, onBehalf);
return shares;
}
/// @dev Returns withdrawn assets.
function redeem(uint256 shares, address receiver, address onBehalf) external returns (uint256) {
accrueInterest();
uint256 assets = previewRedeem(shares);
exit(assets, shares, receiver, onBehalf);
return assets;
}
/// @dev Internal function for withdraw and redeem.
function exit(uint256 assets, uint256 shares, address receiver, address onBehalf) internal {
require(canSendShares(onBehalf), ErrorsLib.CannotSendShares());
require(canReceiveAssets(receiver), ErrorsLib.CannotReceiveAssets());
uint256 idleAssets = IERC20(asset).balanceOf(address(this));
if (assets > idleAssets && liquidityAdapter != address(0)) {
deallocateInternal(liquidityAdapter, liquidityData, assets - idleAssets);
}
if (msg.sender != onBehalf) {
uint256 _allowance = allowance[onBehalf][msg.sender];
if (_allowance != type(uint256).max) allowance[onBehalf][msg.sender] = _allowance - shares;
}
deleteShares(onBehalf, shares);
_totalAssets -= assets.toUint128();
SafeERC20Lib.safeTransfer(asset, receiver, assets);
emit EventsLib.Withdraw(msg.sender, receiver, onBehalf, assets, shares);
}
/// @dev Returns shares withdrawn as penalty.
/// @dev When calling this function, a penalty is taken from onBehalf, in order to discourage allocation
/// manipulations.
/// @dev The penalty is taken as a withdrawal for which assets are returned to the vault. In consequence,
/// totalAssets is decreased normally along with totalSupply (the share price doesn't change except because of
/// rounding errors), but the amount of assets actually controlled by the vault is not decreased.
/// @dev If a user has A assets in the vault, and that the vault is already fully illiquid, the optimal amount to
/// force deallocate in order to exit the vault is min(liquidity_of_market, A / (1 + penalty)).
/// This ensures that either the market is empty or that it leaves no shares nor liquidity after exiting.
function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf)
external
returns (uint256)
{
bytes32[] memory ids = deallocateInternal(adapter, data, assets);
uint256 penaltyAssets = assets.mulDivUp(forceDeallocatePenalty[adapter], WAD);
uint256 penaltyShares = withdraw(penaltyAssets, address(this), onBehalf);
emit EventsLib.ForceDeallocate(msg.sender, adapter, assets, onBehalf, ids, penaltyAssets);
return penaltyShares;
}
/* ERC20 FUNCTIONS */
/// @dev Returns success (always true because reverts on failure).
function transfer(address to, uint256 shares) external returns (bool) {
require(to != address(0), ErrorsLib.ZeroAddress());
require(canSendShares(msg.sender), ErrorsLib.CannotSendShares());
require(canReceiveShares(to), ErrorsLib.CannotReceiveShares());
balanceOf[msg.sender] -= shares;
balanceOf[to] += shares;
emit EventsLib.Transfer(msg.sender, to, shares);
return true;
}
/// @dev Returns success (always true because reverts on failure).
function transferFrom(address from, address to, uint256 shares) external returns (bool) {
require(from != address(0), ErrorsLib.ZeroAddress());
require(to != address(0), ErrorsLib.ZeroAddress());
require(canSendShares(from), ErrorsLib.CannotSendShares());
require(canReceiveShares(to), ErrorsLib.CannotReceiveShares());
if (msg.sender != from) {
uint256 _allowance = allowance[from][msg.sender];
if (_allowance != type(uint256).max) {
allowance[from][msg.sender] = _allowance - shares;
emit EventsLib.AllowanceUpdatedByTransferFrom(from, msg.sender, _allowance - shares);
}
}
balanceOf[from] -= shares;
balanceOf[to] += shares;
emit EventsLib.Transfer(from, to, shares);
return true;
}
/// @dev Returns success (always true because reverts on failure).
function approve(address spender, uint256 shares) external returns (bool) {
allowance[msg.sender][spender] = shares;
emit EventsLib.Approval(msg.sender, spender, shares);
return true;
}
/// @dev Signature malleability is not explicitly prevented but it is not a problem thanks to the nonce.
function permit(address _owner, address spender, uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external
{
require(deadline >= block.timestamp, ErrorsLib.PermitDeadlineExpired());
uint256 nonce = nonces[_owner]++;
bytes32 hashStruct = keccak256(abi.encode(PERMIT_TYPEHASH, _owner, spender, shares, nonce, deadline));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), hashStruct));
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == _owner, ErrorsLib.InvalidSigner());
allowance[_owner][spender] = shares;
emit EventsLib.Approval(_owner, spender, shares);
emit EventsLib.Permit(_owner, spender, shares, nonce, deadline);
}
function createShares(address to, uint256 shares) internal {
require(to != address(0), ErrorsLib.ZeroAddress());
balanceOf[to] += shares;
totalSupply += shares;
emit EventsLib.Transfer(address(0), to, shares);
}
function deleteShares(address from, uint256 shares) internal {
require(from != address(0), ErrorsLib.ZeroAddress());
balanceOf[from] -= shares;
totalSupply -= shares;
emit EventsLib.Transfer(from, address(0), shares);
}
/* PERMISSIONED TOKEN FUNCTIONS */
function canReceiveShares(address account) public view returns (bool) {
return receiveSharesGate == address(0) || IReceiveSharesGate(receiveSharesGate).canReceiveShares(account);
}
function canSendShares(address account) public view returns (bool) {
return sendSharesGate == address(0) || ISendSharesGate(sendSharesGate).canSendShares(account);
}
function canReceiveAssets(address account) public view returns (bool) {
return account == address(this) || receiveAssetsGate == address(0)
|| IReceiveAssetsGate(receiveAssetsGate).canReceiveAssets(account);
}
function canSendAssets(address account) public view returns (bool) {
return sendAssetsGate == address(0) || ISendAssetsGate(sendAssetsGate).canSendAssets(account);
}
}
IAdapter.sol 19 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
/// @dev See VaultV2 NatSpec comments for more details on adapter's spec.
interface IAdapter {
/// @dev Returns the market' ids and the change in assets on this market.
function allocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
external
returns (bytes32[] memory ids, int256 change);
/// @dev Returns the market' ids and the change in assets on this market.
function deallocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
external
returns (bytes32[] memory ids, int256 change);
/// @dev Returns the current value of the investments of the adapter (in underlying asset).
function realAssets() external view returns (uint256 assets);
}
IAdapterRegistry.sol 7 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IAdapterRegistry {
function isInRegistry(address account) external view returns (bool);
}
IERC20.sol 15 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IERC20 {
function decimals() external view returns (uint8);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 shares) external returns (bool success);
function transferFrom(address from, address to, uint256 shares) external returns (bool success);
function approve(address spender, uint256 shares) external returns (bool success);
function allowance(address owner, address spender) external view returns (uint256);
}
IERC2612.sol 10 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IERC2612 {
function permit(address owner, address spender, uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
IERC4626.sol 24 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
import {IERC20} from "./IERC20.sol";
interface IERC4626 is IERC20 {
function asset() external view returns (address);
function totalAssets() external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function deposit(uint256 assets, address onBehalf) external returns (uint256 shares);
function mint(uint256 shares, address onBehalf) external returns (uint256 assets);
function withdraw(uint256 assets, address onBehalf, address receiver) external returns (uint256 shares);
function redeem(uint256 shares, address onBehalf, address receiver) external returns (uint256 assets);
function previewDeposit(uint256 assets) external view returns (uint256 shares);
function previewMint(uint256 shares) external view returns (uint256 assets);
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
function previewRedeem(uint256 shares) external view returns (uint256 assets);
function maxDeposit(address onBehalf) external view returns (uint256 assets);
function maxMint(address onBehalf) external view returns (uint256 shares);
function maxWithdraw(address onBehalf) external view returns (uint256 assets);
function maxRedeem(address onBehalf) external view returns (uint256 shares);
}
IGate.sol 19 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IReceiveSharesGate {
function canReceiveShares(address account) external view returns (bool);
}
interface ISendSharesGate {
function canSendShares(address account) external view returns (bool);
}
interface IReceiveAssetsGate {
function canReceiveAssets(address account) external view returns (bool);
}
interface ISendAssetsGate {
function canSendAssets(address account) external view returns (bool);
}
IVaultV2.sol 107 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
import {IERC20} from "./IERC20.sol";
import {IERC4626} from "./IERC4626.sol";
import {IERC2612} from "./IERC2612.sol";
struct Caps {
uint256 allocation;
uint128 absoluteCap;
uint128 relativeCap;
}
interface IVaultV2 is IERC4626, IERC2612 {
// State variables
function virtualShares() external view returns (uint256);
function owner() external view returns (address);
function curator() external view returns (address);
function receiveSharesGate() external view returns (address);
function sendSharesGate() external view returns (address);
function receiveAssetsGate() external view returns (address);
function sendAssetsGate() external view returns (address);
function adapterRegistry() external view returns (address);
function isSentinel(address account) external view returns (bool);
function isAllocator(address account) external view returns (bool);
function firstTotalAssets() external view returns (uint256);
function _totalAssets() external view returns (uint128);
function lastUpdate() external view returns (uint64);
function maxRate() external view returns (uint64);
function adapters(uint256 index) external view returns (address);
function adaptersLength() external view returns (uint256);
function isAdapter(address account) external view returns (bool);
function allocation(bytes32 id) external view returns (uint256);
function absoluteCap(bytes32 id) external view returns (uint256);
function relativeCap(bytes32 id) external view returns (uint256);
function forceDeallocatePenalty(address adapter) external view returns (uint256);
function liquidityAdapter() external view returns (address);
function liquidityData() external view returns (bytes memory);
function timelock(bytes4 selector) external view returns (uint256);
function abdicated(bytes4 selector) external view returns (bool);
function executableAt(bytes memory data) external view returns (uint256);
function performanceFee() external view returns (uint96);
function performanceFeeRecipient() external view returns (address);
function managementFee() external view returns (uint96);
function managementFeeRecipient() external view returns (address);
// Gating
function canSendShares(address account) external view returns (bool);
function canReceiveShares(address account) external view returns (bool);
function canSendAssets(address account) external view returns (bool);
function canReceiveAssets(address account) external view returns (bool);
// Multicall
function multicall(bytes[] memory data) external;
// Owner functions
function setOwner(address newOwner) external;
function setCurator(address newCurator) external;
function setIsSentinel(address account, bool isSentinel) external;
function setName(string memory newName) external;
function setSymbol(string memory newSymbol) external;
// Timelocks for curator functions
function submit(bytes memory data) external;
function revoke(bytes memory data) external;
// Curator functions
function setIsAllocator(address account, bool newIsAllocator) external;
function setReceiveSharesGate(address newReceiveSharesGate) external;
function setSendSharesGate(address newSendSharesGate) external;
function setReceiveAssetsGate(address newReceiveAssetsGate) external;
function setSendAssetsGate(address newSendAssetsGate) external;
function setAdapterRegistry(address newAdapterRegistry) external;
function addAdapter(address account) external;
function removeAdapter(address account) external;
function increaseTimelock(bytes4 selector, uint256 newDuration) external;
function decreaseTimelock(bytes4 selector, uint256 newDuration) external;
function abdicate(bytes4 selector) external;
function setPerformanceFee(uint256 newPerformanceFee) external;
function setManagementFee(uint256 newManagementFee) external;
function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external;
function setManagementFeeRecipient(address newManagementFeeRecipient) external;
function increaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external;
function decreaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external;
function increaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external;
function decreaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external;
function setMaxRate(uint256 newMaxRate) external;
function setForceDeallocatePenalty(address adapter, uint256 newForceDeallocatePenalty) external;
// Allocator functions
function allocate(address adapter, bytes memory data, uint256 assets) external;
function deallocate(address adapter, bytes memory data, uint256 assets) external;
function setLiquidityAdapterAndData(address newLiquidityAdapter, bytes memory newLiquidityData) external;
// Exchange rate
function accrueInterest() external;
function accrueInterestView()
external
view
returns (uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares);
// Force deallocate
function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf)
external
returns (uint256 penaltyShares);
}
ConstantsLib.sol 12 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
uint256 constant WAD = 1e18;
bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
bytes32 constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
uint256 constant MAX_MAX_RATE = 200e16 / uint256(365 days); // 200% APR
uint256 constant MAX_PERFORMANCE_FEE = 0.5e18; // 50%
uint256 constant MAX_MANAGEMENT_FEE = 0.05e18 / uint256(365 days); // 5%
uint256 constant MAX_FORCE_DEALLOCATE_PENALTY = 0.02e18; // 2%
ErrorsLib.sol 45 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
library ErrorsLib {
error Abdicated();
error AbsoluteCapExceeded();
error AbsoluteCapNotDecreasing();
error AbsoluteCapNotIncreasing();
error ApproveReturnedFalse();
error ApproveReverted();
error CannotReceiveShares();
error CannotReceiveAssets();
error CannotSendShares();
error CannotSendAssets();
error CapExceeded();
error CastOverflow();
error DataAlreadyPending();
error DataNotTimelocked();
error FeeInvariantBroken();
error FeeTooHigh();
error InvalidSigner();
error MaxRateTooHigh();
error NoCode();
error NotAdapter();
error NotInAdapterRegistry();
error PenaltyTooHigh();
error PermitDeadlineExpired();
error RelativeCapAboveOne();
error RelativeCapExceeded();
error RelativeCapNotDecreasing();
error RelativeCapNotIncreasing();
error AutomaticallyTimelocked();
error TimelockNotDecreasing();
error TimelockNotExpired();
error TimelockNotIncreasing();
error TransferFromReturnedFalse();
error TransferFromReverted();
error TransferReturnedFalse();
error TransferReverted();
error Unauthorized();
error ZeroAbsoluteCap();
error ZeroAddress();
error ZeroAllocation();
}
EventsLib.sol 75 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
library EventsLib {
// ERC20 events
event Approval(address indexed owner, address indexed spender, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 shares);
/// @dev Emitted when the allowance is updated by transferFrom (not when it is updated by permit, approve, withdraw,
/// redeem because their respective events allow to track the allowance).
event AllowanceUpdatedByTransferFrom(address indexed owner, address indexed spender, uint256 shares);
event Permit(address indexed owner, address indexed spender, uint256 shares, uint256 nonce, uint256 deadline);
// ERC4626 events
event Deposit(address indexed sender, address indexed onBehalf, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender, address indexed receiver, address indexed onBehalf, uint256 assets, uint256 shares
);
// Vault creation events
event Constructor(address indexed owner, address indexed asset);
// Allocation events
event Allocate(address indexed sender, address indexed adapter, uint256 assets, bytes32[] ids, int256 change);
event Deallocate(address indexed sender, address indexed adapter, uint256 assets, bytes32[] ids, int256 change);
event ForceDeallocate(
address indexed sender,
address adapter,
uint256 assets,
address indexed onBehalf,
bytes32[] ids,
uint256 penaltyAssets
);
// Fee and interest events
event AccrueInterest(
uint256 previousTotalAssets, uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares
);
// Timelock events
event Revoke(address indexed sender, bytes4 indexed selector, bytes data);
event Submit(bytes4 indexed selector, bytes data, uint256 executableAt);
event Accept(bytes4 indexed selector, bytes data);
// Configuration events
event SetOwner(address indexed newOwner);
event SetCurator(address indexed newCurator);
event SetIsSentinel(address indexed account, bool newIsSentinel);
event SetName(string newName);
event SetSymbol(string newSymbol);
event SetIsAllocator(address indexed account, bool newIsAllocator);
event SetReceiveSharesGate(address indexed newReceiveSharesGate);
event SetSendSharesGate(address indexed newSendSharesGate);
event SetReceiveAssetsGate(address indexed newReceiveAssetsGate);
event SetSendAssetsGate(address indexed newSendAssetsGate);
event SetAdapterRegistry(address indexed newAdapterRegistry);
event AddAdapter(address indexed account);
event RemoveAdapter(address indexed account);
event DecreaseTimelock(bytes4 indexed selector, uint256 newDuration);
event IncreaseTimelock(bytes4 indexed selector, uint256 newDuration);
event Abdicate(bytes4 indexed selector);
event SetLiquidityAdapterAndData(
address indexed sender, address indexed newLiquidityAdapter, bytes indexed newLiquidityData
);
event SetPerformanceFee(uint256 newPerformanceFee);
event SetPerformanceFeeRecipient(address indexed newPerformanceFeeRecipient);
event SetManagementFee(uint256 newManagementFee);
event SetManagementFeeRecipient(address indexed newManagementFeeRecipient);
event DecreaseAbsoluteCap(address indexed sender, bytes32 indexed id, bytes idData, uint256 newAbsoluteCap);
event IncreaseAbsoluteCap(bytes32 indexed id, bytes idData, uint256 newAbsoluteCap);
event DecreaseRelativeCap(address indexed sender, bytes32 indexed id, bytes idData, uint256 newRelativeCap);
event IncreaseRelativeCap(bytes32 indexed id, bytes idData, uint256 newRelativeCap);
event SetMaxRate(uint256 newMaxRate);
event SetForceDeallocatePenalty(address indexed adapter, uint256 forceDeallocatePenalty);
}
MathLib.sol 43 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
import {ErrorsLib} from "./ErrorsLib.sol";
library MathLib {
/// @dev Returns (x * y) / d rounded down.
function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
return (x * y) / d;
}
/// @dev Returns (x * y) / d rounded up.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
return (x * y + (d - 1)) / d;
}
/// @dev Returns max(0, x - y).
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Casts from uint256 to uint128, reverting if input number is too large.
function toUint128(uint256 x) internal pure returns (uint128) {
require(x <= type(uint128).max, ErrorsLib.CastOverflow());
return uint128(x);
}
/// @dev Casts from int256 to uint256, reverting if input number is negative.
function toUint256(int256 x) internal pure returns (uint256) {
require(x >= 0, ErrorsLib.CastOverflow());
return uint256(x);
}
/// @dev Returns min(x, y).
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
}
SafeERC20Lib.sol 32 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
import {IERC20} from "../interfaces/IERC20.sol";
import {ErrorsLib} from "./ErrorsLib.sol";
library SafeERC20Lib {
function safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.transfer, (to, value)));
require(success, ErrorsLib.TransferReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TransferReturnedFalse());
}
function safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
require(success, ErrorsLib.TransferFromReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TransferFromReturnedFalse());
}
function safeApprove(address token, address spender, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.approve, (spender, value)));
require(success, ErrorsLib.ApproveReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.ApproveReturnedFalse());
}
}
VaultV2.sol 934 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity 0.8.28;
import {IERC20} from "./interfaces/IERC20.sol";
import {IVaultV2, Caps} from "./interfaces/IVaultV2.sol";
import {IAdapter} from "./interfaces/IAdapter.sol";
import {IAdapterRegistry} from "./interfaces/IAdapterRegistry.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {EventsLib} from "./libraries/EventsLib.sol";
import "./libraries/ConstantsLib.sol"; // forge-lint: disable-line(unaliased-plain-import)
import {MathLib} from "./libraries/MathLib.sol";
import {SafeERC20Lib} from "./libraries/SafeERC20Lib.sol";
import {IReceiveSharesGate, ISendSharesGate, IReceiveAssetsGate, ISendAssetsGate} from "./interfaces/IGate.sol";
/// ERC4626
/// @dev The vault is compliant with ERC-4626 and with ERC-2612 (permit extension). Though the vault has a
/// non-conventional behaviour on max functions: they always return zero.
/// @dev totalSupply is not updated to include shares minted to fee recipients. One can call accrueInterestView to
/// compute the updated totalSupply.
///
/// TOTAL ASSETS
/// @dev Adapters are responsible for reporting to the vault how much their investments are worth at any time, so that
/// the vault can accrue interest or realize losses.
/// @dev _totalAssets stores the last recorded total assets. Use totalAssets() for the updated total assets.
/// @dev Upon interest accrual, the vault loops through adapters' realAssets(). If there are too many adapters and/or
/// they consume too much gas on realAssets(), it could cause issues such as expensive interactions, even DOS.
///
/// LOSS REALIZATION
/// @dev Loss realization occurs in accrueInterest and decreases the total assets, causing shares to lose value.
/// @dev Vault shares should not be loanable to prevent shares shorting on loss realization. Shares can be flashloanable
/// because flashloan-based shorting is prevented as interests and losses are only accounted once per transaction.
///
/// SHARE PRICE
/// @dev The share price can go down if the vault incurs some losses.
/// @dev To get some additional bounds on the share price upon interactions, a check must be performed on top.
/// @dev Interest/loss are accounted only once per transaction (at the first interaction with the vault).
/// @dev Donations increase the share price but not faster than the maxRate.
/// @dev The vault has 1 virtual asset and a decimal offset of max(0, 18 - assetDecimals). In order to protect against
/// inflation attacks, the vault might need to be seeded with an initial deposit. See
/// https://docs.openzeppelin.com/contracts/5.x/erc4626#inflation-attack
/// @dev Donations and forceDeallocate penalties increase the rate, which can attract opportunistic depositors which
/// will dilute interest. This fact can be mitigated by reducing the maxRate.
///
/// CAPS
/// @dev Ids have an asset allocation, and can be absolutely capped and/or relatively capped.
/// @dev The allocation is not always up to date, because interest and losses are accounted only when (de)allocating in
/// the corresponding markets.
/// @dev The caps are checked on allocate (where allocations can increase) for the ids returned by the adapter.
/// @dev Relative caps are "soft" in the sense that they are not checked on exit.
/// @dev Caps can be exceeded because of interest and donations in adapters (if adapters do not prevent them).
/// @dev The relative cap is relative to firstTotalAssets, not realAssets.
/// @dev The relative cap unit is WAD.
/// @dev To track allocations using events, use the Allocate and Deallocate events only.
///
/// FIRST TOTAL ASSETS
/// @dev The variable firstTotalAssets tracks the total assets after the first interest accrual of the transaction.
/// @dev Used to implement a mechanism that prevents bypassing relative caps with flashloans. This mechanism makes the
/// caps conservative and can generate false positives, notably for big deposits that go through the liquidity adapter.
/// @dev Also used to accrue interest only once per transaction (see the "share price" section).
/// @dev Relative caps can still be manipulated by allocators (with short-term deposits), but it requires capital.
/// @dev The behavior of firstTotalAssets is different when the vault has totalAssets=0, but it does not matter
/// internally because in this case there are no investments to cap.
///
/// ADAPTERS
/// @dev Loose specification of adapters:
/// - They must enforce that only the vault can call allocate/deallocate.
/// - They must enter/exit markets only in allocate/deallocate.
/// - They must return the right ids on allocate/deallocate. Returned ids must not repeat.
/// - After a call to deallocate, the vault must have an approval to transfer at least `assets` from the adapter.
/// - They must make it possible to make deallocate possible (for in-kind redemptions).
/// - The totalAssets() calculation ignores markets for which the vault has no allocation.
/// - They must not re-enter (directly or indirectly) the vault. They might not statically prevent it, but the curator
/// must not interact with markets that can re-enter the vault.
/// - After an update, the sum of the changes returned after interactions with a given market must be exactly the
/// current estimated position.
/// @dev Ids being reused are useful to cap multiple investments that have a common property.
/// @dev Allocating is prevented if one of the ids' absolute cap is zero and deallocating is prevented if the id's
/// allocation is zero. This prevents interactions with zero assets with unknown markets. For markets that share all
/// their ids, it will be impossible to "disable" them (preventing any interaction) without disabling the others using
/// the same ids.
/// @dev On allocate or deallocate, the adapters might lose some assets (total realAssets decreases), for instance due
/// to roundings or entry/exit fees. This loss should stay negligible compared to gas. Adapters might not statically
/// ensure this, but the curators should not interact with markets that can create big entry/exit losses.
/// @dev Except particular scenarios, adapters should be removed only if they have no assets. In order to ensure no
/// allocator can allocate some assets in an adapter being removed, there should be an id exclusive to the adapter with
/// its cap set to zero.
///
/// ADAPTER REGISTRY
/// @dev An adapter registry can be added to restrict the adapters. This is useful to commit to using only a certain
/// type of adapters for example.
/// @dev If adapterRegistry is set to address(0), the vault can have any adapters.
/// @dev When an adapterRegistry is set, it retroactively checks already added adapters.
/// @dev If the adapterRegistry now returns false for an already added adapter, it doesn't impact the vault's
/// functioning.
/// @dev The invariant that adapters of the vault are all in the registry holds only if the registry cannot remove
/// adapters (is "add only").
///
/// LIQUIDITY ADAPTER
/// @dev Liquidity is allocated to the liquidityAdapter on deposit/mint, and deallocated from the liquidityAdapter on
/// withdraw/redeem if idle assets don't cover the withdrawal.
/// @dev The liquidity adapter is useful on exit, so that exit liquidity is available in addition to the idle assets.
/// But the same adapter/data is used for both entry and exit to have the property that in the general case looping
/// supply-withdraw or withdraw-supply should not change the allocation.
/// @dev If a cap (absolute or relative) associated with the ids returned by the liquidity adapter on the liquidity data
/// is reached, deposit/mint will revert. In particular, when the vault is empty or almost empty, the relative cap check
/// is likely to make deposits revert.
///
/// TOKEN REQUIREMENTS
/// @dev List of assumptions on the token that guarantees that the vault behaves as expected:
/// - It should be ERC-20 compliant, except that it can omit return values on transfer and transferFrom.
/// - The balance of the vault should only decrease on transfer and transferFrom.
/// - It should not re-enter the vault on transfer or transferFrom.
/// - The balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount on
/// transfer and transferFrom. In particular, tokens with fees on transfer are not supported.
///
/// LIVENESS REQUIREMENTS
/// @dev List of assumptions that guarantees the vault's liveness properties:
/// - Adapters should not revert on realAssets.
/// - The token should not revert on transfer and transferFrom if balances and approvals are right.
/// - The token should not revert on transfer to self.
/// - totalAssets and totalSupply must stay below ~10^35. Initially there are min(1, 10^(18-decimals)) shares per asset.
/// - The vault is pinged at least every 10 years.
/// - Adapters must not revert on deallocate if the underlying markets are liquid.
///
/// TIMELOCKS
/// @dev The timelock duration of decreaseTimelock is the timelock duration of the function whose timelock is being
/// decreased (e.g. the timelock of decreaseTimelock(addAdapter, ...) is timelock[addAdapter]).
/// @dev It is still possible to submit changes of the timelock duration of decreaseTimelock, but it won't have any
/// effect (and trying to execute this change will revert).
/// @dev Multiple clashing data can be pending, for example increaseCap and decreaseCap, which can make so accepted
/// timelocked data can potentially be changed shortly afterwards.
/// @dev If a function is abdicated, it cannot be called no matter its timelock and what executableAt[data] contains.
/// Otherwise, the minimum time in which a function can be called is the following:
/// min(
/// timelock[selector],
/// executableAt[selector::_],
/// executableAt[decreaseTimelock::selector::newTimelock] + newTimelock
/// ).
/// @dev Nothing is checked on the timelocked data, so it could be not executable (function does not exist, argument
/// encoding is wrong, function' conditions are not met, etc.).
///
/// ABDICATION
/// @dev When a timelocked function is abdicated, it can't be called anymore.
/// @dev It is still possible to submit data for it or change its timelock, but it will not be executable / effective.
///
/// GATES
/// @dev Set to 0 to disable a gate.
/// @dev Gates must never revert, nor consume too much gas.
/// @dev receiveSharesGate:
/// - Gates receiving shares.
/// - Can lock users out of getting back their shares deposited on an other contract.
/// @dev sendSharesGate:
/// - Gates sending shares.
/// - Can lock users out of exiting the vault.
/// @dev receiveAssetsGate:
/// - Gates withdrawing assets from the vault.
/// - The vault itself (address(this)) is always allowed to receive assets, regardless of the gate configuration.
/// - Can lock users out of exiting the vault.
/// @dev sendAssetsGate:
/// - Gates depositing assets to the vault.
/// - This gate is not critical (cannot block users' funds), while still being able to gate supplies.
///
/// FEES
/// @dev Fees unit is WAD.
/// @dev This invariant holds for both fees: fee != 0 => recipient != address(0).
///
/// ROLES
/// @dev The owner cannot do actions that can directly hurt depositors. Though it can set the curator and sentinels.
/// @dev The curator cannot do actions that can directly hurt depositors without going through a timelock.
/// @dev Allocators can move funds between markets in the boundaries set by caps without going through timelocks. They
/// can also set the liquidity adapter and data, which can prevent deposits and/or withdrawals (it cannot prevent
/// "in-kind redemptions" with forceDeallocate though). Allocators also set the maxRate.
/// @dev Warning: if setIsAllocator is timelocked, removing an allocator will take time.
/// @dev Roles are not "two-step", so anyone can give a role to anyone, but it does not mean that they will exercise it.
///
/// MISC
/// @dev Zero checks are not systematically performed.
/// @dev No-ops are allowed.
/// @dev NatSpec comments are included only when they bring clarity.
/// @dev The contract uses transient storage.
/// @dev View calls made by this contract should not rely on the executableAt values.
/// @dev At creation, all settings are set to their default values. Notably, timelocks are zero which is useful to set
/// up the vault quickly. Also, there are no gates so anybody can interact with the vault. To prevent that, the gates
/// configuration can be batched with the vault creation.
contract VaultV2 is IVaultV2 {
using MathLib for uint256;
using MathLib for uint128;
using MathLib for int256;
/* IMMUTABLE */
address public immutable asset;
uint8 public immutable decimals;
uint256 public immutable virtualShares;
/* ROLES STORAGE */
address public owner;
address public curator;
address public receiveSharesGate;
address public sendSharesGate;
address public receiveAssetsGate;
address public sendAssetsGate;
address public adapterRegistry;
mapping(address account => bool) public isSentinel;
mapping(address account => bool) public isAllocator;
/* TOKEN STORAGE */
string public name;
string public symbol;
uint256 public totalSupply;
mapping(address account => uint256) public balanceOf;
mapping(address owner => mapping(address spender => uint256)) public allowance;
mapping(address account => uint256) public nonces;
/* INTEREST STORAGE */
uint256 public transient firstTotalAssets;
uint128 public _totalAssets;
uint64 public lastUpdate;
uint64 public maxRate;
/* CURATION STORAGE */
mapping(address account => bool) public isAdapter;
address[] public adapters;
mapping(bytes32 id => Caps) internal caps;
mapping(address adapter => uint256) public forceDeallocatePenalty;
/* LIQUIDITY ADAPTER STORAGE */
address public liquidityAdapter;
bytes public liquidityData;
/* TIMELOCKS STORAGE */
mapping(bytes4 selector => uint256) public timelock;
mapping(bytes4 selector => bool) public abdicated;
mapping(bytes data => uint256) public executableAt;
/* FEES STORAGE */
uint96 public performanceFee;
address public performanceFeeRecipient;
uint96 public managementFee;
address public managementFeeRecipient;
/* GETTERS */
function adaptersLength() external view returns (uint256) {
return adapters.length;
}
function totalAssets() external view returns (uint256) {
(uint256 newTotalAssets,,) = accrueInterestView();
return newTotalAssets;
}
/// forge-lint: disable-next-item(mixed-case-function)
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
}
function absoluteCap(bytes32 id) external view returns (uint256) {
return caps[id].absoluteCap;
}
function relativeCap(bytes32 id) external view returns (uint256) {
return caps[id].relativeCap;
}
function allocation(bytes32 id) external view returns (uint256) {
return caps[id].allocation;
}
/* MULTICALL */
/// @dev Useful for EOAs to batch admin calls.
/// @dev Does not return anything, because accounts who would use the return data would be contracts, which can do
/// the multicall themselves.
function multicall(bytes[] calldata data) external {
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory returnData) = address(this).delegatecall(data[i]);
if (!success) {
assembly ("memory-safe") {
revert(add(32, returnData), mload(returnData))
}
}
}
}
/* CONSTRUCTOR */
constructor(address _owner, address _asset) {
asset = _asset;
owner = _owner;
lastUpdate = uint64(block.timestamp);
uint256 assetDecimals = IERC20(_asset).decimals();
uint256 decimalOffset = uint256(18).zeroFloorSub(assetDecimals);
// forge-lint: disable-next-item(unsafe-typecast) safe because assetDecimals + decimalOffset <= 18.
decimals = uint8(assetDecimals + decimalOffset);
virtualShares = 10 ** decimalOffset;
emit EventsLib.Constructor(_owner, _asset);
}
/* OWNER FUNCTIONS */
function setOwner(address newOwner) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
owner = newOwner;
emit EventsLib.SetOwner(newOwner);
}
function setCurator(address newCurator) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
curator = newCurator;
emit EventsLib.SetCurator(newCurator);
}
function setIsSentinel(address account, bool newIsSentinel) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
isSentinel[account] = newIsSentinel;
emit EventsLib.SetIsSentinel(account, newIsSentinel);
}
function setName(string memory newName) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
name = newName;
emit EventsLib.SetName(newName);
}
function setSymbol(string memory newSymbol) external {
require(msg.sender == owner, ErrorsLib.Unauthorized());
symbol = newSymbol;
emit EventsLib.SetSymbol(newSymbol);
}
/* TIMELOCKS FOR CURATOR FUNCTIONS */
/// @dev Will revert if the timelock value is type(uint256).max or any value that overflows when added to the block
/// timestamp.
function submit(bytes calldata data) external {
require(msg.sender == curator, ErrorsLib.Unauthorized());
require(executableAt[data] == 0, ErrorsLib.DataAlreadyPending());
// forge-lint: disable-next-item(unsafe-typecast) we explicitly want only the first bytes4.
bytes4 selector = bytes4(data);
// forge-lint: disable-next-item(unsafe-typecast) we explicitly want only the second bytes4.
uint256 _timelock =
selector == IVaultV2.decreaseTimelock.selector ? timelock[bytes4(data[4:8])] : timelock[selector];
executableAt[data] = block.timestamp + _timelock;
emit EventsLib.Submit(selector, data, executableAt[data]);
}
function timelocked() internal {
bytes4 selector = bytes4(msg.data);
require(executableAt[msg.data] != 0, ErrorsLib.DataNotTimelocked());
require(block.timestamp >= executableAt[msg.data], ErrorsLib.TimelockNotExpired());
require(!abdicated[selector], ErrorsLib.Abdicated());
executableAt[msg.data] = 0;
emit EventsLib.Accept(selector, msg.data);
}
function revoke(bytes calldata data) external {
require(msg.sender == curator || isSentinel[msg.sender], ErrorsLib.Unauthorized());
require(executableAt[data] != 0, ErrorsLib.DataNotTimelocked());
executableAt[data] = 0;
// forge-lint: disable-next-item(unsafe-typecast) we explicitly want only the first bytes4.
bytes4 selector = bytes4(data);
emit EventsLib.Revoke(msg.sender, selector, data);
}
/* CURATOR FUNCTIONS */
function setIsAllocator(address account, bool newIsAllocator) external {
timelocked();
isAllocator[account] = newIsAllocator;
emit EventsLib.SetIsAllocator(account, newIsAllocator);
}
function setReceiveSharesGate(address newReceiveSharesGate) external {
timelocked();
receiveSharesGate = newReceiveSharesGate;
emit EventsLib.SetReceiveSharesGate(newReceiveSharesGate);
}
function setSendSharesGate(address newSendSharesGate) external {
timelocked();
sendSharesGate = newSendSharesGate;
emit EventsLib.SetSendSharesGate(newSendSharesGate);
}
function setReceiveAssetsGate(address newReceiveAssetsGate) external {
timelocked();
receiveAssetsGate = newReceiveAssetsGate;
emit EventsLib.SetReceiveAssetsGate(newReceiveAssetsGate);
}
function setSendAssetsGate(address newSendAssetsGate) external {
timelocked();
sendAssetsGate = newSendAssetsGate;
emit EventsLib.SetSendAssetsGate(newSendAssetsGate);
}
/// @dev The no-op will revert if the registry now returns false for an already added adapter.
function setAdapterRegistry(address newAdapterRegistry) external {
timelocked();
if (newAdapterRegistry != address(0)) {
for (uint256 i = 0; i < adapters.length; i++) {
require(
IAdapterRegistry(newAdapterRegistry).isInRegistry(adapters[i]), ErrorsLib.NotInAdapterRegistry()
);
}
}
adapterRegistry = newAdapterRegistry;
emit EventsLib.SetAdapterRegistry(newAdapterRegistry);
}
function addAdapter(address account) external {
timelocked();
require(
adapterRegistry == address(0) || IAdapterRegistry(adapterRegistry).isInRegistry(account),
ErrorsLib.NotInAdapterRegistry()
);
if (!isAdapter[account]) {
adapters.push(account);
isAdapter[account] = true;
}
emit EventsLib.AddAdapter(account);
}
function removeAdapter(address account) external {
timelocked();
if (isAdapter[account]) {
for (uint256 i = 0; i < adapters.length; i++) {
if (adapters[i] == account) {
adapters[i] = adapters[adapters.length - 1];
adapters.pop();
break;
}
}
isAdapter[account] = false;
}
emit EventsLib.RemoveAdapter(account);
}
/// @dev This function requires great caution because it can irreversibly disable submit for a selector.
/// @dev Existing pending operations submitted before increasing a timelock can still be executed at the initial
/// executableAt.
function increaseTimelock(bytes4 selector, uint256 newDuration) external {
timelocked();
require(selector != IVaultV2.decreaseTimelock.selector, ErrorsLib.AutomaticallyTimelocked());
require(newDuration >= timelock[selector], ErrorsLib.TimelockNotIncreasing());
timelock[selector] = newDuration;
emit EventsLib.IncreaseTimelock(selector, newDuration);
}
function decreaseTimelock(bytes4 selector, uint256 newDuration) external {
timelocked();
require(selector != IVaultV2.decreaseTimelock.selector, ErrorsLib.AutomaticallyTimelocked());
require(newDuration <= timelock[selector], ErrorsLib.TimelockNotDecreasing());
timelock[selector] = newDuration;
emit EventsLib.DecreaseTimelock(selector, newDuration);
}
function abdicate(bytes4 selector) external {
timelocked();
abdicated[selector] = true;
emit EventsLib.Abdicate(selector);
}
function setPerformanceFee(uint256 newPerformanceFee) external {
timelocked();
require(newPerformanceFee <= MAX_PERFORMANCE_FEE, ErrorsLib.FeeTooHigh());
require(performanceFeeRecipient != address(0) || newPerformanceFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
// forge-lint: disable-next-item(unsafe-typecast) safe because 2**96 > MAX_PERFORMANCE_FEE.
performanceFee = uint96(newPerformanceFee);
emit EventsLib.SetPerformanceFee(newPerformanceFee);
}
function setManagementFee(uint256 newManagementFee) external {
timelocked();
require(newManagementFee <= MAX_MANAGEMENT_FEE, ErrorsLib.FeeTooHigh());
require(managementFeeRecipient != address(0) || newManagementFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
// forge-lint: disable-next-item(unsafe-typecast) safe because 2**96 > MAX_MANAGEMENT_FEE.
managementFee = uint96(newManagementFee);
emit EventsLib.SetManagementFee(newManagementFee);
}
function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external {
timelocked();
require(newPerformanceFeeRecipient != address(0) || performanceFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
performanceFeeRecipient = newPerformanceFeeRecipient;
emit EventsLib.SetPerformanceFeeRecipient(newPerformanceFeeRecipient);
}
function setManagementFeeRecipient(address newManagementFeeRecipient) external {
timelocked();
require(newManagementFeeRecipient != address(0) || managementFee == 0, ErrorsLib.FeeInvariantBroken());
accrueInterest();
managementFeeRecipient = newManagementFeeRecipient;
emit EventsLib.SetManagementFeeRecipient(newManagementFeeRecipient);
}
function increaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external {
timelocked();
bytes32 id = keccak256(idData);
require(newAbsoluteCap >= caps[id].absoluteCap, ErrorsLib.AbsoluteCapNotIncreasing());
caps[id].absoluteCap = newAbsoluteCap.toUint128();
emit EventsLib.IncreaseAbsoluteCap(id, idData, newAbsoluteCap);
}
function decreaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external {
bytes32 id = keccak256(idData);
require(msg.sender == curator || isSentinel[msg.sender], ErrorsLib.Unauthorized());
require(newAbsoluteCap <= caps[id].absoluteCap, ErrorsLib.AbsoluteCapNotDecreasing());
// forge-lint: disable-next-item(unsafe-typecast) safe because newAbsoluteCap <= absoluteCap < 2**128.
caps[id].absoluteCap = uint128(newAbsoluteCap);
emit EventsLib.DecreaseAbsoluteCap(msg.sender, id, idData, newAbsoluteCap);
}
function increaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external {
timelocked();
bytes32 id = keccak256(idData);
require(newRelativeCap <= WAD, ErrorsLib.RelativeCapAboveOne());
require(newRelativeCap >= caps[id].relativeCap, ErrorsLib.RelativeCapNotIncreasing());
// forge-lint: disable-next-item(unsafe-typecast) safe because WAD < 2**128.
caps[id].relativeCap = uint128(newRelativeCap);
emit EventsLib.IncreaseRelativeCap(id, idData, newRelativeCap);
}
function decreaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external {
bytes32 id = keccak256(idData);
require(msg.sender == curator || isSentinel[msg.sender], ErrorsLib.Unauthorized());
require(newRelativeCap <= caps[id].relativeCap, ErrorsLib.RelativeCapNotDecreasing());
// forge-lint: disable-next-item(unsafe-typecast) safe because WAD < 2**128.
caps[id].relativeCap = uint128(newRelativeCap);
emit EventsLib.DecreaseRelativeCap(msg.sender, id, idData, newRelativeCap);
}
function setForceDeallocatePenalty(address adapter, uint256 newForceDeallocatePenalty) external {
timelocked();
require(newForceDeallocatePenalty <= MAX_FORCE_DEALLOCATE_PENALTY, ErrorsLib.PenaltyTooHigh());
forceDeallocatePenalty[adapter] = newForceDeallocatePenalty;
emit EventsLib.SetForceDeallocatePenalty(adapter, newForceDeallocatePenalty);
}
/* ALLOCATOR FUNCTIONS */
function allocate(address adapter, bytes memory data, uint256 assets) external {
require(isAllocator[msg.sender], ErrorsLib.Unauthorized());
allocateInternal(adapter, data, assets);
}
function allocateInternal(address adapter, bytes memory data, uint256 assets) internal {
require(isAdapter[adapter], ErrorsLib.NotAdapter());
accrueInterest();
SafeERC20Lib.safeTransfer(asset, adapter, assets);
(bytes32[] memory ids, int256 change) = IAdapter(adapter).allocate(data, assets, msg.sig, msg.sender);
for (uint256 i; i < ids.length; i++) {
Caps storage _caps = caps[ids[i]];
_caps.allocation = (int256(_caps.allocation) + change).toUint256();
require(_caps.absoluteCap > 0, ErrorsLib.ZeroAbsoluteCap());
require(_caps.allocation <= _caps.absoluteCap, ErrorsLib.AbsoluteCapExceeded());
require(
_caps.relativeCap == WAD || _caps.allocation <= firstTotalAssets.mulDivDown(_caps.relativeCap, WAD),
ErrorsLib.RelativeCapExceeded()
);
}
emit EventsLib.Allocate(msg.sender, adapter, assets, ids, change);
}
function deallocate(address adapter, bytes memory data, uint256 assets) external {
require(isAllocator[msg.sender] || isSentinel[msg.sender], ErrorsLib.Unauthorized());
deallocateInternal(adapter, data, assets);
}
function deallocateInternal(address adapter, bytes memory data, uint256 assets)
internal
returns (bytes32[] memory)
{
require(isAdapter[adapter], ErrorsLib.NotAdapter());
(bytes32[] memory ids, int256 change) = IAdapter(adapter).deallocate(data, assets, msg.sig, msg.sender);
for (uint256 i; i < ids.length; i++) {
Caps storage _caps = caps[ids[i]];
require(_caps.allocation > 0, ErrorsLib.ZeroAllocation());
_caps.allocation = (int256(_caps.allocation) + change).toUint256();
}
SafeERC20Lib.safeTransferFrom(asset, adapter, address(this), assets);
emit EventsLib.Deallocate(msg.sender, adapter, assets, ids, change);
return ids;
}
/// @dev Whether newLiquidityAdapter is an adapter is checked in allocate/deallocate.
function setLiquidityAdapterAndData(address newLiquidityAdapter, bytes memory newLiquidityData) external {
require(isAllocator[msg.sender], ErrorsLib.Unauthorized());
liquidityAdapter = newLiquidityAdapter;
liquidityData = newLiquidityData;
emit EventsLib.SetLiquidityAdapterAndData(msg.sender, newLiquidityAdapter, newLiquidityData);
}
function setMaxRate(uint256 newMaxRate) external {
require(isAllocator[msg.sender], ErrorsLib.Unauthorized());
require(newMaxRate <= MAX_MAX_RATE, ErrorsLib.MaxRateTooHigh());
accrueInterest();
// forge-lint: disable-next-item(unsafe-typecast) safe because newMaxRate <= MAX_MAX_RATE < 2**64-1.
maxRate = uint64(newMaxRate);
emit EventsLib.SetMaxRate(newMaxRate);
}
/* EXCHANGE RATE FUNCTIONS */
function accrueInterest() public {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
emit EventsLib.AccrueInterest(_totalAssets, newTotalAssets, performanceFeeShares, managementFeeShares);
_totalAssets = newTotalAssets.toUint128();
if (firstTotalAssets == 0) firstTotalAssets = newTotalAssets;
if (performanceFeeShares != 0) createShares(performanceFeeRecipient, performanceFeeShares);
if (managementFeeShares != 0) createShares(managementFeeRecipient, managementFeeShares);
lastUpdate = uint64(block.timestamp);
}
/// @dev Returns newTotalAssets, performanceFeeShares, managementFeeShares.
/// @dev The management fee is not bound to the interest, so it can make the share price go down.
/// @dev The management fees is taken even if the vault incurs some losses.
/// @dev Both fees are rounded down, so fee recipients could receive less than expected.
/// @dev The performance fee is taken on the "distributed interest" (which differs from the "real interest" because
/// of the max rate).
function accrueInterestView() public view returns (uint256, uint256, uint256) {
if (firstTotalAssets != 0) return (_totalAssets, 0, 0);
uint256 elapsed = block.timestamp - lastUpdate;
uint256 realAssets = IERC20(asset).balanceOf(address(this));
for (uint256 i = 0; i < adapters.length; i++) {
realAssets += IAdapter(adapters[i]).realAssets();
}
uint256 maxTotalAssets = _totalAssets + (_totalAssets * elapsed).mulDivDown(maxRate, WAD);
uint256 newTotalAssets = MathLib.min(realAssets, maxTotalAssets);
uint256 interest = newTotalAssets.zeroFloorSub(_totalAssets);
// The performance fee assets may be rounded down to 0 if interest * fee < WAD.
uint256 performanceFeeAssets = interest > 0 && performanceFee > 0 && canReceiveShares(performanceFeeRecipient)
? interest.mulDivDown(performanceFee, WAD)
: 0;
// The management fee is taken on newTotalAssets to make all approximations consistent (interacting less
// increases fees).
uint256 managementFeeAssets = elapsed > 0 && managementFee > 0 && canReceiveShares(managementFeeRecipient)
? (newTotalAssets * elapsed).mulDivDown(managementFee, WAD)
: 0;
// Interest should be accrued at least every 10 years to avoid fees exceeding total assets.
uint256 newTotalAssetsWithoutFees = newTotalAssets - performanceFeeAssets - managementFeeAssets;
uint256 performanceFeeShares =
performanceFeeAssets.mulDivDown(totalSupply + virtualShares, newTotalAssetsWithoutFees + 1);
uint256 managementFeeShares =
managementFeeAssets.mulDivDown(totalSupply + virtualShares, newTotalAssetsWithoutFees + 1);
return (newTotalAssets, performanceFeeShares, managementFeeShares);
}
/// @dev Returns previewed minted shares.
function previewDeposit(uint256 assets) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return assets.mulDivDown(newTotalSupply + virtualShares, newTotalAssets + 1);
}
/// @dev Returns previewed deposited assets.
function previewMint(uint256 shares) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return shares.mulDivUp(newTotalAssets + 1, newTotalSupply + virtualShares);
}
/// @dev Returns previewed redeemed shares.
function previewWithdraw(uint256 assets) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return assets.mulDivUp(newTotalSupply + virtualShares, newTotalAssets + 1);
}
/// @dev Returns previewed withdrawn assets.
function previewRedeem(uint256 shares) public view returns (uint256) {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) = accrueInterestView();
uint256 newTotalSupply = totalSupply + performanceFeeShares + managementFeeShares;
return shares.mulDivDown(newTotalAssets + 1, newTotalSupply + virtualShares);
}
/// @dev Returns corresponding shares (rounded down).
/// @dev Takes into account performance and management fees.
function convertToShares(uint256 assets) external view returns (uint256) {
return previewDeposit(assets);
}
/// @dev Returns corresponding assets (rounded down).
/// @dev Takes into account performance and management fees.
function convertToAssets(uint256 shares) external view returns (uint256) {
return previewRedeem(shares);
}
/* MAX FUNCTIONS */
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxDeposit(address) external pure returns (uint256) {
return 0;
}
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxMint(address) external pure returns (uint256) {
return 0;
}
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxWithdraw(address) external pure returns (uint256) {
return 0;
}
/// @dev Gross underestimation because being revert-free cannot be guaranteed when calling the gate.
function maxRedeem(address) external pure returns (uint256) {
return 0;
}
/* USER MAIN FUNCTIONS */
/// @dev Returns minted shares.
function deposit(uint256 assets, address onBehalf) external returns (uint256) {
accrueInterest();
uint256 shares = previewDeposit(assets);
enter(assets, shares, onBehalf);
return shares;
}
/// @dev Returns deposited assets.
function mint(uint256 shares, address onBehalf) external returns (uint256) {
accrueInterest();
uint256 assets = previewMint(shares);
enter(assets, shares, onBehalf);
return assets;
}
/// @dev Internal function for deposit and mint.
function enter(uint256 assets, uint256 shares, address onBehalf) internal {
require(canReceiveShares(onBehalf), ErrorsLib.CannotReceiveShares());
require(canSendAssets(msg.sender), ErrorsLib.CannotSendAssets());
SafeERC20Lib.safeTransferFrom(asset, msg.sender, address(this), assets);
createShares(onBehalf, shares);
_totalAssets += assets.toUint128();
emit EventsLib.Deposit(msg.sender, onBehalf, assets, shares);
if (liquidityAdapter != address(0)) allocateInternal(liquidityAdapter, liquidityData, assets);
}
/// @dev Returns redeemed shares.
function withdraw(uint256 assets, address receiver, address onBehalf) public returns (uint256) {
accrueInterest();
uint256 shares = previewWithdraw(assets);
exit(assets, shares, receiver, onBehalf);
return shares;
}
/// @dev Returns withdrawn assets.
function redeem(uint256 shares, address receiver, address onBehalf) external returns (uint256) {
accrueInterest();
uint256 assets = previewRedeem(shares);
exit(assets, shares, receiver, onBehalf);
return assets;
}
/// @dev Internal function for withdraw and redeem.
function exit(uint256 assets, uint256 shares, address receiver, address onBehalf) internal {
require(canSendShares(onBehalf), ErrorsLib.CannotSendShares());
require(canReceiveAssets(receiver), ErrorsLib.CannotReceiveAssets());
uint256 idleAssets = IERC20(asset).balanceOf(address(this));
if (assets > idleAssets && liquidityAdapter != address(0)) {
deallocateInternal(liquidityAdapter, liquidityData, assets - idleAssets);
}
if (msg.sender != onBehalf) {
uint256 _allowance = allowance[onBehalf][msg.sender];
if (_allowance != type(uint256).max) allowance[onBehalf][msg.sender] = _allowance - shares;
}
deleteShares(onBehalf, shares);
_totalAssets -= assets.toUint128();
SafeERC20Lib.safeTransfer(asset, receiver, assets);
emit EventsLib.Withdraw(msg.sender, receiver, onBehalf, assets, shares);
}
/// @dev Returns shares withdrawn as penalty.
/// @dev When calling this function, a penalty is taken from onBehalf, in order to discourage allocation
/// manipulations.
/// @dev The penalty is taken as a withdrawal for which assets are returned to the vault. In consequence,
/// totalAssets is decreased normally along with totalSupply (the share price doesn't change except because of
/// rounding errors), but the amount of assets actually controlled by the vault is not decreased.
/// @dev If a user has A assets in the vault, and that the vault is already fully illiquid, the optimal amount to
/// force deallocate in order to exit the vault is min(liquidity_of_market, A / (1 + penalty)).
/// This ensures that either the market is empty or that it leaves no shares nor liquidity after exiting.
function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf)
external
returns (uint256)
{
bytes32[] memory ids = deallocateInternal(adapter, data, assets);
uint256 penaltyAssets = assets.mulDivUp(forceDeallocatePenalty[adapter], WAD);
uint256 penaltyShares = withdraw(penaltyAssets, address(this), onBehalf);
emit EventsLib.ForceDeallocate(msg.sender, adapter, assets, onBehalf, ids, penaltyAssets);
return penaltyShares;
}
/* ERC20 FUNCTIONS */
/// @dev Returns success (always true because reverts on failure).
function transfer(address to, uint256 shares) external returns (bool) {
require(to != address(0), ErrorsLib.ZeroAddress());
require(canSendShares(msg.sender), ErrorsLib.CannotSendShares());
require(canReceiveShares(to), ErrorsLib.CannotReceiveShares());
balanceOf[msg.sender] -= shares;
balanceOf[to] += shares;
emit EventsLib.Transfer(msg.sender, to, shares);
return true;
}
/// @dev Returns success (always true because reverts on failure).
function transferFrom(address from, address to, uint256 shares) external returns (bool) {
require(from != address(0), ErrorsLib.ZeroAddress());
require(to != address(0), ErrorsLib.ZeroAddress());
require(canSendShares(from), ErrorsLib.CannotSendShares());
require(canReceiveShares(to), ErrorsLib.CannotReceiveShares());
if (msg.sender != from) {
uint256 _allowance = allowance[from][msg.sender];
if (_allowance != type(uint256).max) {
allowance[from][msg.sender] = _allowance - shares;
emit EventsLib.AllowanceUpdatedByTransferFrom(from, msg.sender, _allowance - shares);
}
}
balanceOf[from] -= shares;
balanceOf[to] += shares;
emit EventsLib.Transfer(from, to, shares);
return true;
}
/// @dev Returns success (always true because reverts on failure).
function approve(address spender, uint256 shares) external returns (bool) {
allowance[msg.sender][spender] = shares;
emit EventsLib.Approval(msg.sender, spender, shares);
return true;
}
/// @dev Signature malleability is not explicitly prevented but it is not a problem thanks to the nonce.
function permit(address _owner, address spender, uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external
{
require(deadline >= block.timestamp, ErrorsLib.PermitDeadlineExpired());
uint256 nonce = nonces[_owner]++;
bytes32 hashStruct = keccak256(abi.encode(PERMIT_TYPEHASH, _owner, spender, shares, nonce, deadline));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), hashStruct));
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == _owner, ErrorsLib.InvalidSigner());
allowance[_owner][spender] = shares;
emit EventsLib.Approval(_owner, spender, shares);
emit EventsLib.Permit(_owner, spender, shares, nonce, deadline);
}
function createShares(address to, uint256 shares) internal {
require(to != address(0), ErrorsLib.ZeroAddress());
balanceOf[to] += shares;
totalSupply += shares;
emit EventsLib.Transfer(address(0), to, shares);
}
function deleteShares(address from, uint256 shares) internal {
require(from != address(0), ErrorsLib.ZeroAddress());
balanceOf[from] -= shares;
totalSupply -= shares;
emit EventsLib.Transfer(from, address(0), shares);
}
/* PERMISSIONED TOKEN FUNCTIONS */
function canReceiveShares(address account) public view returns (bool) {
return receiveSharesGate == address(0) || IReceiveSharesGate(receiveSharesGate).canReceiveShares(account);
}
function canSendShares(address account) public view returns (bool) {
return sendSharesGate == address(0) || ISendSharesGate(sendSharesGate).canSendShares(account);
}
function canReceiveAssets(address account) public view returns (bool) {
return account == address(this) || receiveAssetsGate == address(0)
|| IReceiveAssetsGate(receiveAssetsGate).canReceiveAssets(account);
}
function canSendAssets(address account) public view returns (bool) {
return sendAssetsGate == address(0) || ISendAssetsGate(sendAssetsGate).canSendAssets(account);
}
}
IERC20.sol 15 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IERC20 {
function decimals() external view returns (uint8);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 shares) external returns (bool success);
function transferFrom(address from, address to, uint256 shares) external returns (bool success);
function approve(address spender, uint256 shares) external returns (bool success);
function allowance(address owner, address spender) external view returns (uint256);
}
IVaultV2.sol 106 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
import {IERC4626} from "./IERC4626.sol";
import {IERC2612} from "./IERC2612.sol";
struct Caps {
uint256 allocation;
uint128 absoluteCap;
uint128 relativeCap;
}
interface IVaultV2 is IERC4626, IERC2612 {
// State variables
function virtualShares() external view returns (uint256);
function owner() external view returns (address);
function curator() external view returns (address);
function receiveSharesGate() external view returns (address);
function sendSharesGate() external view returns (address);
function receiveAssetsGate() external view returns (address);
function sendAssetsGate() external view returns (address);
function adapterRegistry() external view returns (address);
function isSentinel(address account) external view returns (bool);
function isAllocator(address account) external view returns (bool);
function firstTotalAssets() external view returns (uint256);
function _totalAssets() external view returns (uint128);
function lastUpdate() external view returns (uint64);
function maxRate() external view returns (uint64);
function adapters(uint256 index) external view returns (address);
function adaptersLength() external view returns (uint256);
function isAdapter(address account) external view returns (bool);
function allocation(bytes32 id) external view returns (uint256);
function absoluteCap(bytes32 id) external view returns (uint256);
function relativeCap(bytes32 id) external view returns (uint256);
function forceDeallocatePenalty(address adapter) external view returns (uint256);
function liquidityAdapter() external view returns (address);
function liquidityData() external view returns (bytes memory);
function timelock(bytes4 selector) external view returns (uint256);
function abdicated(bytes4 selector) external view returns (bool);
function executableAt(bytes memory data) external view returns (uint256);
function performanceFee() external view returns (uint96);
function performanceFeeRecipient() external view returns (address);
function managementFee() external view returns (uint96);
function managementFeeRecipient() external view returns (address);
// Gating
function canSendShares(address account) external view returns (bool);
function canReceiveShares(address account) external view returns (bool);
function canSendAssets(address account) external view returns (bool);
function canReceiveAssets(address account) external view returns (bool);
// Multicall
function multicall(bytes[] memory data) external;
// Owner functions
function setOwner(address newOwner) external;
function setCurator(address newCurator) external;
function setIsSentinel(address account, bool isSentinel) external;
function setName(string memory newName) external;
function setSymbol(string memory newSymbol) external;
// Timelocks for curator functions
function submit(bytes memory data) external;
function revoke(bytes memory data) external;
// Curator functions
function setIsAllocator(address account, bool newIsAllocator) external;
function setReceiveSharesGate(address newReceiveSharesGate) external;
function setSendSharesGate(address newSendSharesGate) external;
function setReceiveAssetsGate(address newReceiveAssetsGate) external;
function setSendAssetsGate(address newSendAssetsGate) external;
function setAdapterRegistry(address newAdapterRegistry) external;
function addAdapter(address account) external;
function removeAdapter(address account) external;
function increaseTimelock(bytes4 selector, uint256 newDuration) external;
function decreaseTimelock(bytes4 selector, uint256 newDuration) external;
function abdicate(bytes4 selector) external;
function setPerformanceFee(uint256 newPerformanceFee) external;
function setManagementFee(uint256 newManagementFee) external;
function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external;
function setManagementFeeRecipient(address newManagementFeeRecipient) external;
function increaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external;
function decreaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external;
function increaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external;
function decreaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external;
function setMaxRate(uint256 newMaxRate) external;
function setForceDeallocatePenalty(address adapter, uint256 newForceDeallocatePenalty) external;
// Allocator functions
function allocate(address adapter, bytes memory data, uint256 assets) external;
function deallocate(address adapter, bytes memory data, uint256 assets) external;
function setLiquidityAdapterAndData(address newLiquidityAdapter, bytes memory newLiquidityData) external;
// Exchange rate
function accrueInterest() external;
function accrueInterestView()
external
view
returns (uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares);
// Force deallocate
function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf)
external
returns (uint256 penaltyShares);
}
IAdapter.sol 19 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
/// @dev See VaultV2 NatSpec comments for more details on adapter's spec.
interface IAdapter {
/// @dev Returns the market' ids and the change in assets on this market.
function allocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
external
returns (bytes32[] memory ids, int256 change);
/// @dev Returns the market' ids and the change in assets on this market.
function deallocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
external
returns (bytes32[] memory ids, int256 change);
/// @dev Returns the current value of the investments of the adapter (in underlying asset).
function realAssets() external view returns (uint256 assets);
}
IAdapterRegistry.sol 7 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IAdapterRegistry {
function isInRegistry(address account) external view returns (bool);
}
ErrorsLib.sol 45 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
library ErrorsLib {
error Abdicated();
error AbsoluteCapExceeded();
error AbsoluteCapNotDecreasing();
error AbsoluteCapNotIncreasing();
error ApproveReturnedFalse();
error ApproveReverted();
error AutomaticallyTimelocked();
error CannotReceiveShares();
error CannotReceiveAssets();
error CannotSendShares();
error CannotSendAssets();
error CapExceeded();
error CastOverflow();
error DataAlreadyPending();
error DataNotTimelocked();
error FeeInvariantBroken();
error FeeTooHigh();
error InvalidSigner();
error MaxRateTooHigh();
error NoCode();
error NotAdapter();
error NotInAdapterRegistry();
error PenaltyTooHigh();
error PermitDeadlineExpired();
error RelativeCapAboveOne();
error RelativeCapExceeded();
error RelativeCapNotDecreasing();
error RelativeCapNotIncreasing();
error TimelockNotDecreasing();
error TimelockNotExpired();
error TimelockNotIncreasing();
error TransferFromReturnedFalse();
error TransferFromReverted();
error TransferReturnedFalse();
error TransferReverted();
error Unauthorized();
error ZeroAbsoluteCap();
error ZeroAddress();
error ZeroAllocation();
}
EventsLib.sol 75 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
library EventsLib {
// ERC20 events
event Approval(address indexed owner, address indexed spender, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 shares);
/// @dev Emitted when the allowance is updated by transferFrom (not when it is updated by permit, approve, withdraw,
/// redeem because their respective events allow to track the allowance).
event AllowanceUpdatedByTransferFrom(address indexed owner, address indexed spender, uint256 shares);
event Permit(address indexed owner, address indexed spender, uint256 shares, uint256 nonce, uint256 deadline);
// ERC4626 events
event Deposit(address indexed sender, address indexed onBehalf, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender, address indexed receiver, address indexed onBehalf, uint256 assets, uint256 shares
);
// Vault creation events
event Constructor(address indexed owner, address indexed asset);
// Allocation events
event Allocate(address indexed sender, address indexed adapter, uint256 assets, bytes32[] ids, int256 change);
event Deallocate(address indexed sender, address indexed adapter, uint256 assets, bytes32[] ids, int256 change);
event ForceDeallocate(
address indexed sender,
address adapter,
uint256 assets,
address indexed onBehalf,
bytes32[] ids,
uint256 penaltyAssets
);
// Fee and interest events
event AccrueInterest(
uint256 previousTotalAssets, uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares
);
// Timelock events
event Revoke(address indexed sender, bytes4 indexed selector, bytes data);
event Submit(bytes4 indexed selector, bytes data, uint256 executableAt);
event Accept(bytes4 indexed selector, bytes data);
// Configuration events
event SetOwner(address indexed newOwner);
event SetCurator(address indexed newCurator);
event SetIsSentinel(address indexed account, bool newIsSentinel);
event SetName(string newName);
event SetSymbol(string newSymbol);
event SetIsAllocator(address indexed account, bool newIsAllocator);
event SetReceiveSharesGate(address indexed newReceiveSharesGate);
event SetSendSharesGate(address indexed newSendSharesGate);
event SetReceiveAssetsGate(address indexed newReceiveAssetsGate);
event SetSendAssetsGate(address indexed newSendAssetsGate);
event SetAdapterRegistry(address indexed newAdapterRegistry);
event AddAdapter(address indexed account);
event RemoveAdapter(address indexed account);
event DecreaseTimelock(bytes4 indexed selector, uint256 newDuration);
event IncreaseTimelock(bytes4 indexed selector, uint256 newDuration);
event Abdicate(bytes4 indexed selector);
event SetLiquidityAdapterAndData(
address indexed sender, address indexed newLiquidityAdapter, bytes indexed newLiquidityData
);
event SetPerformanceFee(uint256 newPerformanceFee);
event SetPerformanceFeeRecipient(address indexed newPerformanceFeeRecipient);
event SetManagementFee(uint256 newManagementFee);
event SetManagementFeeRecipient(address indexed newManagementFeeRecipient);
event DecreaseAbsoluteCap(address indexed sender, bytes32 indexed id, bytes idData, uint256 newAbsoluteCap);
event IncreaseAbsoluteCap(bytes32 indexed id, bytes idData, uint256 newAbsoluteCap);
event DecreaseRelativeCap(address indexed sender, bytes32 indexed id, bytes idData, uint256 newRelativeCap);
event IncreaseRelativeCap(bytes32 indexed id, bytes idData, uint256 newRelativeCap);
event SetMaxRate(uint256 newMaxRate);
event SetForceDeallocatePenalty(address indexed adapter, uint256 forceDeallocatePenalty);
}
ConstantsLib.sol 12 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
uint256 constant WAD = 1e18;
bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
bytes32 constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
uint256 constant MAX_MAX_RATE = 200e16 / uint256(365 days); // 200% APR
uint256 constant MAX_PERFORMANCE_FEE = 0.5e18; // 50%
uint256 constant MAX_MANAGEMENT_FEE = 0.05e18 / uint256(365 days); // 5%
uint256 constant MAX_FORCE_DEALLOCATE_PENALTY = 0.02e18; // 2%
MathLib.sol 45 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
import {ErrorsLib} from "./ErrorsLib.sol";
library MathLib {
/// @dev Returns (x * y) / d rounded down.
function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
return (x * y) / d;
}
/// @dev Returns (x * y) / d rounded up.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
return (x * y + (d - 1)) / d;
}
/// @dev Returns max(0, x - y).
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Casts from uint256 to uint128, reverting if input number is too large.
function toUint128(uint256 x) internal pure returns (uint128) {
require(x <= type(uint128).max, ErrorsLib.CastOverflow());
// forge-lint: disable-next-item(unsafe-typecast) safe because x <= type(uint128).max.
return uint128(x);
}
/// @dev Casts from int256 to uint256, reverting if input number is negative.
function toUint256(int256 x) internal pure returns (uint256) {
require(x >= 0, ErrorsLib.CastOverflow());
// forge-lint: disable-next-item(unsafe-typecast) safe because x >= 0.
return uint256(x);
}
/// @dev Returns min(x, y).
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
}
SafeERC20Lib.sol 32 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
import {IERC20} from "../interfaces/IERC20.sol";
import {ErrorsLib} from "./ErrorsLib.sol";
library SafeERC20Lib {
function safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.transfer, (to, value)));
require(success, ErrorsLib.TransferReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TransferReturnedFalse());
}
function safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
require(success, ErrorsLib.TransferFromReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TransferFromReturnedFalse());
}
function safeApprove(address token, address spender, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.approve, (spender, value)));
require(success, ErrorsLib.ApproveReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.ApproveReturnedFalse());
}
}
IGate.sol 19 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IReceiveSharesGate {
function canReceiveShares(address account) external view returns (bool);
}
interface ISendSharesGate {
function canSendShares(address account) external view returns (bool);
}
interface IReceiveAssetsGate {
function canReceiveAssets(address account) external view returns (bool);
}
interface ISendAssetsGate {
function canSendAssets(address account) external view returns (bool);
}
IERC4626.sol 24 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
import {IERC20} from "./IERC20.sol";
interface IERC4626 is IERC20 {
function asset() external view returns (address);
function totalAssets() external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function deposit(uint256 assets, address onBehalf) external returns (uint256 shares);
function mint(uint256 shares, address onBehalf) external returns (uint256 assets);
function withdraw(uint256 assets, address receiver, address onBehalf) external returns (uint256 shares);
function redeem(uint256 shares, address receiver, address onBehalf) external returns (uint256 assets);
function previewDeposit(uint256 assets) external view returns (uint256 shares);
function previewMint(uint256 shares) external view returns (uint256 assets);
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
function previewRedeem(uint256 shares) external view returns (uint256 assets);
function maxDeposit(address onBehalf) external view returns (uint256 assets);
function maxMint(address onBehalf) external view returns (uint256 shares);
function maxWithdraw(address onBehalf) external view returns (uint256 assets);
function maxRedeem(address onBehalf) external view returns (uint256 shares);
}
IERC2612.sol 10 lines
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IERC2612 {
function permit(address owner, address spender, uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
Read Contract
DOMAIN_SEPARATOR 0x3644e515 → bytes32
_totalAssets 0xce04bebb → uint128
abdicated 0xe470b8bc → bool
absoluteCap 0xbc0dd374 → uint256
accrueInterestView 0x60a38ac1 → uint256, uint256, uint256
adapterRegistry 0x50b5c16a → address
adapters 0x4ef501ac → address
adaptersLength 0x5aa22bc8 → uint256
allocation 0xc69507dd → uint256
allowance 0xdd62ed3e → uint256
asset 0x38d52e0f → address
balanceOf 0x70a08231 → uint256
canReceiveAssets 0x0d326b18 → bool
canReceiveShares 0x98c9b49c → bool
canSendAssets 0x20fe8d58 → bool
canSendShares 0x8e511e4d → bool
convertToAssets 0x07a2d13a → uint256
convertToShares 0xc6e6f592 → uint256
curator 0xe66f53b7 → address
decimals 0x313ce567 → uint8
executableAt 0x8639fb07 → uint256
firstTotalAssets 0xf73f8f31 → uint256
forceDeallocatePenalty 0x99e99183 → uint256
isAdapter 0x68c18beb → bool
isAllocator 0x4dedf20e → bool
isSentinel 0xba03a75f → bool
lastUpdate 0xc0463711 → uint64
liquidityAdapter 0xad468d11 → address
liquidityData 0x2e029228 → bytes
managementFee 0xa6f7f5d6 → uint96
managementFeeRecipient 0x6d9a3010 → address
maxDeposit 0x402d267d → uint256
maxMint 0xc63d75b6 → uint256
maxRate 0xece1d6e5 → uint64
maxRedeem 0xd905777e → uint256
maxWithdraw 0xce96cb77 → uint256
name 0x06fdde03 → string
nonces 0x7ecebe00 → uint256
owner 0x8da5cb5b → address
performanceFee 0x87788782 → uint96
performanceFeeRecipient 0xed27f7c9 → address
previewDeposit 0xef8b30f7 → uint256
previewMint 0xb3d7f6b9 → uint256
previewRedeem 0x4cdad506 → uint256
previewWithdraw 0x0a28a477 → uint256
receiveAssetsGate 0x54cde13e → address
receiveSharesGate 0x7e729ac4 → address
relativeCap 0xa68bafa3 → uint256
sendAssetsGate 0x8eede801 → address
sendSharesGate 0x93ab2ab7 → address
symbol 0x95d89b41 → string
timelock 0xe78ab14e → uint256
totalAssets 0x01e1d114 → uint256
totalSupply 0x18160ddd → uint256
virtualShares 0xc719946c → uint256
Write Contract 42 functions
These functions modify contract state and require a wallet transaction to execute.
abdicate 0xb2e32848
bytes4 selector
accrueInterest 0xa6afed95
No parameters
addAdapter 0x60d54d41
address account
allocate 0x5c9ce04d
address adapter
bytes data
uint256 assets
approve 0x095ea7b3
address spender
uint256 shares
returns: bool
deallocate 0x4b219d16
address adapter
bytes data
uint256 assets
decreaseAbsoluteCap 0x8c54519b
bytes idData
uint256 newAbsoluteCap
decreaseRelativeCap 0x57975270
bytes idData
uint256 newRelativeCap
decreaseTimelock 0x5c1a1a4f
bytes4 selector
uint256 newDuration
deposit 0x6e553f65
uint256 assets
address onBehalf
returns: uint256
forceDeallocate 0xe4d38cd8
address adapter
bytes data
uint256 assets
address onBehalf
returns: uint256
increaseAbsoluteCap 0xf6f98fd5
bytes idData
uint256 newAbsoluteCap
increaseRelativeCap 0x2438525b
bytes idData
uint256 newRelativeCap
increaseTimelock 0x47966291
bytes4 selector
uint256 newDuration
mint 0x94bf804d
uint256 shares
address onBehalf
returns: uint256
multicall 0xac9650d8
bytes[] data
permit 0xd505accf
address _owner
address spender
uint256 shares
uint256 deadline
uint8 v
bytes32 r
bytes32 s
redeem 0xba087652
uint256 shares
address receiver
address onBehalf
returns: uint256
removeAdapter 0x585cd34b
address account
revoke 0x0b467b9b
bytes data
setAdapterRegistry 0x5b34b823
address newAdapterRegistry
setCurator 0xe90956cf
address newCurator
setForceDeallocatePenalty 0x3e9d2ac7
address adapter
uint256 newForceDeallocatePenalty
setIsAllocator 0xb192a84a
address account
bool newIsAllocator
setIsSentinel 0x920ed706
address account
bool newIsSentinel
setLiquidityAdapterAndData 0x7fb6caad
address newLiquidityAdapter
bytes newLiquidityData
setManagementFee 0xfe56e232
uint256 newManagementFee
setManagementFeeRecipient 0x9faae464
address newManagementFeeRecipient
setMaxRate 0xaa4abe7f
uint256 newMaxRate
setName 0xc47f0027
string newName
setOwner 0x13af4035
address newOwner
setPerformanceFee 0x70897b23
uint256 newPerformanceFee
setPerformanceFeeRecipient 0x6a5f1aa2
address newPerformanceFeeRecipient
setReceiveAssetsGate 0x04dbf0ce
address newReceiveAssetsGate
setReceiveSharesGate 0x2cb19f98
address newReceiveSharesGate
setSendAssetsGate 0x871c979c
address newSendAssetsGate
setSendSharesGate 0xc21ad028
address newSendSharesGate
setSymbol 0xb84c8246
string newSymbol
submit 0xef7fa71b
bytes data
transfer 0xa9059cbb
address to
uint256 shares
returns: bool
transferFrom 0x23b872dd
address from
address to
uint256 shares
returns: bool
withdraw 0xb460af94
uint256 assets
address receiver
address onBehalf
returns: uint256
Recent Transactions
No transactions found for this address