Cryo Explorer Ethereum Mainnet

Address Contract Partially Verified

Address 0x57E69699381a651Fb0BBDBB31888F5D655Bf3f06
Balance 0 ETH
Nonce 2
Code Size 23869 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

23869 bytes
0x60806040526004361015610011575f80fd5b5f5f3560e01c80628cc2621461382457806302ce728f146137f657806306b6f7e91461377757806306fdde031461365a57806308a0c375146135c257806308c7e366146135a55780630cab937d146130395780630df8dfac1461301057806313966db514612ff2578063146a29eb14612fd25780631c6c959714612f515780632164e85b14612f335780632b3ba681146110be5780632cdacb5014612eee5780632f86556814612a9f57806333fd6f7414612a4c5780633f2617cb146129585780633f4ba83a146128fd57806342f43d0b146128b8578063476343ee14612644578063487413761461257a5780634ac8eb5f1461255f5780634cae6513146124e55780634fd422df146124c157806354fd4d501461249757806356ecf28b1461247957806357d775f81461243e5780635e43c47b1461240957806360c52d05146123c05780636551f16c146123365780636b091695146122725780636f307dc31461222d578063730a7514146121a2578063757991a81461213b578063759cb53b1461210c57806375a410141461208c57806378e97925146120515780637adbf97314611f635780637b10399914611f1e5780637e92968414611bbb5780637ec4b57114611b7a5780638049d97114611afd5780638214ba4814611a835780638285ef4014611a5457806383d4433914611a1b5780638456cb591461199657806386993bef146119785780638cad7fbe146119395780638da5cb5b146109d85780638f50ea921461191b57806391ebebbd146118bc57806392bbcaed1461189e57806393ae0df91461187757806393f46f6414611831578063945c91421461180257806395d14ca8146117c05780639a295e731461177a5780639fe34bdc14611158578063a053db68146112af578063a36a363014611291578063ab7cfaf914611273578063acb70815146111f5578063b5af3062146111d1578063b5b5454714611198578063b68d0a091461115d578063b78294dd14611158578063b95c57461461113a578063c00007b0146110e1578063c0a7e892146110be578063c3192f14146110c3578063c49bb2d1146110be578063ca51bb59146110a0578063cacf3b5814611074578063cadac47914610f86578063cdd72d5214610f2c578063d41ddc9614610dee578063d619658b14610dd2578063d6bda0c014610bf4578063d870ce6614610b5a578063d8dfeb4514610b15578063e509b9d914610ad4578063e551d11d14610ab6578063e69bc27114610a71578063e7a3317414610a1d578063f2f4eb26146109d8578063f301af4214610982578063f384bd0514610964578063f5c7f89914610927578063f8112eed146108e6578063fbbbf94c146108ac578063fd6d0526146104325763fff5d9da1461040d575f80fd5b3461042f578060031936011261042f5760206104276140e3565b604051908152f35b80fd5b503461042f57608036600319011261042f5761044c6139b8565b90606435906024356001600160a01b0383168084036108a85761046d61413a565b60405163e8673ef560e01b81526020816004817f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b03165afa90811561089d57849161085f575b506001600160a01b03163303610850578015908115610846575b5061083757600e548110610828576104ea6144c4565b505050604435670de0b6b3a764000003670de0b6b3a764000081116108145761051c670de0b6b3a76400009183613d37565b0493670de0b6b3a764000061053d6105348785613b9c565b600d5490613d37565b04946105498684613b9c565b90610552613c9f565b6001600160801b03815116838181119182156107ff575b50506107f0576001600160801b03610586818516828451166140c3565b169081815264e8d4a5100082029180830464e8d4a5100014901517156107525760208101916001600160801b0383511611610766575b51905160801b6001600160801b0319166001600160801b039190911617601755670de0b6b3a764000090610608905b6105f789601954613d68565b601955610602614158565b90613d37565b0495610613876157d2565b7f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03169561064a908890886158d3565b6001600160a01b037f00000000000000000000000086f3188e24a6902c6d4d7b820eabdc4255f636e416620186a088810290898204148915171561075257813b1561074e57869160248392604051948593849263140e25ad60e31b845260048401525af180156107435761072a575b5060408051948552602085018890528401526060830152600192916001600160a01b03909116907f76cd0cedf979345ca241ce6de696a520a8efc860c6c10d9db2a7953307c237fc90608090a255604080516001600160a01b039092168252602082019290925290819081015b0390f35b610735868092613a58565b61073f575f6106b9565b8480fd5b6040513d88823e3d90fd5b8680fd5b634e487b7160e01b87526011600452602487fd5b61076e615150565b600254600181018091116107dc576001600160801b036106089381670de0b6b3a76400009694846105eb956002557febad8099c467528a56c98b63c8d476d251cf1ffb4c75db94b4d23fa2b6a1e3358d80a28164e8d4a51000818551160416835294965050935050506105bc565b634e487b7160e01b88526011600452602488fd5b6316c6823f60e01b8652600486fd5b6108099250613b9c565b600f5411835f610569565b634e487b7160e01b83526011600452602483fd5b6362171e8f60e01b8252600482fd5b631e4ec46b60e01b8252600482fd5b905030145f6104d4565b63d520e5ad60e01b8352600483fd5b90506020813d602011610895575b8161087a60209383613a58565b810103126108915761088b90613d23565b5f6104ba565b8380fd5b3d915061086d565b6040513d86823e3d90fd5b8280fd5b503461042f578060031936011261042f57601554601654604080516001600160a01b038416815260a09390931c6020840152820152606090f35b503461042f57602036600319011261042f576109206109036139b8565b61091361090e61495a565b613f6f565b61091b61413a565b615726565b6001815580f35b503461042f57604036600319011261042f576001906109586109476139b8565b61094f61413a565b60243590615002565b55602060405160018152f35b503461042f578060031936011261042f576020600954604051908152f35b503461042f57602036600319011261042f576004359060015482101561042f5760606109ad83613b64565b506001815491015460ff6040519260018060a01b038116845260a01c16151560208301526040820152f35b503461042f578060031936011261042f576040517f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b03168152602090f35b503461042f57602036600319011261042f57610a63337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b610a6e60043561491b565b80f35b503461042f57604036600319011261042f576040610a8d6139ce565b9160043581526004602052209060018060a01b03165f52602052602060405f2054604051908152f35b503461042f578060031936011261042f576020600a54604051908152f35b503461042f57602036600319011261042f576020906001600160a01b03610af96139b8565b16815260088252604060018060a01b0391205416604051908152f35b503461042f578060031936011261042f576040517f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03168152602090f35b503461042f57602036600319011261042f57600435610ba3337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b68056bc75e2d631000008110610be5576020817febc3aac103344d02f2e1b3fb50ca740929143c84082dd6e7698963e902aa636992600e55604051908152a180f35b630309cb8760e51b8252600482fd5b503461042f57606036600319011261042f57602435610c116139e4565b610c1961413a565b610c2233614290565b6001600160a01b03811615610dc357610c396144c4565b505050610c44614158565b5081610cd3575b610c609150610c5b600435614729565b61529f565b906040610c6b613c71565b01610c77815133614320565b15610c8b5760208360018455604051908152f35b60649250610cad610c9a613c9f565b338452601b6020526040842054906143c2565b90338352601a6020526040832054905191633b49de0f60e21b8452600452602452604452fd5b602082610d05610d2c9430337f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e61523b565b604051636e553f6560e01b8152600481019190915230602482015292839081906044820190565b0381867f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03165af1918215610db8578392610d7e575b50610d79610c6092339030614bd4565b610c4b565b91506020823d602011610db0575b81610d9960209383613a58565b81010312610dac57905190610d79610d69565b5f80fd5b3d9150610d8c565b6040513d85823e3d90fd5b631e4ec46b60e01b8352600483fd5b503461042f578060031936011261042f576020604051600f8152f35b503461042f57604036600319011261042f57600435610e0b6139ce565b610e1361413a565b610e1c33614290565b6001600160a01b03168015610dc357610e336144c4565b505050338352601b6020526040832054610f1e575b610e53333084614644565b604051635d043b2960e11b815260048101929092526024820152306044820152602081606481857f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03165af18015610f1357610ee4575b506040610ebc613c71565b01610ec8815133614320565b15610ed557506001815580f35b90606491610cad610c9a613c9f565b610f059060203d602011610f0c575b610efd8183613a58565b810190613ce9565b505f610eb1565b503d610ef3565b6040513d84823e3d90fd5b610f26614158565b50610e48565b503461042f578060031936011261042f57610f45613ba9565b506080610f5061402d565b915091506001600160801b0360208183511692015116610f6e613dcd565b91604051938452602084015260408301526060820152f35b503461042f57604036600319011261042f57600435610fa36139ce565b90610fac61413a565b6001600160a01b03821615610dc357906020610ff992610fca6144c4565b505050610d058130337f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e61523b565b0381867f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03165af18015610db8578390611040575b610920925030614bd4565b506020823d60201161106c575b8161105a60209383613a58565b81010312610dac576109209151611035565b3d915061104d565b503461042f578060031936011261042f5761072661109061402d565b9060409492945194859485613a88565b503461042f578060031936011261042f576020601c54604051908152f35b613ae2565b503461042f578060031936011261042f576020601854604051908152f35b503461042f57602036600319011261042f576110fb6139b8565b61110361413a565b6001600160a01b0381811683526008602052604083205416801561112a5761092091614e9b565b508061113591614e9b565b610920565b503461042f578060031936011261042f576020600154604051908152f35b613b30565b503461042f57602036600319011261042f5760406111796139b8565b61118b61118582613fac565b91613f11565b9082519182526020820152f35b503461042f57602036600319011261042f576020906040906001600160a01b036111c06139b8565b168152600383522054604051908152f35b503461042f57602036600319011261042f5760206104276111f06139b8565b613fac565b503461042f57604036600319011261042f576004356112126139ce565b61121a61413a565b6001600160a01b03811615610dc35760209261126a60019261123a6144c4565b505050611245613c9f565b61124f86826143c2565b9561126261125c88614729565b91614729565b90339261475a565b55604051908152f35b503461042f578060031936011261042f576020600d54604051908152f35b503461042f578060031936011261042f576020600c54604051908152f35b503461042f5760a036600319011261042f576112c96139b8565b60243591906064356044356084356001600160401b03811161073f573660238201121561073f5780600401356112fe81613b4d565b9161130c6040519384613a58565b818352602083016024819360051b8301019136831161177657602401905b82821061175e5750505061133c61413a565b61134533614290565b859061134f6144c4565b50505061135a614158565b506001600160a01b0386168088526012602052604088205490969060ff161561174f576001600160a01b0361138e85613c1c565b517f00000000000000000000000057ab1e0003f623289cd798b1824be09a793e4bec6001600160a01b03169116819003611739575083515f198101908111611725576001600160a01b03906113e39086613c29565b517f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03169291168290036116e9578561163e575b61142b90610c5b8b614729565b93604051926370a0823160e01b8452306004850152602084602481865afa938415611633578a946115ff575b50883b156115fb57604051634b4ecc5560e11b8152336004820152602481018c9052608060448201529151608483018190528a90839060a4820190835b8181106115d6575050819293503060648301520381838c5af180156115cb579089916115b2575b5050906020602492604051938480926370a0823160e01b82523060048301525afa80156115a7578890611573575b6114f39250613b9c565b9380851061155c5750836115119161150c338330614bd4565b613d68565b9560405194855260208501526040840152606083015260808201527fb19ca0df3f3a01af950d8e6ad62aeff167cf14c73e98af6c52afef1add5c97ed60a03392a26040610c6b613c71565b633b5d56ed60e11b87526004526024849052604486fd5b506020823d60201161159f575b8161158d60209383613a58565b81010312610dac576114f391516114e9565b3d9150611580565b6040513d8a823e3d90fd5b816115bc91613a58565b6115c757875f6114bb565b8780fd5b6040513d8b823e3d90fd5b85516001600160a01b031683526020958601958f955087945090920191600101611494565b8980fd5b9093506020813d60201161162b575b8161161b60209383613a58565b81010312610dac5751925f611457565b3d915061160e565b6040513d8c823e3d90fd5b925061166c8530337f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e61523b565b604051636e553f6560e01b8152600481018690523060248201526020816044818c865af180156115cb5789906116b5575b61142b91506116ad338230614bd4565b93905061141e565b506020813d6020116116e1575b816116cf60209383613a58565b81010312610dac5761142b905161169d565b3d91506116c2565b8489838251925f198401938411610814576044936001600160a01b03916117109190613c29565b51169063b0b3262d60e01b8352600452602452fd5b634e487b7160e01b89526011600452602489fd5b60449089906001600160a01b0361171088613c1c565b631311dc6d60e01b8852600488fd5b6020809161176b846139fa565b81520191019061132a565b8880fd5b503461042f578060031936011261042f576080906040519050620186a08152620186a06020820152670de0b6b3a76400006040820152670de0b6b3a76400006060820152f35b503461042f578060031936011261042f576060601454604051906001600160401b03811682526001600160401b038160401c16602083015260801c6040820152f35b503461042f578060031936011261042f57602060405173d533a949740bb3306d119cc777fa900ba034cd528152f35b503461042f5760209061184336613b04565b9192509015611861576104279161185861402d565b925050506146e3565b6118729161186d613c9f565b6146e3565b610427565b503461042f578060031936011261042f57602061042761189561402d565b925050506151f9565b503461042f578060031936011261042f576020600f54604051908152f35b503461042f57606036600319011261042f576118d66139ce565b60406118e06139e4565b9260043581526005602052209060018060a01b03165f5260205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b503461042f578060031936011261042f576020600e54604051908152f35b503461042f57602036600319011261042f5760209060ff906040906001600160a01b036119646139b8565b168152601284522054166040519015158152f35b503461042f578060031936011261042f576020601d54604051908152f35b503461042f578060031936011261042f576119db337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b600a54806119e7575080f35b601e5580600a557fbf1ce7fb3a8e648b70ea830f99b52f7ea31554186d29763280751f42e77f63866020604051838152a180f35b503461042f57602036600319011261042f576020906040906001600160a01b03611a436139b8565b168152600783522054604051908152f35b503461042f578060031936011261042f5760406017548151906001600160801b038116825260801c6020820152f35b503461042f57602036600319011261042f577fc98711414b05c67ac27ffb415026d97ad5094c5490747891a834cb44f64940d96020600435611aef337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b80600f55604051908152a180f35b503461042f57604036600319011261042f57611b176139ce565b611b1f61413a565b611b2833614290565b6001600160a01b0381161561083757611b6290611b436144c4565b505050338352601b6020526040832054611b6c575b3390600435614644565b6040610ebc613c71565b611b74614158565b50611b58565b503461042f57602090611b8c36613b04565b9192509015611baa5761042791611ba161402d565b92505050614460565b61187291611bb6613c9f565b614460565b503461042f57602036600319011261042f57600435611c04337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b81601d54828103611c3f575b507fa83ced135f4f6135be6fb52ae9183bf190452765f5b75b36caa332537c3ca7e5602083604051908152a180f35b604051631526fe2760e01b815260048101919091527f000000000000000000000000f403c135812408bfbe8713b5a23a04b3d48aae316001600160a01b03169060c081602481855afa908115610db8578391611ee9575b506040516370a0823160e01b8152306004820152906001600160a01b0316602082602481845afa91821561089d578492611eb2575b5081611d95575b5090606460209260405194859384926321d0683360e11b84528860048501526024840152600160448401525af18015610db857611d37575b50601d819055817fa83ced135f4f6135be6fb52ae9183bf190452765f5b75b36caa332537c3ca7e5611c10565b6020813d602011611d8d575b81611d5060209383613a58565b810103126108a8577fa83ced135f4f6135be6fb52ae9183bf190452765f5b75b36caa332537c3ca7e591611d85602092613cf8565b509150611d0a565b3d9150611d43565b60209060446040959394955180948193636197390160e11b83528760048401528160248401525af18015611e7057611e7b575b506040516370a0823160e01b81523060048201526020816024817f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03165afa8015611e705782918691611e3b575b5010611e2c579083915f611cd2565b6346bc68ed60e01b8452600484fd5b9150506020813d602011611e68575b81611e5760209383613a58565b81010312610dac578190515f611e1d565b3d9150611e4a565b6040513d87823e3d90fd5b6020813d602011611eaa575b81611e9460209383613a58565b8101031261073f57611ea590613cf8565b611dc8565b3d9150611e87565b935090506020833d602011611ee1575b81611ecf60209383613a58565b81010312610dac57849251905f611ccb565b3d9150611ec2565b611f0b915060c03d60c011611f17575b611f038183613a58565b810190613d75565b5050925050505f611c96565b503d611ef9565b503461042f578060031936011261042f576040517f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b03168152602090f35b503461042f57602036600319011261042f576040611f7f6139b8565b611fb3337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b612021611fbe613c71565b805184516001600160a01b03918216815290841660208201529092907fb9d23ad01dc54c1fad84c770708fbe314a369b505c073949334dad786229805e90604090a16001600160a01b0316808352601580546001600160a01b0319169091179055565b6020810151601580546001600160a01b031660a09290921b6001600160a01b031916919091179055015160165580f35b503461042f578060031936011261042f5760206040517f0000000000000000000000000000000000000000000000000000000067d220008152f35b503461042f57602036600319011261042f576120a66139b8565b6120ae61413a565b3380835260086020908152604080852080546001600160a01b0319166001600160a01b039590951694851790555192835290917ff4239ad0860f93469699dd4be8040b8838c5e25bb6cf24a1dfb381b937ff078c9190a26001815580f35b503461042f578060031936011261042f576020604051734e3fbd56cd56c3e72c1403e103b45db9da5b9d2b8152f35b503461042f578060031936011261042f57602061042761217b7f0000000000000000000000000000000000000000000000000000000067d2200042613b9c565b7f0000000000000000000000000000000000000000000000000000000000093a8090613d4a565b503461042f57602036600319011261042f576004356121eb337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b620186a08111610be5577f1f93e1cc6afaef2ddc4d7ef7b0f45ac3278d548338c839e9be18695732a662746040600c548151908152836020820152a1600c5580f35b503461042f578060031936011261042f576040517f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e6001600160a01b03168152602090f35b503461042f57604036600319011261042f5761228c6139b8565b6122946139ce565b61229c61413a565b6001600160a01b0382163303612309576001600160a01b038116156122c45761092091614e9b565b60405162461bcd60e51b815260206004820152601760248201527f66776420616464726573732063616e6e6f7420626520300000000000000000006044820152606490fd5b60405162461bcd60e51b815260206004820152600560248201526410b9b2b63360d91b6044820152606490fd5b503461042f57602036600319011261042f5760043561237f337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b670de0b6b3a76400008111610be5576020817f3e0c428758b3ad6ab4fd85e8257e4eee404fb36e80c0251143635b549cd70b8892600d55604051908152a180f35b503461042f57604036600319011261042f576123da6139ce565b6123e261413a565b6001600160a01b0381161561083757610920906123fd6144c4565b50505060043533614bd4565b503461042f57602036600319011261042f576109206124266139b8565b61243161090e61495a565b61243961413a565b614a26565b503461042f578060031936011261042f5760206040517f0000000000000000000000000000000000000000000000000000000000093a808152f35b503461042f578060031936011261042f576020600254604051908152f35b503461042f578060031936011261042f576060906040519060038252602082015260026040820152f35b503461042f57602036600319011261042f5760206104276124e06139b8565b613f11565b503461042f57602036600319011261042f577f18e1a8f58cc03bc99f69c27336072db255c3f01827f2923f654ddc209e2b8db56020600435612551337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b80601055604051908152a180f35b503461042f578060031936011261042f576020610427613dcd565b503461042f57604036600319011261042f576125946139b8565b61259c613a79565b6125d0337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b612634575b601154604080516001600160a01b038084168252841660208201529192917f74cd8ef76f78382ae0f3ee4e21117be974af0041a24dc98f7aa8ddabb0b2960c9190a16001600160a01b03166001600160a01b0319919091161760115580f35b61263c6144c4565b5050506125d5565b503461042f578060031936011261042f5761265d61413a565b6126656144c4565b5050604051630abca72960e41b81527f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b03169150602081600481855afa908115610db857839161287e575b506040516379bd9e4160e01b81526001600160a01b03821690602081600481855afa908115611e7057859161284c575b5061271561217b7f0000000000000000000000000000000000000000000000000000000067d2200042613b9c565b90601c54821190811591612841575b5061283257601c5583601854936019549382601855826019556127478587613d68565b823b15610891576040516340c10f1960e01b81526001600160a01b0392909216600483015260248201529082908290604490829084905af18015610f135761281d575b5050803b15610891576040516362f2221960e01b8152836004820152826024820152848160448183865af18015611e7057612808575b506040937f9cc800ba322ea82ab3e1e911dd4ccd84129687c4952f4ee2f937e3ac68755131606060019387519081528660208201528588820152a15582519182526020820152f35b612813858092613a58565b610891575f6127c0565b8161282791613a58565b61089157835f61278a565b630e1b248d60e01b8552600485fd5b90508114155f612724565b90506020813d602011612876575b8161286760209383613a58565b81010312610dac57515f6126e7565b3d915061285a565b90506020813d6020116128b0575b8161289960209383613a58565b810103126108a8576128aa90613d23565b5f6126b7565b3d915061288c565b503461042f578060031936011261042f576040517f00000000000000000000000086f3188e24a6902c6d4d7b820eabdc4255f636e46001600160a01b03168152602090f35b503461042f578060031936011261042f57612942337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b600a541561294d5780f35b610a6e601e5461491b565b503461042f57604036600319011261042f576129726139b8565b61297a613a79565b90337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b0316148015612a1a575b15612a0b577fea1eefb4fd58778d7b274fe54045a9feeec8f2847899c2e71126d3a74d486da59160409160018060a01b03169081855260126020528285209015159060ff1981541660ff831617905582519182526020820152a180f35b631d1e647b60e01b8352600483fd5b50337f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b0316146129ae565b503461042f57604036600319011261042f576040612a686139b8565b91612a716139ce565b9260018060a01b031681526006602052209060018060a01b03165f52602052602060405f2054604051908152f35b503461042f57602036600319011261042f57612ab96139b8565b612ac161413a565b60405163d25adeb360e01b8152906020826004817f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b03165afa918215610db8578392612eb2575b506001600160a01b0382169133839003612ea3576001600160a01b0382168015612e9457612b3b6144c4565b505050612b46614158565b612b4f84614290565b612b598185614320565b612e8557612b65613c9f565b90828752601a602052604087205490838852601b602052612b896040892054614729565b926001600160801b0384169260208201926001600160801b03845116155f14612dee57612bbf670de0b6b3a76400009186613d37565b04600c54620186a0019081620186a011612dda576001600160801b0398969492620186a0612bf6612c749d9c9a9896948c94613d37565b0481811115612dd057509a8b955b877f158ba9ab7bbbd08eeffa4753bad41f4d450e24831d293427308badf3eadd8c766060612c3a612c358a896143c2565b614729565b9d8e6040519b8c528a60208d015216998a6040820152a2612c5a8a614d34565b82612c688c828751166140c3565b168452828551166140c3565b168252848b52601b602052612c8d8360408d2054613b9c565b858c52601b6020528060408d20558015159081612dbb575b50612dac5751905160801b6001600160801b0319166001600160801b03919091161760175588969594612d119490939092909188917fe4a1ae657f49cb1fb1c7d3a94ae6093565c4c8c0e03de488f79c377c3c3a24e0916040919082519182526020820152a386614644565b813b156108a857604051632be71c5f60e11b81526001600160a01b037f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd2289166004820152602481018590526001600160801b0391909116604482015291908290606490829084905af18015610db857612d93575b50600160209255604051908152f35b612d9e838092613a58565b612da8575f612d84565b5080fd5b631abfe8a760e01b8b5260048bfd5b612dc69150826143c2565b601054115f612ca5565b90509a8b95612c04565b634e487b7160e01b8c52601160045260248cfd5b612e16612e056001600160801b0385511687613d37565b6001600160801b0386511690613d4a565b8b80612e55575b612e35575b670de0b6b3a764000091612bbf91613d37565b6001810180911115612e2257634e487b7160e01b8c52601160045260248cfd5b5085612e7f612e6e6001600160801b0388511684613d37565b6001600160801b0387511690613d4a565b10612e1d565b633af2cafd60e11b8652600486fd5b631e4ec46b60e01b8552600485fd5b6387e9041360e01b8452600484fd5b9091506020813d602011612ee6575b81612ece60209383613a58565b810103126108a857612edf90613d23565b905f612b0f565b3d9150612ec1565b503461042f578060031936011261042f576040517f000000000000000000000000f403c135812408bfbe8713b5a23a04b3d48aae316001600160a01b03168152602090f35b503461042f578060031936011261042f576020601954604051908152f35b503461042f57602036600319011261042f576004358015158103612da857612f77613d05565b50610726604051612f8781613a0e565b5f81525f6020820152612f9861413a565b83612fa16144c4565b96915094612fba575b6001905560405194859485613a88565b9150506018546001612fca613c9f565b929050612faa565b503461042f578060031936011261042f57602060405164e8d4a510008152f35b503461042f578060031936011261042f576020600b54604051908152f35b503461042f578060031936011261042f576011546040516001600160a01b039091168152602090f35b5034610dac576080366003190112610dac576130536139b8565b60443591906064356024356001600160401b038211610dac5736602383011215610dac578160040135926001600160401b038411610dac576024830192602436918660051b010111610dac576130a761413a565b6130b033614290565b6130b86144c4565b5050506130c3614158565b506130cc613c9f565b9260018060a01b03821694855f52601260205260ff60405f20541615613596578015613582576130fb82613cd5565b6001600160a01b037f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22898116911681900361357957505f198101928184116135655761314f61314a858486613cc5565b613cd5565b6001600160a01b037f00000000000000000000000057ab1e0003f623289cd798b1824be09a793e4bec811695911685900361353357506001600160801b0386511615613524576131a190339086614644565b6040516370a0823160e01b815230600482015291602083602481875afa9283156134b7575f936134f0575b50863b15610dac57604051634b4ecc5560e11b815233600482015260248101869052608060448201526084810183905291829160a48301915f5b8181106134c25750505090805f923060648301520381838a5af180156134b7576134a2575b506040516370a0823160e01b8152306004820152602081602481865afa80156133ee578290889061346c575b6132619250613b9c565b968088106134555750856001600160801b03855116155f14613411575086935b338752601b60205260408720548086116133f9575b506132b7906132a489614729565b339130916132b189614729565b9161475a565b6040516370a0823160e01b8152306004820152602081602481865afa9081156133ee57908793929184916133b5575b50906132f191613b9c565b80613340575b505050604051928352602083015283604083015260608201527f6b2b212085c14b2dc48ec56d19c05084e5d8ef233539cdd3dc0d70a4ec7f4f9e60803392a26040610c6b613c71565b60405163a9059cbb60e01b815233600482015260248101919091529160209183916044918391905af18015611e705761337c575b8085916132f7565b6020813d6020116133ad575b8161339560209383613a58565b8101031261073f576133a690613cf8565b505f613374565b3d9150613388565b919350506020813d6020116133e6575b816133d260209383613a58565b81010312610dac57518692906132f16132e6565b3d91506133c5565b6040513d89823e3d90fd5b945096506132b761340a85896143c2565b9790613296565b9361343d61342c6001600160801b036020840151168a613d37565b6001600160801b0383511690613d4a565b94156132815793600181018091116107525793613281565b633b5d56ed60e11b87526004526024879052604486fd5b50506020813d60201161349a575b8161348760209383613a58565b81010312610dac57816132619151613257565b3d915061347a565b6134af9196505f90613a58565b5f945f61322b565b6040513d5f823e3d90fd5b9193509160019060209081906001600160a01b036134df886139fa565b168152019401910191849392613206565b9092506020813d60201161351c575b8161350c60209383613a58565b81010312610dac5751915f6131cc565b3d91506134ff565b631abfe8a760e01b5f5260045ffd5b905061314a9161354293613cc5565b63b0b3262d60e01b5f9081526004929092526001600160a01b0316602452604490fd5b634e487b7160e01b5f52601160045260245ffd5b61354283613cd5565b634e487b7160e01b5f52603260045260245ffd5b631311dc6d60e01b5f5260045ffd5b34610dac575f366003190112610dac576020601054604051908152f35b34610dac576020366003190112610dac5760043561360a337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b620186a0811161364b577fe796e9ae748449310fcd1cc6718aab236c9b8d2e0e04dacb232ba564d5b338cc60406009548151908152836020820152a1600955005b630309cb8760e51b5f5260045ffd5b34610dac575f366003190112610dac576040515f90601354918260011c6001841693841561376d575b60208210851461375957818452602084019490811561373e57506001146136e0575b50906136b5816040930382613a58565b8151928391602083525180918160208501528484015e5f828201840152601f01601f19168101030190f35b91905060135f527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090915f905b808210613724575090915081016020016136b56136a5565b91926001816020925483858801015201910190929161370c565b60ff1916855250151560051b820160200190506136b56136a5565b634e487b7160e01b5f52602260045260245ffd5b90607f1690613683565b34610dac576020366003190112610dac576004356137bf337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614613c3d565b7f388cba11a61d4b31467a533c88eef75abfb6097ff2ab4652d086560fffcc2f836040600b548151908152836020820152a1600b55005b34610dac575f366003190112610dac5761380e61413a565b6020613818614158565b60015f55604051908152f35b34610dac576020366003190112610dac5761383d6139b8565b61384561413a565b61384e81614d34565b60015461385a81613bc1565b916001600160a01b03165f5b8281106139415760015f5583515f19810190859082116135655761388982613bc1565b9160015b818111156138ef57836040518091602082016020835281518091526020604084019201905f5b8181106138c1575050500390f35b825180516001600160a01b0316855260209081015181860152869550604090940193909201916001016138b3565b60206138fb8285613c29565b510151905f1981018181116135655760019260206139198389613c29565b510152613939838060a01b0361392f8488613c29565b5151169187613c29565b51520161388d565b8061394d600192613b64565b5054828060a01b0381169081159081156139aa575b506139a257805f52600660205260405f20845f5260205260405f2054602061398a8489613c29565b5101526139978287613c29565b515201915b91613866565b50019161399c565b60ff915060a01c1687613962565b600435906001600160a01b0382168203610dac57565b602435906001600160a01b0382168203610dac57565b604435906001600160a01b0382168203610dac57565b35906001600160a01b0382168203610dac57565b604081019081106001600160401b03821117613a2957604052565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b03821117613a2957604052565b90601f801991011681019081106001600160401b03821117613a2957604052565b602435908115158203610dac57565b926001600160801b0391959460209183604060c09660e089019a89526001600160401b03815116868a01526001600160401b038682015116828a0152015116606087015260808601528281511660a0860152015116910152565b34610dac575f366003190112610dac576020604051670de0b6b3a76400008152f35b6060906003190112610dac57600435906024358015158103610dac57906044358015158103610dac5790565b34610dac575f366003190112610dac576020604051620186a08152f35b6001600160401b038111613a295760051b60200190565b6001548110156135825760015f81815291901b7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60191565b9190820391821161356557565b60405190613bb682613a0e565b5f6020838281520152565b90613bcb82613b4d565b613bd86040519182613a58565b8281528092613be9601f1991613b4d565b01905f5b828110613bf957505050565b602090604051613c0881613a0e565b5f81525f8382015282828501015201613bed565b8051156135825760200190565b80518210156135825760209160051b010190565b15613c4457565b60405162461bcd60e51b815260206004820152600560248201526421636f726560d81b6044820152606490fd5b60405190613c7e82613a3d565b6015546001600160a01b038116835260a01c60208301526016546040830152565b60405190613cac82613a0e565b6017546001600160801b038116835260801c6020830152565b91908110156135825760051b0190565b356001600160a01b0381168103610dac5790565b90816020910312610dac575190565b51908115158203610dac57565b60405190613d1282613a3d565b5f6040838281528260208201520152565b51906001600160a01b0382168203610dac57565b8181029291811591840414171561356557565b8115613d54570490565b634e487b7160e01b5f52601260045260245ffd5b9190820180921161356557565b91908260c0910312610dac57613d8a82613d23565b91613d9760208201613d23565b91613da460408301613d23565b91613db160608201613d23565b91613dca60a0613dc360808501613d23565b9301613cf8565b90565b601d548015613eba57604051631526fe2760e01b8152600481019190915260c0816024817f000000000000000000000000f403c135812408bfbe8713b5a23a04b3d48aae316001600160a01b03165afa9081156134b7576024916020915f91613e95575b506040516370a0823160e01b815230600482015292839182906001600160a01b03165afa9081156134b7575f91613e66575090565b90506020813d602011613e8d575b81613e8160209383613a58565b81010312610dac575190565b3d9150613e74565b613eae915060c03d60c011611f1757611f038183613a58565b5050925050505f613e31565b506040516370a0823160e01b81523060048201526020816024817f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896001600160a01b03165afa9081156134b7575f91613e66575090565b6001600160a01b03165f908152601b6020908152604080832054600254600390935292205491929190808210613f45575050565b909264e8d4a510006001915b04930192818414613f6a5764e8d4a51000600191613f51565b925050565b15613f7657565b60405162461bcd60e51b815260206004820152600e60248201526d10b932bbb0b93226b0b730b3b2b960911b6044820152606490fd5b613fb461413a565b613fbd81614290565b6001600160a01b03165f908152601a6020526040902054613fdc613dcd565b9081811115613fee57505b9060015f55565b9050613fe7565b6040519061400282613a3d565b8160406014546001600160401b03811683526001600160401b0381831c16602084015260801c910152565b5f614036613d05565b5060405161404381613a0e565b5f81525f602082015250614055613ff5565b9061405f826154f7565b8051909190156140b457506060810151926001600160401b0360208301511660208401526001600160801b03604083015116604084015260806140ae6018546001600160801b03871690613d68565b92015190565b92905060185490613dca613c9f565b906001600160801b03809116911603906001600160801b03821161356557565b600a54801561412d576001600160801b036140fc61402d565b92505050511690670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561356557613dca91613d4a565b50670de0b6b3a764000090565b60025f54146141495760025f55565b633ee5aeb560e01b5f5260045ffd5b614160613c71565b80516040516315caaba160e21b81526001600160a01b037f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd228981166004830152929392909160209183916024918391165afa9081156134b7575f9161425e575b508015613d54576ec097ce7bc90715b34b9f10000000000491604081019083825181036141eb57505050565b6bffffffffffffffffffffffff4290811660208481019190915291909352905160a09290921b6001600160a01b0319166001600160a01b039092169190911760155560168390556040518381527f4fc1b45960547ee95894b08a284c3c066cf5aca706a7420639c42c3ec2e118a49190a1565b90506020813d602011614288575b8161427960209383613a58565b81010312610dac57515f6141bf565b3d915061426c565b61429981614d34565b6001600160a01b037f00000000000000000000000086f3188e24a6902c6d4d7b820eabdc4255f636e481165f90815260066020908152604080832094909316808352938152828220805490839055601a9091529190205490620186a090048082106143175761430791613b9c565b905b5f52601a60205260405f2055565b50505f90614309565b906009549182156143ba57614356614336613c9f565b6001600160a01b039092165f818152601b602052604090205490926143c2565b9081156143b1575f52601a60205260405f20549182156143a95761437991613d37565b90620186a0820291808304620186a0149015171561356557670de0b6b3a76400006143a49204613d4a565b111590565b505050505f90565b50505050600190565b505050600190565b6020810180516001600160801b03166143db575050905b565b9092916144066143f56001600160801b0386511683613d37565b6001600160801b0384511690613d4a565b936001600160801b0381511615159283614433575b50505061442457565b90600181018091116135655790565b61445792935061444e6001600160801b038092511687613d37565b91511690613d4a565b105f808061441b565b909160208201916001600160801b03835116155f1461447f5750505090565b61449b612e6e6001600160801b03839796949597511684613d37565b94836144b0575b836144335750505061442457565b81516001600160801b0316151593506144a2565b5f905f906144d0613d05565b506144d9613ff5565b906144e3826154f7565b80511515806144f0575050565b9194509250926143d96001600160801b03602060806060870151968287016001600160401b0381511690604089019186835116907f6b9ef8676ff86d806b7a7bd7a9b0266910c9fce560c26289d7ed7cd7743127c786888701926001600160401b038451169460408901958c875116916040519384528c84015260408301526060820152a17f939dcec711228d083924a0db6cecbb66bb8403ea7cdbfe2f928901cbac2cdfc160408d6001600160401b0384511682519182528a820152a151916001600160401b0383169052519186831690526001600160401b034216808a526fffffffffffffffff00000000000000006014549260401b169187191617176014558460145491811990821990861b161691161760145561461388601854613d68565b601855015182815116831960175416176017550151166001600160801b036017549181199060801b16911617601755565b9160207fba68c7a8c8efbddb7e938ee32ecc28a68930c18105a3a0ab9563eac7f051cf5a9160018060a01b031693845f52601a825260405f20614688828254613b9c565b9055614693816157d2565b6001600160a01b03841693813086036146b2575b5050604051908152a3565b6146dc917f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd22896158d3565b5f816146a7565b90916001600160801b03825116155f146146fc57505090565b9092916020840161471a612e6e6001600160801b0383511684613d37565b94836144335750505061442457565b6001600160801b038111614743576001600160801b031690565b6306dfcc6560e41b5f52608060045260245260445ffd5b9391909161476781614d34565b6001600160801b0361477c84828851166140c3565b16855260208501906001600160801b0361479984828551166140c3565b16825260018060a01b031694855f52601b6020526147c66001600160801b0360405f205494168094613b9c565b865f52601b6020528060405f20558015159081614906575b506135245751905160801b6001600160801b0319166001600160801b0391909116176017556001600160a01b0383169283614850575b507fe4a1ae657f49cb1fb1c7d3a94ae6093565c4c8c0e03de488f79c377c3c3a24e0916040916001600160801b038351921682526020820152a3565b917f00000000000000000000000057ab1e0003f623289cd798b1824be09a793e4bec6001600160a01b0316803b15610dac57604051632770a7eb60e21b81526001600160a01b039490941660048501526001600160801b03821660248501525f908490604490829084905af19182156134b7577fe4a1ae657f49cb1fb1c7d3a94ae6093565c4c8c0e03de488f79c377c3c3a24e0936040936148f6575b50915091614814565b5f61490091613a58565b5f6148ed565b6149119150826143c2565b601054115f6147de565b6001600160801b03811161364b576020817fbf1ce7fb3a8e648b70ea830f99b52f7ea31554186d29763280751f42e77f638692600a55604051908152a1565b337f000000000000000000000000c07e000044f95655c11fda4cd37f70a94d7e0a7d6001600160a01b031614801561498f5790565b5060405163670fb82160e01b81526020816004817f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b03165afa9081156134b7575f916149ec575b506001600160a01b0316331490565b90506020813d602011614a1e575b81614a0760209383613a58565b81010312610dac57614a1890613d23565b5f6149dd565b3d91506149fa565b6001600160a01b038116903082148015614bcc575b8015614bbc575b614b8257815f52600760205260405f2054155f14614b4e57600154600f811015614b1b5768010000000000000000811015613a2957806001614a879201600155613b64565b50826bffffffffffffffffffffffff60a01b825416179055600154825f52600760205260405f2055817fb13fd610fe4e1b384966826794a9b2f6100ad031f352cc5ec6f22667f60749805f80a2803b15614b1157506143d9906040519063a9059cbb60e01b60208301523060248301525f604483015260448252614b0c606483613a58565b615cd8565b6143d99150615726565b60405162461bcd60e51b815260206004820152600b60248201526a6d6178207265776172647360a81b6044820152606490fd5b50805f52600760205260405f20545f19810190811161356557614b7090613b64565b5080546001600160a01b031615614b86575b5050565b80546001600160a01b031916821790557fb13fd610fe4e1b384966826794a9b2f6100ad031f352cc5ec6f22667f60749805f80a2565b50614bc68161590f565b15614a42565b508115614a3b565b9160018060a01b031691825f52601a60205260405f20614bf5838254613d68565b905581306001600160a01b03831603614d01575b5050601d5480614c42575b5060207f2ac90482c3b6bea30a2c085cf093016bad7f970d91c5fd233e6b848d3e231dc391604051908152a2565b6040516321d0683360e11b8152600481019190915260248101829052600160448201526020816064815f7f000000000000000000000000f403c135812408bfbe8713b5a23a04b3d48aae316001600160a01b03165af180156134b75715614c14576020813d602011614cf9575b81614cbc60209383613a58565b81010312610dac577f2ac90482c3b6bea30a2c085cf093016bad7f970d91c5fd233e6b848d3e231dc391614cf1602092613cf8565b509150614c14565b3d9150614caf565b614d2d9130907f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd228961523b565b5f81614c09565b5f907f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b0316803b15610dac575f8091602460405180948193633bd73ee360e21b83523060048401525af180156134b757614e86575b506002549060015490835b5f198110614daa575050505050565b836001600160a01b03831680614e3d575b865b858110614e16575085821015614e0d57808752601b60205264e8d4a51000604088205404818852601b60205260408820556001820180921161075257865260036020526040862055600101614d9b565b50505050505050565b80614e2e89876001948b889c999d9e9a979b98615980565b01919490939796929591614dbd565b8087526003602052604087205491508582141580614e72575b15614dbb57905084908087526003602052856040882055614dbb565b50808752601b602052604087205415614e56565b614e939192505f90613a58565b5f905f614d90565b7f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b031691823b15610dac57604051633bd73ee360e21b81525f816024818380983060048401525af180156134b757614fed575b506002549160015492845b5f198110614f1057505050505050565b816001600160a01b03841680614fa4575b875b878110614f7d575083821015614f7357808852601b60205264e8d4a51000604089205404818952601b6020526040892055600182018092116107dc57875260036020526040872055600101614f00565b5050505050505050565b80614f95888860019489889d999e9f9b979c98615980565b01929691959094989792614f23565b8088526003602052604088205491508382141580614fd9575b15614f2157905082908088526003602052836040892055614f21565b50808852601b602052604088205415614fbd565b614ffa9193505f90613a58565b5f915f614ef5565b5f91907f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b0316803b15610dac575f8091602460405180948193633bd73ee360e21b83523060048401525af180156134b75761513b575b506002549060015491845b84811061507957505050505050565b816001600160a01b038416806150f2575b875b8681106150dc575083821015614f7357808852601b60205264e8d4a51000604089205404818952601b6020526040892055600182018092116107dc5787526003602052604087205560010161506a565b806150ec8a886001948988615980565b0161508c565b8088526003602052604088205491508382141580615127575b1561508a5790508290808852600360205283604089205561508a565b50808852601b60205260408820541561510b565b6151489193505f90613a58565b5f915f61505f565b5f7f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b0316803b15610dac575f8091602460405180948193633bd73ee360e21b83523060048401525af180156134b7576151e6575b5060025460015491905f1982106151c157505050565b815b8381106151d05750505050565b806151e084806001948680615980565b016151c3565b6151f291505f90613a58565b5f5f6151ab565b600a5490516001600160801b0316808211156152335761521891613b9c565b6001600160801b03811115613dca57506001600160801b0390565b50505f615218565b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526143d991614b0c608483613a58565b906001600160801b03809116911601906001600160801b03821161356557565b9190916152aa613c9f565b92601054916001600160801b03811692831061352457600b54620186a00180620186a011613565576152e0620186a09185613d37565b046152ea866151f9565b8181106154e2575085516001600160801b031661548357805b61536b82976001600160801b036020820191816153446153268883875116613d68565b8261533c6153338a614729565b8287511661527f565b168452614729565b1692839052516001600160801b0391161660809190911b6001600160801b03191617601755565b335f52601b60205260405f20615382838254613d68565b9055848082111561547a5761539691613b9c565b915b82615466575b7f00000000000000000000000010101010e0c3171d894b71b3400668af311e7d946001600160a01b0316803b15610dac576040516340c10f1960e01b81526001600160a01b03861660048201526001600160801b039290921660248301525f908290604490829084905af180156134b757615456575b506040519384526020840152604083015260018060a01b0316907f10a0132d3bf8c82a7fb93a86160f3074ca5c3e5706fa2bcdf0e2b5fd495af09b60603392a3565b5f61546091613a58565b5f615414565b61547283601954613d68565b60195561539e565b50505f91615398565b6020860190806154ca6154b46154a36001600160801b0386511684613d37565b6001600160801b038b511690613d4a565b936001600160801b0361444e818c511687613d37565b10156153035790600181018091116135655790615303565b63586ce21f60e11b5f5260045260245260445ffd5b9060405160a081018181106001600160401b03821117613a29576040525f8152602081015f815260408201935f855260608301915f8352608084019360405161553f81613a0e565b5f81525f6020820152855280966001600160401b038451164211615566575b505050505050565b60016155fd9252615575613c9f565b8652604061558d6001600160401b0386511642613b9c565b6011549582015182516356fb8f2d60e01b81526001600160a01b037f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd228981166004830152602482018490526001600160801b0390921660448201529196919485929190911690829081906064820190565b03915afa80156134b7575f925f916156bf575b50670de0b6b3a7640000946001600160401b03615657959481946001600160801b0361564e951690521684526001600160801b038851511690613d37565b91511690613d37565b04808252801515908161569e575b5015615695576156866001600160801b03809251169251928284511661527f565b1690525b5f808080808061555e565b5f91505261568a565b6001600160801b0391506156b790828551511690613d68565b11155f615665565b939250506040833d60401161571e575b816156dc60409383613a58565b81010312610dac578251926001600160401b0384168403610dac5760200151906001600160801b0382168203610dac5791929190670de0b6b3a7640000615610565b3d91506156cf565b6001600160a01b03165f8181526007602052604090205480615746575050565b5f1981019081116135655761575a90613b64565b5080546001600160a01b03168290036157a15780546001600160a01b03191690557f646cfe9445aed85f4853d501d1924d2bdabb1bbf12531df29f929f07ba4169e05f80a2565b60405162461bcd60e51b8152602060048201526009602482015268042dad2e6dac2e8c6d60bb1b6044820152606490fd5b601d54806157de575050565b604051631526fe2760e01b8152600481019190915260c0816024817f000000000000000000000000f403c135812408bfbe8713b5a23a04b3d48aae316001600160a01b03165afa80156134b7575f9260209284926158a9575b50604051636197390160e11b815260048101919091526024810184905292839160449183916001600160a01b03165af180156134b7576158745750565b6020813d6020116158a1575b8161588d60209383613a58565b81010312610dac5761589e90613cf8565b50565b3d9150615880565b60449192506158c69060c03d60c011611f1757611f038183613a58565b5050925050509190615837565b60405163a9059cbb60e01b60208201526001600160a01b039290921660248301526044808301939093529181526143d991614b0c606483613a58565b6001600160a01b03908116907f000000000000000000000000c33aa628b10655b36eaa7ee880d6bc4789dd228916811461597b577f00000000000000000000000057ab1e0003f623289cd798b1824be09a793e4bec6001600160a01b03161461597757600190565b5f90565b505f90565b93929161598c90613b64565b5080546001600160a01b03169290831561555e576040516370a0823160e01b815230600482015293602085602481845afa9485156134b7575f95615ca4575b508496600183019687549582149686888099615c96575b80615c8d575b615c0c575b50505f8281526004602090815260408083206001600160a01b03878116855292529091205495169384615a3e575b50505050505081615a33575b50615a30575050565b55565b90508214155f615a27565b825f52600560205260405f2060018060a01b0385165f5260205260405f20855f5260205260405f20548860018060a01b03841692831580158091615c03575b615a8b575b50505050615a1b565b82615bfb575b5081615beb575b5015615b8b57615ae690855f52600660205260405f20875f5260205269021e19e0c9bab2400000615adf60405f205492895f52601b60205261060260405f2054918c613b9c565b0490613d68565b9182615b28575b5050505b5f52600560205260405f209060018060a01b03165f5260205260405f20905f5260205260405f20555f808080808080888180615a82565b90615b5183615b82959c93875f52600660205260405f20895f526020525f6040812055876158d3565b84867fce405e67b4d6e56e438257e15f160ae28b450e6e7659bbc4c1f4e09a1ac846cb6020604051878152a4613b9c565b965f8080615aed565b9050615bcf9150835f52600660205260405f20855f5260205269021e19e0c9bab2400000615adf60405f205492875f52601b60205261060260405f2054918a613b9c565b825f52600660205260405f20845f5260205260405f2055615af1565b60ff91505460a01c16155f615a98565b91505f615a91565b50898410615a7d565b615c1591613b9c565b69021e19e0c9bab240000081029080820469021e19e0c9bab2400000149015171561356557615c4a9060175460801c90613d4a565b8015615c8357825f52600460205260405f2060018060a01b0385165f52602052615c7960405f20918254613d68565b90555b5f866159ed565b5097508497615c7c565b508082116159e8565b5060175460801c15156159e2565b9094506020813d602011615cd0575b81615cc060209383613a58565b81010312610dac5751935f6159cb565b3d9150615cb3565b905f602091828151910182855af1156134b7575f513d615d2757506001600160a01b0381163b155b615d075750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b60011415615d0056fea164736f6c634300081c000a

Verified Source Code Partial Match

Compiler: v0.8.28+commit.7893614a EVM: cancun Optimization: Yes (200 runs)
ResupplyPair.sol 450 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/**
 * @title ResupplyPair
 * @notice Based on code from Drake Evans and Frax Finance's lending pair contract (https://github.com/FraxFinance/fraxlend), adapted for Resupply Finance
 */

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { ResupplyPairConstants } from "./pair/ResupplyPairConstants.sol";
import { ResupplyPairCore } from "./pair/ResupplyPairCore.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { VaultAccount, VaultAccountingLibrary } from "../libraries/VaultAccount.sol";
import { IRateCalculator } from "../interfaces/IRateCalculator.sol";
import { ISwapper } from "../interfaces/ISwapper.sol";
import { IFeeDeposit } from "../interfaces/IFeeDeposit.sol";
import { IResupplyRegistry } from "../interfaces/IResupplyRegistry.sol";
import { IConvexStaking } from "../interfaces/IConvexStaking.sol";
import { EpochTracker } from "../dependencies/EpochTracker.sol";

contract ResupplyPair is ResupplyPairCore, EpochTracker {
    using VaultAccountingLibrary for VaultAccount;
    using SafeERC20 for IERC20;
    using SafeCast for uint256;

    uint256 public lastFeeEpoch;
    address public constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52;
    address public constant CVX = 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B;

    // Staking Info
    address public immutable convexBooster;
    uint256 public convexPid;
    
    error FeesAlreadyDistributed();
    error IncorrectStakeBalance();

    /// @param _core Core contract address
    /// @param _configData config data
    /// @param _immutables immutable data
    /// @param _customConfigData extras
    constructor(
        address _core,
        bytes memory _configData,
        bytes memory _immutables,
        bytes memory _customConfigData
    ) ResupplyPairCore(_core, _configData, _immutables, _customConfigData) EpochTracker(_core) {

        (, address _govToken, address _convexBooster, uint256 _convexpid) = abi.decode(
            _customConfigData,
            (string, address, address, uint256)
        );
        //add gov token rewards
        _insertRewardToken(_govToken);

        //convex info
        if(_convexBooster != address(0)){
            convexBooster = _convexBooster;
            convexPid = _convexpid;
            //approve
            collateral.forceApprove(convexBooster, type(uint256).max);
            //add rewards for curve staking
            _insertRewardToken(CRV);
            _insertRewardToken(CVX);

            emit SetConvexPool(_convexpid);
        }
    }


    // ============================================================================================
    // Functions: Helpers
    // ============================================================================================

    function getConstants()
        external
        pure
        returns (
            uint256 _LTV_PRECISION,
            uint256 _LIQ_PRECISION,
            uint256 _EXCHANGE_PRECISION,
            uint256 _RATE_PRECISION
        )
    {
        _LTV_PRECISION = LTV_PRECISION;
        _LIQ_PRECISION = LIQ_PRECISION;
        _EXCHANGE_PRECISION = EXCHANGE_PRECISION;
        _RATE_PRECISION = RATE_PRECISION;
    }

    /// @notice The ```getUserSnapshot``` function gets user level accounting data
    /// @param _address The user address
    /// @return _borrowShares The user borrow shares
    /// @return _collateralBalance The user collateral balance
    function getUserSnapshot(
        address _address
    ) external returns (uint256 _borrowShares, uint256 _collateralBalance) {
        _collateralBalance = userCollateralBalance(_address);
        _borrowShares = userBorrowShares(_address);
    }

    /// @notice The ```getPairAccounting``` function gets all pair level accounting numbers
    /// @return _claimableFees Total claimable fees
    /// @return _totalBorrowAmount Total borrows
    /// @return _totalBorrowShares Total borrow shares
    /// @return _totalCollateral Total collateral
    function getPairAccounting()
        external
        view
        returns (
            uint256 _claimableFees,
            uint128 _totalBorrowAmount,
            uint128 _totalBorrowShares,
            uint256 _totalCollateral
        )
    {
        VaultAccount memory _totalBorrow;
        (, , _claimableFees, _totalBorrow) = previewAddInterest();
        _totalBorrowAmount = _totalBorrow.amount;
        _totalBorrowShares = _totalBorrow.shares;
        _totalCollateral = totalCollateral();
    }

    /// @notice The ```toBorrowShares``` function converts a given amount of borrow debt into the number of shares
    /// @param _amount Amount of borrow
    /// @param _roundUp Whether to roundup during division
    /// @param _previewInterest Whether to simulate interest accrual
    /// @return _shares The number of shares
    function toBorrowShares(
        uint256 _amount,
        bool _roundUp,
        bool _previewInterest
    ) external view returns (uint256 _shares) {
        if (_previewInterest) {
            (, , , VaultAccount memory _totalBorrow) = previewAddInterest();
            _shares = _totalBorrow.toShares(_amount, _roundUp);
        } else {
            _shares = totalBorrow.toShares(_amount, _roundUp);
        }
    }

    /// @notice The ```toBorrowAmount``` function converts a given amount of borrow debt into the number of shares
    /// @param _shares Shares of borrow
    /// @param _roundUp Whether to roundup during division
    /// @param _previewInterest Whether to simulate interest accrual
    /// @return _amount The amount of asset
    function toBorrowAmount(
        uint256 _shares,
        bool _roundUp,
        bool _previewInterest
    ) external view returns (uint256 _amount) {
        if (_previewInterest) {
            (, , , VaultAccount memory _totalBorrow) = previewAddInterest();
            _amount = _totalBorrow.toAmount(_shares, _roundUp);
        } else {
            _amount = totalBorrow.toAmount(_shares, _roundUp);
        }
    }
    // ============================================================================================
    // Functions: Configuration
    // ============================================================================================


    /// @notice The ```SetOracleInfo``` event is emitted when the oracle info is set
    /// @param oldOracle The old oracle address
    /// @param newOracle The new oracle address
    event SetOracleInfo(
        address oldOracle,
        address newOracle
    );

    /// @notice The ```setOracleInfo``` function sets the oracle data
    /// @param _newOracle The new oracle address
    function setOracle(address _newOracle) external onlyOwner{
        ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
        emit SetOracleInfo(
            _exchangeRateInfo.oracle,
            _newOracle
        );
        _exchangeRateInfo.oracle = _newOracle;
        exchangeRateInfo = _exchangeRateInfo;
    }

    /// @notice The ```SetMaxLTV``` event is emitted when the max LTV is set
    /// @param oldMaxLTV The old max LTV
    /// @param newMaxLTV The new max LTV
    event SetMaxLTV(uint256 oldMaxLTV, uint256 newMaxLTV);

    /// @notice The ```setMaxLTV``` function sets the max LTV
    /// @param _newMaxLTV The new max LTV
    function setMaxLTV(uint256 _newMaxLTV) external onlyOwner{
        if (_newMaxLTV > LTV_PRECISION) revert InvalidParameter();
        emit SetMaxLTV(maxLTV, _newMaxLTV);
        maxLTV = _newMaxLTV;
    }

 
    /// @notice The ```SetRateCalculator``` event is emitted when the rate contract is set
    /// @param oldRateCalculator The old rate contract
    /// @param newRateCalculator The new rate contract
    event SetRateCalculator(address oldRateCalculator, address newRateCalculator);

    /// @notice The ```setRateCalculator``` function sets the rate contract address
    /// @param _newRateCalculator The new rate contract address
    /// @param _updateInterest Whether to update interest before setting new rate calculator
    function setRateCalculator(address _newRateCalculator, bool _updateInterest) external onlyOwner{
        //should add interest before changing rate calculator
        //however if there is an intrinsic problem with the current rate calculate, need to be able
        //to update without calling addInterest
        if(_updateInterest){
            _addInterest();
        }
        emit SetRateCalculator(address(rateCalculator), _newRateCalculator);
        rateCalculator = IRateCalculator(_newRateCalculator);
    }


    /// @notice The ```SetLiquidationFees``` event is emitted when the liquidation fees are set
    /// @param oldLiquidationFee The old clean liquidation fee
    /// @param newLiquidationFee The new clean liquidation fee
    event SetLiquidationFees(
        uint256 oldLiquidationFee,
        uint256 newLiquidationFee
    );

    /// @notice The ```setLiquidationFees``` function sets the liquidation fees
    /// @param _newLiquidationFee The new clean liquidation fee
    function setLiquidationFees(
        uint256 _newLiquidationFee
    ) external onlyOwner{
        if (_newLiquidationFee > LIQ_PRECISION) revert InvalidParameter();
        emit SetLiquidationFees(
            liquidationFee,
            _newLiquidationFee
        );
        liquidationFee = _newLiquidationFee;
    }

    /// @notice The ```SetMintFees``` event is emitted when the liquidation fees are set
    /// @param oldMintFee The old mint fee
    /// @param newMintFee The new mint fee
    event SetMintFees(
        uint256 oldMintFee,
        uint256 newMintFee
    );

    /// @notice The ```setMintFees``` function sets the mint
    /// @param _newMintFee The new mint fee
    function setMintFees(
        uint256 _newMintFee
    ) external onlyOwner{
        emit SetMintFees(
            mintFee,
            _newMintFee
        );
        mintFee = _newMintFee;
    }

    function setBorrowLimit(uint256 _limit) external onlyOwner{
        _setBorrowLimit(_limit);
    }

    /// @notice The ```SetBorrowLimit``` event is emitted when the borrow limit is set
    /// @param limit The new borrow limit
    event SetBorrowLimit(uint256 limit);

    function _setBorrowLimit(uint256 _limit) internal {
        if(_limit > type(uint128).max){
            revert InvalidParameter();
        }
        borrowLimit = _limit;
        emit SetBorrowLimit(_limit);
    }

    event SetMinimumRedemption(uint256 min);

    function setMinimumRedemption(uint256 _min) external onlyOwner{
        if(_min < 100 * PAIR_DECIMALS ){
            revert InvalidParameter();
        }
        minimumRedemption = _min;
        emit SetMinimumRedemption(_min);
    }

    event SetMinimumLeftover(uint256 min);

    function setMinimumLeftoverDebt(uint256 _min) external onlyOwner{
        minimumLeftoverDebt = _min;
        emit SetMinimumLeftover(_min);
    }

    event SetMinimumBorrowAmount(uint256 min);

    function setMinimumBorrowAmount(uint256 _min) external onlyOwner{
        minimumBorrowAmount = _min;
        emit SetMinimumBorrowAmount(_min);
    }

    event SetProtocolRedemptionFee(uint256 fee);

    /// @notice Sets the redemption fee percentage for this specific pair
    /// @dev The fee is 1e18 precision (1e16 = 1%) and taken from redemptions and sent to the protocol.
    /// @param _fee The new redemption fee percentage. Must be less than or equal to 1e18 (100%)
    function setProtocolRedemptionFee(uint256 _fee) external onlyOwner{
        if(_fee > EXCHANGE_PRECISION) revert InvalidParameter();

        protocolRedemptionFee = _fee;
        emit SetProtocolRedemptionFee(_fee);
    }

    /// @notice The ```WithdrawFees``` event fires when the fees are withdrawn
    /// @param recipient To whom the assets were sent
    /// @param interestFees the amount of interest based fees claimed
    /// @param otherFees the amount of other fees claimed(mint/redemption)
    event WithdrawFees(address recipient, uint256 interestFees, uint256 otherFees);

    /// @notice The ```withdrawFees``` function withdraws fees accumulated
    /// @return _fees the amount of interest based fees claimed
    /// @return _otherFees the amount of other fees claimed(mint/redemption)
    function withdrawFees() external nonReentrant returns (uint256 _fees, uint256 _otherFees) {

        // Accrue interest if necessary
        _addInterest();

        //get deposit contract
        address feeDeposit = IResupplyRegistry(registry).feeDeposit();
        uint256 lastDistributedEpoch = IFeeDeposit(feeDeposit).lastDistributedEpoch();
        uint256 currentEpoch = getEpoch();

        //current epoch must be greater than last claimed epoch
        //current epoch must be equal to the FeeDeposit prev distributed epoch (FeeDeposit must distribute first)
        if(currentEpoch <= lastFeeEpoch || currentEpoch != lastDistributedEpoch){
            revert FeesAlreadyDistributed();
        }

        lastFeeEpoch = currentEpoch;

        //get fees and clear
        _fees = claimableFees;
        _otherFees = claimableOtherFees;
        claimableFees = 0;
        claimableOtherFees = 0;
        //mint new stables to the receiver
        IResupplyRegistry(registry).mint(feeDeposit,_fees+_otherFees);
        //inform deposit contract of this pair's contribution
        IFeeDeposit(feeDeposit).incrementPairRevenue(_fees,_otherFees);
        emit WithdrawFees(feeDeposit, _fees, _otherFees);
    }

    /// @notice The ```SetSwapper``` event fires whenever a swapper is black or whitelisted
    /// @param swapper The swapper address
    /// @param approval The approval
    event SetSwapper(address swapper, bool approval);

    /// @notice The ```setSwapper``` function is called to black or whitelist a given swapper address
    /// @dev
    /// @param _swapper The swapper address
    /// @param _approval The approval
    function setSwapper(address _swapper, bool _approval) external{
        if(msg.sender == owner() || msg.sender == registry){
            swappers[_swapper] = _approval;
            emit SetSwapper(_swapper, _approval);
        }else{
            revert OnlyProtocolOrOwner();
        }
    }

    /// @notice The ```SetConvexPool``` event fires when convex pool id is updated
    /// @param pid the convex pool id
    event SetConvexPool(uint256 pid);

    /// @notice The ```setConvexPool``` function is called update the underlying convex pool
    /// @dev
    /// @param pid the convex pool id
    function setConvexPool(uint256 pid) external onlyOwner{
        _updateConvexPool(pid);
        emit SetConvexPool(pid);
    }

    function _updateConvexPool(uint256 _pid) internal{
        uint256 currentPid = convexPid;
        if(currentPid != _pid){
            //get previous staking
            (,,,address _rewards,,) = IConvexStaking(convexBooster).poolInfo(currentPid);
            //get balance
            uint256 stakedBalance = IConvexStaking(_rewards).balanceOf(address(this));
            
            if(stakedBalance > 0){
                //withdraw
                IConvexStaking(_rewards).withdrawAndUnwrap(stakedBalance,false);
                if(collateral.balanceOf(address(this)) < stakedBalance){
                    revert IncorrectStakeBalance();
                }
            }

            //stake in new pool
            IConvexStaking(convexBooster).deposit(_pid, stakedBalance, true);

            //update pid
            convexPid = _pid;
        }
    }

    function _stakeUnderlying(uint256 _amount) internal override{
        uint256 currentPid = convexPid;
        if(currentPid != 0){
            IConvexStaking(convexBooster).deposit(currentPid, _amount, true);
        }
    }

    function _unstakeUnderlying(uint256 _amount) internal override{
        uint256 currentPid = convexPid;
        if(currentPid != 0){
            (,,,address _rewards,,) = IConvexStaking(convexBooster).poolInfo(currentPid);
            IConvexStaking(_rewards).withdrawAndUnwrap(_amount, false);
        }
    }

    function totalCollateral() public view override returns(uint256 _totalCollateralBalance){
        uint256 currentPid = convexPid;
        if(currentPid != 0){
            //get staking
            (,,,address _rewards,,) = IConvexStaking(convexBooster).poolInfo(currentPid);
            //get balance
            _totalCollateralBalance = IConvexStaking(_rewards).balanceOf(address(this));
        }else{
            _totalCollateralBalance = collateral.balanceOf(address(this));   
        }
    }

    // ============================================================================================
    // Functions: Access Control
    // ============================================================================================

    uint256 previousBorrowLimit;
    /// @notice The ```pause``` function is called to pause all contract functionality
    function pause() external onlyOwner{
        if (borrowLimit > 0) {
            previousBorrowLimit = borrowLimit;
            _setBorrowLimit(0);
        }
    }

    /// @notice The ```unpause``` function is called to unpause all contract functionality
    function unpause() external onlyOwner{
        if (borrowLimit == 0) _setBorrowLimit(previousBorrowLimit);
    }
}
ERC20.sol 311 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * Both values are immutable: they can only be set once during construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

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

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
ReentrancyGuard.sol 87 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}
SafeCast.sol 1162 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }

    /**
     * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
     */
    function toUint(bool b) internal pure returns (uint256 u) {
        assembly ("memory-safe") {
            u := iszero(iszero(b))
        }
    }
}
ResupplyPairConstants.sol 32 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/**
 * @title ResupplyPairConstants
 * @notice Based on code from Drake Evans and Frax Finance's pair constants (https://github.com/FraxFinance/fraxlend), adapted for Resupply Finance
 */

abstract contract ResupplyPairConstants {

    // Precision settings
    uint256 public constant LTV_PRECISION = 1e5; // 5 decimals
    uint256 public constant LIQ_PRECISION = 1e5;
    uint256 public constant EXCHANGE_PRECISION = 1e18;
    uint256 public constant RATE_PRECISION = 1e18;
    uint256 public constant SHARE_REFACTOR_PRECISION = 1e12;
    uint256 public constant PAIR_DECIMALS = 1e18;
    error Insolvent(uint256 _borrow, uint256 _collateral, uint256 _exchangeRate);
    error BorrowerSolvent();
    error InsufficientDebtAvailable(uint256 _assets, uint256 _request);
    error SlippageTooHigh(uint256 _minOut, uint256 _actual);
    error BadSwapper();
    error InvalidReceiver();
    error InvalidLiquidator();
    error InvalidRedemptionHandler();
    error InvalidParameter();
    error InvalidPath(address _expected, address _actual);
    error InsufficientDebtToRedeem();
    error MinimumRedemption();
    error InsufficientBorrowAmount();
    error OnlyProtocolOrOwner();
}
ResupplyPairCore.sol 1252 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/**
 * @title ResupplyPairCore
 * @notice Based on code from Drake Evans and Frax Finance's lending pair core contract (https://github.com/FraxFinance/fraxlend), adapted for Resupply Finance
 */

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { ResupplyPairConstants } from "./ResupplyPairConstants.sol";
import { VaultAccount, VaultAccountingLibrary } from "../../libraries/VaultAccount.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IOracle } from "../../interfaces/IOracle.sol";
import { IRateCalculator } from "../../interfaces/IRateCalculator.sol";
import { ISwapper } from "../../interfaces/ISwapper.sol";
import { IResupplyRegistry } from "../../interfaces/IResupplyRegistry.sol";
import { ILiquidationHandler } from "../../interfaces/ILiquidationHandler.sol";
import { RewardDistributorMultiEpoch } from "../RewardDistributorMultiEpoch.sol";
import { WriteOffToken } from "../WriteOffToken.sol";
import { IERC4626 } from "../../interfaces/IERC4626.sol";
import { CoreOwnable } from "../../dependencies/CoreOwnable.sol";
import { IMintable } from "../../interfaces/IMintable.sol";


abstract contract ResupplyPairCore is CoreOwnable, ResupplyPairConstants, RewardDistributorMultiEpoch {
    using VaultAccountingLibrary for VaultAccount;
    using SafeERC20 for IERC20;
    using SafeCast for uint256;

    //forked fraxlend at version 3,0,0
    function version() external pure returns (uint256 _major, uint256 _minor, uint256 _patch) {
        _major = 3;
        _minor = 0;
        _patch = 2;
    }

    // ============================================================================================
    // Settings set by constructor()
    // ============================================================================================

    // Asset and collateral contracts
    address public immutable registry;
    IERC20 internal immutable debtToken;
    IERC20 public immutable collateral;
    IERC20 public immutable underlying;

    // LTV Settings
    /// @notice The maximum LTV allowed for this pair
    /// @dev 1e5 precision
    uint256 public maxLTV;

    //max borrow
    uint256 public borrowLimit;

    //Fees
    /// @notice The liquidation fee, given as a % of repayment amount
    /// @dev 1e5 precision
    uint256 public mintFee;
    uint256 public liquidationFee;
    /// @dev 1e18 precision
    uint256 public protocolRedemptionFee;
    uint256 public minimumRedemption = 100 * PAIR_DECIMALS; //minimum amount of debt to redeem
    uint256 public minimumLeftoverDebt = 10000 * PAIR_DECIMALS; //minimum amount of assets left over via redemptions
    uint256 public minimumBorrowAmount = 1000 * PAIR_DECIMALS; //minimum amount of assets to borrow
    

    // Interest Rate Calculator Contract
    IRateCalculator public rateCalculator; // For complex rate calculations

    // Swapper
    mapping(address => bool) public swappers; // approved swapper addresses

    // Metadata
    string public name;
    
    // ============================================================================================
    // Storage
    // ============================================================================================

    /// @notice Stores information about the current interest rate
    CurrentRateInfo public currentRateInfo;

    struct CurrentRateInfo {
        uint64 lastTimestamp;
        uint64 ratePerSec;
        uint128 lastShares;
    }

    /// @notice Stores information about the current exchange rate. Collateral:Asset ratio
    /// @dev Struct packed to save SLOADs. Amount of Collateral Token to buy 1e18 Asset Token
    ExchangeRateInfo public exchangeRateInfo;

    struct ExchangeRateInfo {
        address oracle;
        uint96 lastTimestamp;
        uint256 exchangeRate;
    }

    // Contract Level Accounting
    VaultAccount public totalBorrow; // amount = total borrow amount with interest accrued, shares = total shares outstanding
    uint256 public claimableFees; //amount of interest gained that is claimable as fees
    uint256 public claimableOtherFees; //amount of redemption/mint fees claimable by protocol
    WriteOffToken immutable public redemptionWriteOff; //token to keep track of redemption write offs

    // User Level Accounting
    /// @notice Stores the balance of collateral for each user
    mapping(address => uint256) internal _userCollateralBalance; // amount of collateral each user is backed
    /// @notice Stores the balance of borrow shares for each user
    mapping(address => uint256) internal _userBorrowShares; // represents the shares held by individuals

    

    // ============================================================================================
    // Constructor
    // ============================================================================================

    /// @notice The ```constructor``` function is called on deployment
    /// @param _core Core contract address
    /// @param _configData abi.encode(address _collateral, address _oracle, address _rateCalculator, uint256 _maxLTV, uint256 _borrowLimit, uint256 _liquidationFee, uint256 _mintFee, uint256 _protocolRedemptionFee)
    /// @param _immutables abi.encode(address _registry)
    /// @param _customConfigData abi.encode(address _name, address _govToken, address _underlyingStaking, uint256 _stakingId)
    constructor(
        address _core,
        bytes memory _configData,
        bytes memory _immutables,
        bytes memory _customConfigData
    ) CoreOwnable(_core){
        (address _registry) = abi.decode(
            _immutables,
            (address)
        );
        registry = _registry;
        debtToken = IERC20(IResupplyRegistry(registry).token());
        {
            (
                address _collateral,
                address _oracle,
                address _rateCalculator,
                uint256 _maxLTV,
                uint256 _initialBorrowLimit,
                uint256 _liquidationFee,
                uint256 _mintFee,
                uint256 _protocolRedemptionFee
            ) = abi.decode(
                    _configData,
                    (address, address, address, uint256, uint256, uint256, uint256, uint256)
                );

            // Pair Settings
            collateral = IERC20(_collateral);
            if(IERC20Metadata(_collateral).decimals() != 18){
                revert InvalidParameter();
            }
            underlying = IERC20(IERC4626(_collateral).asset());
            if(IERC20Metadata(address(underlying)).decimals() != 18){
                revert InvalidParameter();
            }
            // approve so this contract can deposit
            underlying.forceApprove(_collateral, type(uint256).max);
            currentRateInfo.lastShares = uint128(IERC4626(_collateral).convertToShares(PAIR_DECIMALS));
            exchangeRateInfo.oracle = _oracle;
            rateCalculator = IRateCalculator(_rateCalculator);
            borrowLimit = _initialBorrowLimit;

            //Liquidation Fee Settings
            liquidationFee = _liquidationFee;
            mintFee = _mintFee;
            protocolRedemptionFee = _protocolRedemptionFee;

            // set maxLTV
            maxLTV = _maxLTV;
        }

        //starting reward types
        redemptionWriteOff = new WriteOffToken(address(this));
        _insertRewardToken(address(redemptionWriteOff));//add redemption token as a reward
        //set the redemption token as non claimable via getReward
        rewards[0].is_non_claimable = true;

        {
            (string memory _name,,,) = abi.decode(
                _customConfigData,
                (string, address, address, uint256)
            );

            // Metadata
            name = _name;

            // Instantiate Interest
            _addInterest();
            // Instantiate Exchange Rate
            _updateExchangeRate();
        }
    }

    // ============================================================================================
    // Helpers
    // ============================================================================================


    //get total collateral, either parked here or staked 
    function totalCollateral() public view virtual returns(uint256 _totalCollateralBalance);

    function userBorrowShares(address _account) public view returns(uint256 borrowShares){
        borrowShares = _userBorrowShares[_account];

        uint256 globalEpoch = currentRewardEpoch;
        uint256 userEpoch = userRewardEpoch[_account];

        if(userEpoch < globalEpoch){
            //need to calculate shares while keeping this as a view function
            for(;;){
                //reduce shares by refactoring amount
                borrowShares /= SHARE_REFACTOR_PRECISION;
                unchecked {
                    userEpoch += 1;
                }
                if(userEpoch == globalEpoch){
                    break;
                }
            }
        }
    }

    //get _userCollateralBalance minus redemption tokens
    function userCollateralBalance(address _account) public nonReentrant returns(uint256 _collateralAmount){
        _syncUserRedemptions(_account);

        _collateralAmount = _userCollateralBalance[_account];

        //since there are some very small dust during distribution there could be a few wei
        //in user collateral that is over total collateral. clamp to total
        uint256 total = totalCollateral();
        _collateralAmount = _collateralAmount > total ? total : _collateralAmount;
    }

    /// @notice The ```totalDebtAvailable``` function returns the total balance of debt tokens in the contract
    /// @return The balance of debt tokens held by contract
    function totalDebtAvailable() external view returns (uint256) {
        (,,, VaultAccount memory _totalBorrow) = previewAddInterest();
        
        return _totalDebtAvailable(_totalBorrow);
    }

    /// @notice The ```_totalDebtAvailable``` function returns the total amount of debt that can be issued on this pair
    /// @param _totalBorrow Total borrowed amount, inclusive of interest
    /// @return The amount of debt that can be issued
    function _totalDebtAvailable(VaultAccount memory _totalBorrow) internal view returns (uint256) {
        uint256 _borrowLimit = borrowLimit;
        uint256 borrowable = _borrowLimit > _totalBorrow.amount ? _borrowLimit - _totalBorrow.amount : 0;

        return borrowable > type(uint128).max ? type(uint128).max : borrowable; 
    }

    function currentUtilization() external view returns (uint256) {
        uint256 _borrowLimit = borrowLimit;
        if(_borrowLimit == 0){
            return PAIR_DECIMALS;
        }
        (,,, VaultAccount memory _totalBorrow) = previewAddInterest();
        return _totalBorrow.amount * PAIR_DECIMALS / _borrowLimit;
    }

    /// @notice The ```_isSolvent``` function determines if a given borrower is solvent given an exchange rate
    /// @param _borrower The borrower address to check
    /// @param _exchangeRate The exchange rate, i.e. the amount of collateral to buy 1e18 asset
    /// @return Whether borrower is solvent
    function _isSolvent(address _borrower, uint256 _exchangeRate) internal view returns (bool) {
        uint256 _maxLTV = maxLTV;
        if (_maxLTV == 0) return true;
        //must look at borrow shares of current epoch so user helper function
        //user borrow shares should be synced before _isSolvent is called
        uint256 _borrowerAmount = totalBorrow.toAmount(_userBorrowShares[_borrower], true);
        if (_borrowerAmount == 0) return true;
        
        //anything that calls _isSolvent will call _syncUserRedemptions beforehand
        uint256 _collateralAmount = _userCollateralBalance[_borrower];
        if (_collateralAmount == 0) return false;

        uint256 _ltv = ((_borrowerAmount * _exchangeRate * LTV_PRECISION) / EXCHANGE_PRECISION) / _collateralAmount;
        return _ltv <= _maxLTV;
    }

    function _isSolventSync(address _borrower, uint256 _exchangeRate) internal returns (bool){
         //checkpoint rewards and sync _userCollateralBalance
        _syncUserRedemptions(_borrower);
        return _isSolvent(_borrower, _exchangeRate);
    }

    // ============================================================================================
    // Modifiers
    // ============================================================================================

    /// @notice Checks for solvency AFTER executing contract code
    /// @param _borrower The borrower whose solvency we will check
    modifier isSolvent(address _borrower) {
        //checkpoint rewards and sync _userCollateralBalance before doing other actions
        _syncUserRedemptions(_borrower);
        _;
        ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;

        if (!_isSolvent(_borrower, _exchangeRateInfo.exchangeRate)) {
            revert Insolvent(
                totalBorrow.toAmount(_userBorrowShares[_borrower], true),
                _userCollateralBalance[_borrower], //_issolvent sync'd so take base _userCollateral
                _exchangeRateInfo.exchangeRate
            );
        }
    }

    // ============================================================================================
    // Reward Implementation
    // ============================================================================================

    function _isRewardManager() internal view override returns(bool){
        return msg.sender == address(core) || msg.sender == IResupplyRegistry(registry).rewardHandler();
    }

    function _fetchIncentives() internal override{
        IResupplyRegistry(registry).claimRewards(address(this));
    }

    function _totalRewardShares() internal view override returns(uint256){
        return totalBorrow.shares;
    }

    function _userRewardShares(address _account) internal view override returns(uint256){
        return _userBorrowShares[_account];
    }

    function _increaseUserRewardEpoch(address _account, uint256 _currentUserEpoch) internal override{
        //convert shares to next epoch shares
        //share refactoring will never be 0
        _userBorrowShares[_account] = _userBorrowShares[_account] / SHARE_REFACTOR_PRECISION;
        //update user reward epoch
        userRewardEpoch[_account] = _currentUserEpoch + 1;
    }

    function earned(address _account) public override returns(EarnedData[] memory claimable){
        EarnedData[] memory earneddata = super.earned(_account);
        uint256 rewardCount = earneddata.length - 1;
        claimable = new EarnedData[](rewardCount);

        //remove index 0 as we dont need to report the write off tokens
        for (uint256 i = 1; i <= rewardCount; ) {
            claimable[i-1].amount = earneddata[i].amount;
            claimable[i-1].token = earneddata[i].token;
            unchecked{ i += 1; }
        }
    }

    function _checkAddToken(address _address) internal view virtual override returns(bool){
        if(_address == address(collateral)) return false;
        if(_address == address(debtToken)) return false;
        return true;
    }

    // ============================================================================================
    // Underlying Staking
    // ============================================================================================

    function _stakeUnderlying(uint256 _amount) internal virtual;

    function _unstakeUnderlying(uint256 _amount) internal virtual;

    // ============================================================================================
    // Functions: Interest Accumulation and Adjustment
    // ============================================================================================

    /// @notice The ```AddInterest``` event is emitted when interest is accrued by borrowers
    /// @param interestEarned The total interest accrued by all borrowers
    /// @param rate The interest rate used to calculate accrued interest
    event AddInterest(uint256 interestEarned, uint256 rate);

    /// @notice The ```UpdateRate``` event is emitted when the interest rate is updated
    /// @param oldRatePerSec The old interest rate (per second)
    /// @param oldShares previous used shares
    /// @param newRatePerSec The new interest rate (per second)
    /// @param newShares new shares
    event UpdateRate(
        uint256 oldRatePerSec,
        uint128 oldShares,
        uint256 newRatePerSec,
        uint128 newShares
    );

    /// @notice The ```addInterest``` function is a public implementation of _addInterest and allows 3rd parties to trigger interest accrual
    /// @param _returnAccounting Whether to return additional accounting data
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _currentRateInfo The new rate info struct
    /// @return _claimableFees The new total of fees that are claimable
    /// @return _totalBorrow The new total borrow struct
    function addInterest(
        bool _returnAccounting
    )
        external
        nonReentrant
        returns (
            uint256 _interestEarned,
            CurrentRateInfo memory _currentRateInfo,
            uint256 _claimableFees,
            VaultAccount memory _totalBorrow
        )
    {
        (, _interestEarned, _currentRateInfo) = _addInterest();
        if (_returnAccounting) {
            _claimableFees = claimableFees;
            _totalBorrow = totalBorrow;
        }
    }

    /// @notice The ```previewAddInterest``` function
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _newCurrentRateInfo The new rate info struct
    /// @return _claimableFees The new total of fees that are claimable
    /// @return _totalBorrow The new total borrow struct
    function previewAddInterest()
        public
        view
        returns (
            uint256 _interestEarned,
            CurrentRateInfo memory _newCurrentRateInfo,
            uint256 _claimableFees,
            VaultAccount memory _totalBorrow
        )
    {
        _newCurrentRateInfo = currentRateInfo;

        // Write return values
        InterestCalculationResults memory _results = _calculateInterest(_newCurrentRateInfo);

        if (_results.isInterestUpdated) {
            _interestEarned = _results.interestEarned;

            _newCurrentRateInfo.ratePerSec = _results.newRate;
            _newCurrentRateInfo.lastShares = _results.newShares;

            _claimableFees = claimableFees + uint128(_interestEarned);
            _totalBorrow = _results.totalBorrow;
        } else {
            _claimableFees = claimableFees;
            _totalBorrow = totalBorrow;
        }
    }

    struct InterestCalculationResults {
        bool isInterestUpdated;
        uint64 newRate;
        uint128 newShares;
        uint256 interestEarned;
        VaultAccount totalBorrow;
    }

    /// @notice The ```_calculateInterest``` function calculates the interest to be accrued and the new interest rate info
    /// @param _currentRateInfo The current rate info
    /// @return _results The results of the interest calculation
    function _calculateInterest(
        CurrentRateInfo memory _currentRateInfo
    ) internal view returns (InterestCalculationResults memory _results) {
        // Short circuit if interest already calculated this block
        if (_currentRateInfo.lastTimestamp < block.timestamp) {
            // Indicate that interest is updated and calculated
            _results.isInterestUpdated = true;

            // Write return values and use these to save gas
            _results.totalBorrow = totalBorrow;

            // Time elapsed since last interest update
            uint256 _deltaTime = block.timestamp - _currentRateInfo.lastTimestamp;

            // Request new interest rate and full utilization rate from the rate calculator
            (_results.newRate, _results.newShares) = IRateCalculator(rateCalculator).getNewRate(
                address(collateral),
                _deltaTime,
                _currentRateInfo.lastShares
            );

            // Calculate interest accrued
            _results.interestEarned = (_deltaTime * _results.totalBorrow.amount * _results.newRate) / RATE_PRECISION;

            // Accrue interest (if any) and fees if no overflow
            if (
                _results.interestEarned > 0 &&
                _results.interestEarned + _results.totalBorrow.amount <= type(uint128).max
            ) {
                // Increment totalBorrow by interestEarned
                _results.totalBorrow.amount += uint128(_results.interestEarned);
            }else{
                //reset interest earned
                _results.interestEarned = 0;
            }
        }
    }

    /// @notice The ```_addInterest``` function is invoked prior to every external function and is used to accrue interest and update interest rate
    /// @dev Can only called once per block
    /// @return _isInterestUpdated True if interest was calculated
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _currentRateInfo The new rate info struct
    function _addInterest()
        internal
        returns (
            bool _isInterestUpdated,
            uint256 _interestEarned,
            CurrentRateInfo memory _currentRateInfo
        )
    {
        // Pull from storage and set default return values
        _currentRateInfo = currentRateInfo;

        // Calc interest
        InterestCalculationResults memory _results = _calculateInterest(_currentRateInfo);

        // Write return values only if interest was updated and calculated
        if (_results.isInterestUpdated) {
            _isInterestUpdated = _results.isInterestUpdated;
            _interestEarned = _results.interestEarned;

            // emit here so that we have access to the old values
            emit UpdateRate(
                _currentRateInfo.ratePerSec,
                _currentRateInfo.lastShares,
                _results.newRate,
                _results.newShares
            );
            emit AddInterest(_interestEarned, _results.newRate);

            // overwrite original values
            _currentRateInfo.ratePerSec = _results.newRate;
            _currentRateInfo.lastShares = _results.newShares;
            _currentRateInfo.lastTimestamp = uint64(block.timestamp);

            // Effects: write to state
            currentRateInfo = _currentRateInfo;
            claimableFees += _interestEarned; //increase claimable fees by interest earned
            totalBorrow = _results.totalBorrow;
        }
    }

    // ============================================================================================
    // Functions: ExchangeRate
    // ============================================================================================

    /// @notice The ```UpdateExchangeRate``` event is emitted when the Collateral:Asset exchange rate is updated
    /// @param exchangeRate The exchange rate
    event UpdateExchangeRate(uint256 exchangeRate);

    /// @notice The ```updateExchangeRate``` function is the external implementation of _updateExchangeRate.
    /// @dev This function is invoked at most once per block as these queries can be expensive
    /// @return _exchangeRate The exchange rate
    function updateExchangeRate()
        external
        nonReentrant
        returns (uint256 _exchangeRate)
    {
        return _updateExchangeRate();
    }

    /// @notice The ```_updateExchangeRate``` function retrieves the latest exchange rate. i.e how much collateral to buy 1e18 asset.
    /// @dev This function is invoked at most once per block as these queries can be expensive
    /// @return _exchangeRate The exchange rate
    function _updateExchangeRate()
        internal
        returns (uint256 _exchangeRate)
    {
        // Pull from storage to save gas and set default return values
        ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;

        // Get the latest exchange rate from the oracle
        //convert price of collateral as debt is priced in terms of collateral amount (inverse)
        _exchangeRate = 1e36 / IOracle(_exchangeRateInfo.oracle).getPrices(address(collateral));
        
        //skip storage writes if value doesnt change
        if (_exchangeRate != _exchangeRateInfo.exchangeRate) {

            // Effects: Bookkeeping and write to storage
            _exchangeRateInfo.lastTimestamp = uint96(block.timestamp);
            _exchangeRateInfo.exchangeRate = _exchangeRate;
            exchangeRateInfo = _exchangeRateInfo;
            emit UpdateExchangeRate(_exchangeRate);
        }
    }

    // ============================================================================================
    // Functions: Lending
    // ============================================================================================

    // ONLY Protocol can lend

    // ============================================================================================
    // Functions: Borrowing
    // ============================================================================================

    //sync user collateral by removing account of userCollateralBalance based on
    //how many "claimable" redemption tokens are available to the user
    //should be called before anything with userCollateralBalance is used
    function _syncUserRedemptions(address _account) internal{
        //sync rewards first
        _checkpoint(_account);

        //get token count (divide by LTV_PRECISION as precision is padded)
        uint256 rTokens = claimable_reward[address(redemptionWriteOff)][_account] / LTV_PRECISION;
        //reset claimables
        claimable_reward[address(redemptionWriteOff)][_account] = 0;

        //remove from collateral balance the number of rtokens the user has
        uint256 currentUserBalance = _userCollateralBalance[_account];
        _userCollateralBalance[_account] = currentUserBalance >= rTokens ? currentUserBalance - rTokens : 0;
    }

    /// @notice The ```Borrow``` event is emitted when a borrower increases their position
    /// @param _borrower The borrower whose account was debited
    /// @param _receiver The address to which the Asset Tokens were transferred
    /// @param _borrowAmount The amount of Asset Tokens transferred
    /// @param _sharesAdded The number of Borrow Shares the borrower was debited
    /// @param _mintFees The amount of mint fees incurred
    event Borrow(
        address indexed _borrower,
        address indexed _receiver,
        uint256 _borrowAmount,
        uint256 _sharesAdded,
        uint256 _mintFees
    );

    /// @notice The ```_borrow``` function is the internal implementation for borrowing assets
    /// @param _borrowAmount The amount of the Asset Token to borrow
    /// @param _receiver The address to receive the Asset Tokens
    /// @return _sharesAdded The amount of borrow shares the msg.sender will be debited
    function _borrow(uint128 _borrowAmount, address _receiver) internal returns (uint256 _sharesAdded) {
        // Get borrow accounting from storage to save gas
        VaultAccount memory _totalBorrow = totalBorrow;

        if(_borrowAmount < minimumBorrowAmount){
            revert InsufficientBorrowAmount();
        }

        //mint fees
        uint256 debtForMint = (_borrowAmount * (LIQ_PRECISION + mintFee) / LIQ_PRECISION);

        // Check available capital
        uint256 _assetsAvailable = _totalDebtAvailable(_totalBorrow);
        if (_assetsAvailable < debtForMint) {
            revert InsufficientDebtAvailable(_assetsAvailable, debtForMint);
        }
        
        // Calculate the number of shares to add based on the amount to borrow
        _sharesAdded = _totalBorrow.toShares(debtForMint, true);

        //combine current shares and new shares
        uint256 newTotalShares = _totalBorrow.shares + _sharesAdded;

        // Effects: Bookkeeping to add shares & amounts to total Borrow accounting
        _totalBorrow.amount += debtForMint.toUint128();
        _totalBorrow.shares = newTotalShares.toUint128();

        // Effects: write back to storage
        totalBorrow = _totalBorrow;
        _userBorrowShares[msg.sender] += _sharesAdded;

        uint256 otherFees = debtForMint > _borrowAmount ? debtForMint - _borrowAmount : 0;
        if (otherFees > 0) claimableOtherFees += otherFees;

        // Interactions
        IResupplyRegistry(registry).mint(_receiver, _borrowAmount);
        
        emit Borrow(msg.sender, _receiver, _borrowAmount, _sharesAdded, otherFees);
    }

    /// @notice The ```borrow``` function allows a user to open/increase a borrow position
    /// @dev Borrower must call ```ERC20.approve``` on the Collateral Token contract if applicable
    /// @param _borrowAmount The amount to borrow
    /// @param _underlyingAmount The amount of underlying tokens to transfer to Pair
    /// @param _receiver The address which will receive the Asset Tokens
    /// @return _shares The number of borrow Shares the msg.sender will be debited
    function borrow(
        uint256 _borrowAmount,
        uint256 _underlyingAmount,
        address _receiver
    ) external nonReentrant isSolvent(msg.sender) returns (uint256 _shares) {
        if (_receiver == address(0)) revert InvalidReceiver();

        // Accrue interest if necessary
        _addInterest();

        // Update _exchangeRate
        _updateExchangeRate();

        // Only add collateral if necessary
        if (_underlyingAmount > 0) {
            //pull underlying and deposit in vault
            underlying.safeTransferFrom(msg.sender, address(this), _underlyingAmount);
            uint256 collateralShares = IERC4626(address(collateral)).deposit(_underlyingAmount, address(this));
            //add collateral to msg.sender
            _addCollateral(address(this), collateralShares, msg.sender);
        }

        // Effects: Call internal borrow function
        _shares = _borrow(_borrowAmount.toUint128(), _receiver);
    }

    /// @notice The ```AddCollateral``` event is emitted when a borrower adds collateral to their position
    /// @param borrower The borrower account for which the collateral should be credited
    /// @param collateralAmount The amount of Collateral Token to be transferred
    event AddCollateral(address indexed borrower, uint256 collateralAmount);

    /// @notice The ```_addCollateral``` function is an internal implementation for adding collateral to a borrowers position
    /// @param _sender The source of funds for the new collateral
    /// @param _collateralAmount The amount of Collateral Token to be transferred
    /// @param _borrower The borrower account for which the collateral should be credited
    function _addCollateral(address _sender, uint256 _collateralAmount, address _borrower) internal {
        _userCollateralBalance[_borrower] += _collateralAmount;
        if (_sender != address(this)) {
            collateral.safeTransferFrom(_sender, address(this), _collateralAmount);
        }
        //stake underlying
        _stakeUnderlying(_collateralAmount);

        emit AddCollateral(_borrower, _collateralAmount);
    }

    /// @notice The ```addCollateral``` function allows the caller to add Collateral Token to a borrowers position
    /// @dev msg.sender must call ERC20.approve() on the Collateral Token contract prior to invocation
    /// @param _collateralAmount The amount of Collateral Token to be added to borrower's position
    /// @param _borrower The account to be credited
    function addCollateralVault(uint256 _collateralAmount, address _borrower) external nonReentrant {
        if (_borrower == address(0)) revert InvalidReceiver();

        _addInterest();
        _addCollateral(msg.sender, _collateralAmount, _borrower);
    }

    /// @notice Allows depositing in terms of underlying asset, and have it converted to collateral shares to the borrower's position.
    /// @param _amount The amount of the underlying asset to deposit.
    /// @param _borrower The address of the borrower whose collateral balance will be credited.
    function addCollateral(uint256 _amount, address _borrower) external nonReentrant {
        if (_borrower == address(0)) revert InvalidReceiver();

        _addInterest();

        underlying.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 collateralShares = IERC4626(address(collateral)).deposit(_amount, address(this));
        _addCollateral(address(this), collateralShares, _borrower);
    }

    /// @notice The ```RemoveCollateral``` event is emitted when collateral is removed from a borrower's position
    /// @param _collateralAmount The amount of Collateral Token to be transferred
    /// @param _receiver The address to which Collateral Tokens will be transferred
    /// @param _borrower The address of the account in which collateral is being removed
    event RemoveCollateral(
        uint256 _collateralAmount,
        address indexed _receiver,
        address indexed _borrower
    );

    /// @notice The ```_removeCollateral``` function is the internal implementation for removing collateral from a borrower's position
    /// @param _collateralAmount The amount of Collateral Token to remove from the borrower's position
    /// @param _receiver The address to receive the Collateral Token transferred
    /// @param _borrower The borrower whose account will be debited the Collateral amount
    function _removeCollateral(uint256 _collateralAmount, address _receiver, address _borrower) internal {

        // Effects: write to state
        // NOTE: Following line will revert on underflow if _collateralAmount > userCollateralBalance
        _userCollateralBalance[_borrower] -= _collateralAmount;

        //unstake underlying
        //NOTE: following will revert on underflow if total collateral < _collateralAmount
        _unstakeUnderlying(_collateralAmount);

        // Interactions
        if (_receiver != address(this)) {
            collateral.safeTransfer(_receiver, _collateralAmount);
        }
        emit RemoveCollateral(_collateralAmount, _receiver, _borrower);
    }

    /// @notice The ```removeCollateralVault``` function is used to remove collateral from msg.sender's borrow position
    /// @dev msg.sender must be solvent after invocation or transaction will revert
    /// @param _collateralAmount The amount of Collateral Token to transfer
    /// @param _receiver The address to receive the transferred funds
    function removeCollateralVault(
        uint256 _collateralAmount,
        address _receiver
    ) external nonReentrant isSolvent(msg.sender) {
        //note: isSolvent checkpoints msg.sender via _syncUserRedemptions

        if (_receiver == address(0)) revert InvalidReceiver();

        _addInterest();
        // Note: exchange rate is irrelevant when borrower has no debt shares
        if (_userBorrowShares[msg.sender] > 0) {
            _updateExchangeRate();
        }
        _removeCollateral(_collateralAmount, _receiver, msg.sender);
    }

    /// @notice The ```removeCollateral``` function is used to remove collateral from msg.sender's borrow position and redeem it for underlying tokens
    /// @dev msg.sender must be solvent after invocation or transaction will revert
    /// @param _collateralAmount The amount of Collateral Token to redeem
    /// @param _receiver The address to receive the redeemed underlying tokens
    function removeCollateral(
        uint256 _collateralAmount,
        address _receiver
    ) external nonReentrant isSolvent(msg.sender) {
        //note: isSolvent checkpoints msg.sender via _syncUserRedemptions

        if (_receiver == address(0)) revert InvalidReceiver();

        _addInterest();
        // Note: exchange rate is irrelevant when borrower has no debt shares
        if (_userBorrowShares[msg.sender] > 0) {
            _updateExchangeRate();
        }
        _removeCollateral(_collateralAmount, address(this), msg.sender);
        IERC4626(address(collateral)).redeem(_collateralAmount, _receiver, address(this));
    }

    /// @notice The ```Repay``` event is emitted whenever a debt position is repaid
    /// @param payer The address paying for the repayment
    /// @param borrower The borrower whose account will be credited
    /// @param amountToRepay The amount of Asset token to be transferred
    /// @param shares The amount of Borrow Shares which will be debited from the borrower after repayment
    event Repay(address indexed payer, address indexed borrower, uint256 amountToRepay, uint256 shares);

    /// @notice The ```_repay``` function is the internal implementation for repaying a borrow position
    /// @dev The payer must have called ERC20.approve() on the Asset Token contract prior to invocation
    /// @param _totalBorrow An in memory copy of the totalBorrow VaultAccount struct
    /// @param _amountToRepay The amount of Asset Token to transfer
    /// @param _shares The number of Borrow Shares the sender is repaying
    /// @param _payer The address from which funds will be transferred
    /// @param _borrower The borrower account which will be credited
    function _repay(
        VaultAccount memory _totalBorrow,
        uint128 _amountToRepay,
        uint128 _shares,
        address _payer,
        address _borrower
    ) internal {
        //checkpoint rewards for borrower before adjusting borrow shares
        _checkpoint(_borrower);

        // Effects: Bookkeeping
        _totalBorrow.amount -= _amountToRepay;
        _totalBorrow.shares -= _shares;

        // Effects: write user state
        uint256 usershares = _userBorrowShares[_borrower] - _shares;
        _userBorrowShares[_borrower] = usershares;
    
        //check that any remaining user amount is greater than minimumBorrowAmount
        if(usershares > 0 && _totalBorrow.toAmount(usershares, true) < minimumBorrowAmount){
            revert InsufficientBorrowAmount();
        }

        // Effects: write global state
        totalBorrow = _totalBorrow;

        // Interactions
        // burn from non-zero address.  zero address is only supplied during liquidations
        // for liqudations the handler will do the burning
        if (_payer != address(0)) {
            IMintable(address(debtToken)).burn(_payer, _amountToRepay);
        }
        emit Repay(_payer, _borrower, _amountToRepay, _shares);
    }

    /// @notice The ```repay``` function allows the caller to pay down the debt for a given borrower.
    /// @dev Caller must first invoke ```ERC20.approve()``` for the Asset Token contract
    /// @param _shares The number of Borrow Shares which will be repaid by the call
    /// @param _borrower The account for which the debt will be reduced
    /// @return _amountToRepay The amount of Asset Tokens which were burned to repay the Borrow Shares
    function repay(uint256 _shares, address _borrower) external nonReentrant returns (uint256 _amountToRepay) {
        if (_borrower == address(0)) revert InvalidReceiver();

        // Accrue interest if necessary
        _addInterest();

        // Calculate amount to repay based on shares
        VaultAccount memory _totalBorrow = totalBorrow;
        _amountToRepay = _totalBorrow.toAmount(_shares, true);

        // Execute repayment effects
        _repay(_totalBorrow, _amountToRepay.toUint128(), _shares.toUint128(), msg.sender, _borrower);
    }

    // ============================================================================================
    // Functions: Redemptions
    // ============================================================================================
    event Redeemed(
        address indexed _caller,
        uint256 _amount,
        uint256 _collateralFreed,
        uint256 _protocolFee,
        uint256 _debtReduction
    );

    /// @notice Allows redemption of the debt tokens for collateral
    /// @dev Only callable by the registry's redeemer contract
    /// @param _caller The address of the caller
    /// @param _amount The amount of debt tokens to redeem
    /// @param _totalFeePct Total fee to charge, expressed as a percentage of the stablecoin input; to be subdivided between protocol and borrowers.
    /// @param _receiver The address to receive the collateral tokens
    /// @return _collateralToken The address of the collateral token
    /// @return _collateralFreed The amount of collateral tokens returned to receiver
    function redeemCollateral(
        address _caller,
        uint256 _amount,
        uint256 _totalFeePct,
        address _receiver
    ) external nonReentrant returns(address _collateralToken, uint256 _collateralFreed){
        //check sender. must go through the registry's redemptionHandler
        if(msg.sender != IResupplyRegistry(registry).redemptionHandler()) revert InvalidRedemptionHandler();

        if (_receiver == address(0) || _receiver == address(this)) revert InvalidReceiver();

        if(_amount < minimumRedemption){
          revert MinimumRedemption();
        }

        // accrue interest if necessary
        _addInterest();

        //redemption fees
        //assuming 1% redemption fee(0.5% to protocol, 0.5% to borrowers) and a redemption of $100
        // reduce totalBorrow.amount by 99.5$
        // add 0.5$ to protocol earned fees
        // return 99$ of collateral
        // burn $100 of stables
        uint256 valueToRedeem = _amount * (EXCHANGE_PRECISION - _totalFeePct) / EXCHANGE_PRECISION;
        uint256 protocolFee = (_amount - valueToRedeem) * protocolRedemptionFee / EXCHANGE_PRECISION;
        uint256 debtReduction = _amount - protocolFee; // protocol fee portion is not burned

        //check if theres enough debt to write off
        VaultAccount memory _totalBorrow = totalBorrow;
        if(debtReduction > _totalBorrow.amount || _totalBorrow.amount - debtReduction < minimumLeftoverDebt ){
            revert InsufficientDebtToRedeem(); // size of request exceeeds total pair debt
        }

        _totalBorrow.amount -= uint128(debtReduction);

        //if after many redemptions the amount to shares ratio has deteriorated too far, then refactor
        //cast to uint256 to reduce chance of overflow
        if(uint256(_totalBorrow.amount) * SHARE_REFACTOR_PRECISION < _totalBorrow.shares){
            _increaseRewardEpoch(); //will do final checkpoint on previous total supply
            _totalBorrow.shares /= uint128(SHARE_REFACTOR_PRECISION);
        }

        // Effects: write to state
        totalBorrow = _totalBorrow;

        claimableOtherFees += protocolFee; //increase claimable fees

        // Update exchange rate
        uint256 _exchangeRate = _updateExchangeRate();
        //calc collateral units
        _collateralFreed = ((valueToRedeem * _exchangeRate) / EXCHANGE_PRECISION);
        
        _unstakeUnderlying(_collateralFreed);
        _collateralToken = address(collateral);
        IERC20(_collateralToken).safeTransfer(_receiver, _collateralFreed);

        //distribute write off tokens to adjust userCollateralbalances
        //padded with LTV_PRECISION for extra precision
        redemptionWriteOff.mint(_collateralFreed * LTV_PRECISION);

        emit Redeemed(_caller, _amount, _collateralFreed, protocolFee, debtReduction);
    }

    // ============================================================================================
    // Functions: Liquidations
    // ============================================================================================
    /// @notice The ```Liquidate``` event is emitted when a liquidation occurs
    /// @param _borrower The borrower account for which the liquidation occurred
    /// @param _collateralForLiquidator The amount of collateral token transferred to the liquidator
    /// @param _sharesLiquidated The number of borrow shares liquidated
    /// @param _amountLiquidatorToRepay The amount of asset tokens to be repaid by the liquidator
    event Liquidate(
        address indexed _borrower,
        uint256 _collateralForLiquidator,
        uint256 _sharesLiquidated,
        uint256 _amountLiquidatorToRepay
    );

    /// @notice The ```liquidate``` function allows a third party to repay a borrower's debt if they have become insolvent
    /// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling ```Liquidate()```
    /// @param _borrower The account for which the repayment is credited and from whom collateral will be taken
    /// @return _collateralForLiquidator The amount of Collateral Token transferred to the liquidator
    function liquidate(
        address _borrower
    ) external nonReentrant returns (uint256 _collateralForLiquidator) {
        address liquidationHandler = IResupplyRegistry(registry).liquidationHandler();
        if(msg.sender != liquidationHandler) revert InvalidLiquidator();

        if (_borrower == address(0)) revert InvalidReceiver();

        // accrue interest if necessary
        _addInterest();

        // Update exchange rate and use the lower rate for liquidations
        uint256 _exchangeRate = _updateExchangeRate();

        // Check if borrower is solvent, revert if they are
        //_isSolventSync calls _syncUserRedemptions which checkpoints rewards and userCollateral
        if (_isSolventSync(_borrower, _exchangeRate)) {
            revert BorrowerSolvent();
        }

        // Read from state
        VaultAccount memory _totalBorrow = totalBorrow;
        uint256 _collateralBalance = _userCollateralBalance[_borrower];
        uint128 _borrowerShares = _userBorrowShares[_borrower].toUint128();

        // Checks & Calculations
        // Determine the liquidation amount in collateral units (i.e. how much debt liquidator is going to repay)
        uint256 _liquidationAmountInCollateralUnits = ((_totalBorrow.toAmount(_borrowerShares, false) *
            _exchangeRate) / EXCHANGE_PRECISION);

        // add fee for liquidation
        _collateralForLiquidator = (_liquidationAmountInCollateralUnits *
            (LIQ_PRECISION + liquidationFee)) / LIQ_PRECISION;

        // clamp to user collateral balance as we cant take more than that
        _collateralForLiquidator = _collateralForLiquidator > _collateralBalance ? _collateralBalance : _collateralForLiquidator;

        // Calculated here for use during repayment, grouped with other calcs before effects start
        uint128 _amountLiquidatorToRepay = (_totalBorrow.toAmount(_borrowerShares, true)).toUint128();

        emit Liquidate(
                _borrower,
                _collateralForLiquidator,
                _borrowerShares,
                _amountLiquidatorToRepay
            );

        // Effects & Interactions
        // repay using address(0) to skip burning (liquidationHandler will burn from insurance pool)
        _repay(
            _totalBorrow,
            _amountLiquidatorToRepay,
            _borrowerShares,
            address(0),
            _borrower
        );

        // Collateral is removed on behalf of borrower and sent to liquidationHandler
        // NOTE: isSolvent above checkpoints user with _syncUserRedemptions before removing collateral
        _removeCollateral(_collateralForLiquidator, liquidationHandler, _borrower);

        //call liquidation handler to distribute and burn debt
        ILiquidationHandler(liquidationHandler).processLiquidationDebt(address(collateral), _collateralForLiquidator, _amountLiquidatorToRepay);
    }

    // ============================================================================================
    // Functions: Leverage
    // ============================================================================================

    /// @notice The ```LeveragedPosition``` event is emitted when a borrower takes out a new leveraged position
    /// @param _borrower The account for which the debt is debited
    /// @param _swapperAddress The address of the swapper which conforms the FraxSwap interface
    /// @param _borrowAmount The amount of Asset Token to be borrowed to be borrowed
    /// @param _borrowShares The number of Borrow Shares the borrower is credited
    /// @param _initialUnderlyingAmount The amount of initial underlying Tokens supplied by the borrower
    /// @param _amountCollateralOut The amount of Collateral Token which was received for the Asset Tokens
    event LeveragedPosition(
        address indexed _borrower,
        address _swapperAddress,
        uint256 _borrowAmount,
        uint256 _borrowShares,
        uint256 _initialUnderlyingAmount,
        uint256 _amountCollateralOut
    );

    /// @notice The ```leveragedPosition``` function allows a user to enter a leveraged borrow position with minimal upfront Underlying tokens
    /// @dev Caller must invoke ```ERC20.approve()``` on the Underlying Token contract prior to calling function
    /// @param _swapperAddress The address of the whitelisted swapper to use to swap borrowed Asset Tokens for Collateral Tokens
    /// @param _borrowAmount The amount of Asset Tokens borrowed
    /// @param _initialUnderlyingAmount The initial amount of underlying Tokens supplied by the borrower
    /// @param _amountCollateralOutMin The minimum amount of Collateral Tokens to be received in exchange for the borrowed Asset Tokens
    /// @param _path An array containing the addresses of ERC20 tokens to swap.  Adheres to UniV2 style path params.
    /// @return _totalCollateralBalance The total amount of Collateral Tokens added to a users account (initial + swap)
    function leveragedPosition(
        address _swapperAddress,
        uint256 _borr...

// [truncated — 57379 bytes total]
SafeERC20.sol 212 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

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

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
VaultAccount.sol 39 lines
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;

struct VaultAccount {
    uint128 amount; // Total amount, analogous to market cap
    uint128 shares; // Total shares, analogous to shares outstanding
}

/// @title VaultAccount Library
/// @author Drake Evans (Frax Finance) github.com/drakeevans, modified from work by @Boring_Crypto github.com/boring_crypto
/// @notice Provides a library for use with the VaultAccount struct, provides convenient math implementations
/// @dev Uses uint128 to save on storage
library VaultAccountingLibrary {
    /// @notice Calculates the shares value in relationship to `amount` and `total`
    /// @dev Given an amount, return the appropriate number of shares
    function toShares(VaultAccount memory total, uint256 amount, bool roundUp) internal pure returns (uint256 shares) {
        if (total.amount == 0) {
            shares = amount;
        } else {
            shares = (amount * total.shares) / total.amount;
            if (roundUp && (shares * total.amount) / total.shares < amount) {
                shares = shares + 1;
            }
        }
    }

    /// @notice Calculates the amount value in relationship to `shares` and `total`
    /// @dev Given a number of shares, returns the appropriate amount
    function toAmount(VaultAccount memory total, uint256 shares, bool roundUp) internal pure returns (uint256 amount) {
        if (total.shares == 0) {
            amount = shares;
        } else {
            amount = (shares * total.amount) / total.shares;
            if (roundUp && total.amount > 0 && (amount * total.shares) / total.amount < shares) {
                amount = amount + 1;
            }
        }
    }
}
IRateCalculator.sol 14 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IRateCalculator {
    function name() external view returns (string memory);

    function version() external view returns (uint256, uint256, uint256);

    function getNewRate(
        address _vault,
        uint256 _deltaTime,
        uint256 _previousShares
    ) external view returns (uint64 _newRatePerSec, uint128 _newShares);
}
ISwapper.sol 13 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface ISwapper {
    function swap(
        address account,
        uint256 amountIn,
        address[] calldata path,
        address to
    ) external;

    function swapPools(address tokenIn, address tokenOut) external view returns(address swappool, int32 tokenInIndex, int32 tokenOutIndex, uint32 swaptype);
}
IFeeDeposit.sol 11 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IFeeDeposit {
    function operator() external view returns(address);
    function lastDistributedEpoch() external view returns(uint256);
    function setOperator(address _newAddress) external;
    function distributeFees() external;
    function incrementPairRevenue(uint256 _fees, uint256 _otherFees) external;
    function getEpoch() external view returns(uint256);
}
IResupplyRegistry.sol 73 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IResupplyRegistry {
    event AddPair(address pairAddress);
    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event SetDeployer(address deployer, bool _bool);
    event DefaultSwappersSet(address[] addresses);
    event EntryUpdated(string indexed key, address indexed addr);
    event WithdrawTo(address indexed user, uint256 amount);

    // Protected keys
    function LIQUIDATION_HANDLER() external pure returns (string memory);
    function FEE_DEPOSIT() external pure returns (string memory);
    function REDEMPTION_HANDLER() external pure returns (string memory);
    function INSURANCE_POOL() external pure returns (string memory);
    function REWARD_HANDLER() external pure returns (string memory);
    function TREASURY() external pure returns (string memory);
    function STAKER() external pure returns (string memory);
    function L2_MANAGER() external pure returns (string memory);
    function VEST_MANAGER() external pure returns (string memory);

    // Other public functions
    function token() external view returns (address);
    function govToken() external view returns (address);
    function getAddress(string memory key) external view returns (address);
    function getAllKeys() external view returns (string[] memory);
    function getAllAddresses() external view returns (address[] memory);
    function getProtectedKeys() external pure returns (string[] memory);
    function keyExists(string memory) external view returns (bool);
    function hashToKey(bytes32) external view returns (string memory);
    function setAddress(string memory key, address addr) external;
    function acceptOwnership() external;
    function addPair(address _pairAddress) external;
    function registeredPairs(uint256) external view returns (address);
    function pairsByName(string memory) external view returns (address);
    function registeredPairsLength() external view returns (uint256);
    function getAllPairAddresses() external view returns (address[] memory _deployedPairsArray);
    function defaultSwappers(uint256 _index) external view returns (address);
    function owner() external view returns (address);
    function pendingOwner() external view returns (address);
    function renounceOwnership() external;
    function transferOwnership(address newOwner) external;
    function claimFees(address _pair) external;
    function claimRewards(address _pair) external;
    function claimInsuranceRewards() external;
    function withdrawTo(address _asset, uint256 _amount, address _to) external;
    function mint(address receiver, uint256 amount) external;
    function burn(address target, uint256 amount) external;
    function liquidationHandler() external view returns(address);
    function feeDeposit() external view returns(address);
    function redemptionHandler() external view returns(address);
    function rewardHandler() external view returns(address);
    function insurancePool() external view returns(address);
    function setRewardClaimer(address _newAddress) external;
    function setRedemptionHandler(address _newAddress) external;
    function setFeeDeposit(address _newAddress) external;
    function setLiquidationHandler(address _newAddress) external;
    function setInsurancePool(address _newAddress) external;
    function setStaker(address _newAddress) external;
    function setTreasury(address _newAddress) external;
    function staker() external view returns(address);
    function treasury() external view returns(address);
    function l2manager() external view returns(address);
    function setRewardHandler(address _newAddress) external;
    function setVestManager(address _newAddress) external;
    function setDefaultSwappers(address[] memory _swappers) external;
    function collateralId(address _collateral) external view returns(uint256);

    error NameMustBeUnique();
    error ProtectedKey(string key);
}
IConvexStaking.sol 25 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IConvexStaking {
    function poolInfo(uint256 _pid) external view returns(
        address lptoken,
        address token,
        address gauge,
        address crvRewards,
        address stash,
        bool shutdown
    );
    function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns(bool);
    function depositAll(uint256 _pid, bool _stake) external returns(bool);
    function withdrawAndUnwrap(uint256 amount, bool claim) external returns(bool);
    function withdrawAllAndUnwrap(bool claim) external;
    function getReward() external returns(bool);
    function getReward(address _account, bool _claimExtras) external returns(bool);
    function totalSupply() external view returns (uint256);
    function extraRewardsLength() external view returns (uint256);
    function extraRewards(uint256 _rid) external view returns (address _rewardContract);
    function rewardToken() external view returns (address _rewardToken);
    function token() external view returns (address _token);
    function balanceOf(address account) external view returns (uint256);
}
EpochTracker.sol 24 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import "../interfaces/ICore.sol";

/**
    @title EpochTracker
    @dev Provides a unified `startTime` and `getEpoch`, used for tracking epochs.
 */
contract EpochTracker {
    uint256 public immutable startTime;
    
    /// @notice Length of an epoch, in seconds
    uint256 public immutable epochLength;

    constructor(address _core) {
        startTime = ICore(_core).startTime();
        epochLength = ICore(_core).epochLength();
    }

    function getEpoch() public view returns (uint256 epoch) {
        return (block.timestamp - startTime) / epochLength;
    }
}
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
draft-IERC6093.sol 161 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC-20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC-721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC-1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
IOracle.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IOracle {
    function decimals() external view returns (uint8);

    function getPrices(address _vault) external view returns (uint256 _price);

    function name() external view returns (string memory);
}
ILiquidationHandler.sol 13 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface ILiquidationHandler {
    function operator() external view returns(address);
    function setOperator(address _newAddress) external;
    function processLiquidationDebt(address _collateral, uint256 _collateralAmount, uint256 _debtAmount) external;
    function processCollateral(address _collateral) external;
    function liquidate(
        address _pair,
        address _borrower
    ) external returns (uint256 _collateralForLiquidator);
}
RewardDistributorMultiEpoch.sol 321 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";


//abstract reward handling to attach to another contract
//supports an epoch system for supply changes
abstract contract RewardDistributorMultiEpoch is ReentrancyGuard{
    using SafeERC20 for IERC20;

    struct EarnedData {
        address token;
        uint256 amount;
    }

    struct RewardType {
        address reward_token;
        bool is_non_claimable; //a bit unothrodox setting but need to block claims on our redemption tokens as they will be processed differently
        uint256 reward_remaining;
    }

    //rewards
    RewardType[] public rewards;
    uint256 public currentRewardEpoch;
    mapping(address => uint256) public userRewardEpoch; //account -> epoch
    mapping(uint256 => mapping(address => uint256)) public global_reward_integral; //epoch -> token -> integral
    mapping(uint256 => mapping(address => mapping(address => uint256))) public reward_integral_for;// epoch -> token -> account -> integral
    mapping(address => mapping(address => uint256)) public claimable_reward;//token -> account -> claimable
    mapping(address => uint256) public rewardMap;
    mapping(address => address) public rewardRedirect;
    
    uint256 constant private PRECISION = 1e22;

    //events
    event RewardPaid(address indexed _user, address indexed _rewardToken, address indexed _receiver, uint256 _rewardAmount);
    event RewardAdded(address indexed _rewardToken);
    event RewardInvalidated(address indexed _rewardToken);
    event RewardRedirected(address indexed _account, address _forward);
    event NewEpoch(uint256 indexed _epoch);

    constructor() {

    }

    modifier onlyRewardManager() {
        require(_isRewardManager(), "!rewardManager");
        _;
    }

/////////
//  Abstract functions
////////

    function _isRewardManager() internal view virtual returns(bool);

    function _fetchIncentives() internal virtual;

    function _totalRewardShares() internal view virtual returns(uint256);

    function _userRewardShares(address _account) internal view virtual returns(uint256);

    function _increaseUserRewardEpoch(address _account, uint256 _currentUserEpoch) internal virtual;

    function _checkAddToken(address _address) internal view virtual returns(bool);
//////////

    function maxRewards() public pure virtual returns(uint256){
        return 15;
    }

    //register an extra reward token to be handled
    function addExtraReward(address _token) external onlyRewardManager nonReentrant{
        //add to reward list
        _insertRewardToken(_token);
    }

    //insert a new reward, ignore if already registered or invalid
    function _insertRewardToken(address _token) internal{
        if(_token == address(this) || _token == address(0) || !_checkAddToken(_token)){
            //dont allow reward tracking of the staking token or invalid address
            return;
        }

        //add to reward list if new
        if(rewardMap[_token] == 0){
            //check reward count for new additions
            require(rewards.length < maxRewards(), "max rewards");

            //set token
            RewardType storage r = rewards.push();
            r.reward_token = _token;
            
            //set map index after push (mapped value is +1 of real index)
            rewardMap[_token] = rewards.length;

            emit RewardAdded(_token);
            //workaround: transfer 0 to self so that earned() reports correctly
            //with new tokens
            if(_token.code.length > 0){
                IERC20(_token).safeTransfer(address(this), 0);
            }else{
                //non contract address added? invalidate
                _invalidateReward(_token);
            }
        }else{
            //get previous used index of given token
            //this ensures that reviving can only be done on the previous used slot
            uint256 index = rewardMap[_token];
            //index is rewardMap minus one
            RewardType storage reward = rewards[index-1];
            //check if it was invalidated
            if(reward.reward_token == address(0)){
                //revive
                reward.reward_token = _token;
                emit RewardAdded(_token);
            }
        }
    }

    //allow invalidating a reward if the token causes trouble in calcRewardIntegral
    function invalidateReward(address _token) external onlyRewardManager nonReentrant{
        _invalidateReward(_token);
    }

    function _invalidateReward(address _token) internal{
        uint256 index = rewardMap[_token];
        if(index > 0){
            //index is registered rewards minus one
            RewardType storage reward = rewards[index-1];
            require(reward.reward_token == _token, "!mismatch");
            //set reward token address to 0, integral calc will now skip
            reward.reward_token = address(0);
            emit RewardInvalidated(_token);
        }
    }

    //get reward count
    function rewardLength() external view returns(uint256) {
        return rewards.length;
    }

    //calculate and record an account's earnings of the given reward.  if _claimTo is given it will also claim.
    function _calcRewardIntegral(uint256 _epoch, uint256 _currentEpoch, uint256 _index, address _account, address _claimTo) internal{
        RewardType storage reward = rewards[_index];
        address rewardToken = reward.reward_token;
        //skip invalidated rewards
        //if a reward token starts throwing an error, calcRewardIntegral needs a way to exit
        if(rewardToken == address(0)){
           return;
        }

        //get difference in balance and remaining rewards
        //getReward is unguarded so we use reward_remaining to keep track of how much was actually claimed since last checkpoint
        uint256 bal = IERC20(rewardToken).balanceOf(address(this));
        uint256 remainingRewards = reward.reward_remaining;
        
        //update the global integral but only for the current epoch
        if (_epoch == _currentEpoch && _totalRewardShares() > 0 && bal > remainingRewards) {
            uint256 rewardPerToken = ((bal - remainingRewards) * PRECISION / _totalRewardShares());
            if(rewardPerToken > 0){
                //increase integral
                global_reward_integral[_epoch][rewardToken] += rewardPerToken;
            }else{
                //set balance as current reward_remaining to let dust grow
                bal = remainingRewards;
            }
        }

        uint256 reward_global = global_reward_integral[_epoch][rewardToken];

        if(_account != address(0)){
            //update user integrals
            uint userI = reward_integral_for[_epoch][rewardToken][_account];
            if(_claimTo != address(0) || userI < reward_global){
                //_claimTo address non-zero means its a claim 
                // only allow claims if current epoch and if the reward allows it
                if(_epoch == _currentEpoch && _claimTo != address(0) && !reward.is_non_claimable){
                    uint256 receiveable = claimable_reward[rewardToken][_account] + (_userRewardShares(_account) * (reward_global - userI) / PRECISION);
                    if(receiveable > 0){
                        claimable_reward[rewardToken][_account] = 0;
                        IERC20(rewardToken).safeTransfer(_claimTo, receiveable);
                        emit RewardPaid(_account, rewardToken, _claimTo, receiveable);
                        //remove what was claimed from balance
                        bal -= receiveable;
                    }
                }else{
                    claimable_reward[rewardToken][_account] = claimable_reward[rewardToken][_account] + ( _userRewardShares(_account) * (reward_global - userI) / PRECISION);
                }
                reward_integral_for[_epoch][rewardToken][_account] = reward_global;
            }
        }


        //update remaining reward so that next claim can properly calculate the balance change
        //claims and tracking new rewards should only happen on current epoch
        if(_epoch == _currentEpoch && bal != remainingRewards){
            reward.reward_remaining = bal;
        }
    }

    function _increaseRewardEpoch() internal{
        //final checkpoint for this epoch
        _checkpoint(address(0), address(0), type(uint256).max);

        //move epoch up
        uint256 newEpoch = currentRewardEpoch + 1;
        currentRewardEpoch = newEpoch;

        emit NewEpoch(newEpoch);
    }

    //checkpoint without claiming
    function _checkpoint(address _account) internal {
        //checkpoint without claiming by passing address(0)
        //default to max as most operations such as deposit/withdraw etc needs to fully sync beforehand
        _checkpoint(_account, address(0), type(uint256).max);
    }

    //checkpoint with claim
    function _checkpoint(address _account, address _claimTo, uint256 _maxloops) internal {
        //claim rewards first
        _fetchIncentives();

        uint256 globalEpoch = currentRewardEpoch;
        uint256 rewardCount = rewards.length;

        for (uint256 loops = 0; loops < _maxloops;) {
            uint256 userEpoch = globalEpoch;

            if(_account != address(0)){
                //take user epoch
                userEpoch = userRewardEpoch[_account];

                //if no shares then jump to current epoch
                if(userEpoch != globalEpoch && _userRewardShares(_account) == 0){
                    userEpoch = globalEpoch;
                    userRewardEpoch[_account] = userEpoch;
                }
            }
            
            //calc reward integrals
            for(uint256 i = 0; i < rewardCount;){
                _calcRewardIntegral(userEpoch, globalEpoch, i,_account,_claimTo);
                unchecked { i += 1; }
            }
            if(userEpoch < globalEpoch){
                _increaseUserRewardEpoch(_account, userEpoch);
            }else{
                return;
            }
            unchecked { loops += 1; }
        }
    }

    //manually checkpoint a user account
    function user_checkpoint(address _account, uint256 _epochloops) external nonReentrant returns(bool) {
        _checkpoint(_account, address(0), _epochloops);
        return true;
    }

    //get earned token info
    //change ABI to view to use this off chain
    function earned(address _account) public nonReentrant virtual returns(EarnedData[] memory claimable) {
        
        //because this is a state mutative function
        //we can simplify the earned() logic of all rewards (internal and external)
        //and allow this contract to be agnostic to outside reward contract design
        //by just claiming everything and updating state via _checkpoint()
        _checkpoint(_account);
        uint256 rewardCount = rewards.length;
        claimable = new EarnedData[](rewardCount);

        for (uint256 i = 0; i < rewardCount;) {
            RewardType storage reward = rewards[i];

            //skip invalidated and non claimable rewards
            if(reward.reward_token == address(0) || reward.is_non_claimable){
                unchecked{ i += 1; }
                continue;
            }
    
            claimable[i].amount = claimable_reward[reward.reward_token][_account];
            claimable[i].token = reward.reward_token;

            unchecked{ i += 1; }
        }
        return claimable;
    }

    //set any claimed rewards to automatically go to a different address
    //set address to zero to disable
    function setRewardRedirect(address _to) external nonReentrant{
        rewardRedirect[msg.sender] = _to;
        emit RewardRedirected(msg.sender, _to);
    }

    //claim reward for given account (unguarded)
    function getReward(address _account) public virtual nonReentrant {
        //check if there is a redirect address
        address redirect = rewardRedirect[_account];
        if(redirect != address(0)){
            _checkpoint(_account, redirect, type(uint256).max);
        }else{
            //claim directly in checkpoint logic to save a bit of gas
            _checkpoint(_account, _account, type(uint256).max);
        }
    }

    //claim reward for given account and forward (guarded)
    function getReward(address _account, address _forwardTo) public virtual nonReentrant{
        //in order to forward, must be called by the account itself
        require(msg.sender == _account, "!self");
        require(_forwardTo != address(0), "fwd address cannot be 0");
        //use _forwardTo address instead of _account
        _checkpoint(_account, _forwardTo, type(uint256).max);
    }
}
WriteOffToken.sol 50 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

/*
A token-like contract to track write offs via erc20 interfaces to be used
in reward distribution logic

interfaces needed:
- balanceOf
- transfer
- mint

*/
contract WriteOffToken {

    address public immutable owner;
    uint256 public totalSupply;

    constructor(address _owner)
    {
        owner = _owner;
    }

    function name() external pure returns (string memory){
        return "WriteOffToken";
    }

    function symbol() external pure returns (string memory){
        return "WOT";
    }

    function decimals() external pure returns (uint8){
        return 18;
    }

    function mint(uint256 _amount) external{
        if(msg.sender == owner){
            totalSupply += _amount;
        }
    }

    function transfer(address to, uint256 amount) external returns (bool){
        return true;
    }

    function balanceOf(address) external view returns (uint256){
        //just return total supply
        return totalSupply;
    }
}
IERC4626.sol 48 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

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

interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    function asset() external view returns (address);

    function convertToAssets(uint256 shares) external view returns (uint256);

    function convertToShares(uint256 assets) external view returns (uint256);

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

    function maxMint(address) external view returns (uint256);

    function maxRedeem(address owner) external view returns (uint256);

    function maxWithdraw(address owner) external view returns (uint256);

    function previewDeposit(uint256 assets) external view returns (uint256);

    function previewMint(uint256 shares) external view returns (uint256);

    function previewRedeem(uint256 shares) external view returns (uint256);

    function previewWithdraw(uint256 assets) external view returns (uint256);

    function totalAssets() external view returns (uint256);

    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
}
CoreOwnable.sol 27 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ICore} from "../interfaces/ICore.sol";

/**
    @title Core Ownable
    @author Prisma Finance (with edits by Resupply Finance)
    @notice Contracts inheriting `CoreOwnable` have the same owner as `Core`.
            The ownership cannot be independently modified or renounced.
 */
contract CoreOwnable {
    ICore public immutable core;

    constructor(address _core) {
        core = ICore(_core);
    }

    modifier onlyOwner() {
        require(msg.sender == address(core), "!core");
        _;
    }

    function owner() public view returns (address) {
        return address(core);
    }
}
IMintable.sol 7 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IMintable{
    function mint(address _to, uint256 _amount) external;
    function burn(address _from, uint256 _amount) external;
}
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

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

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
ICore.sol 31 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { IAuthHook } from './IAuthHook.sol';

interface ICore {
    struct OperatorAuth {
        bool authorized;
        IAuthHook hook;
    }

    event VoterSet(address indexed newVoter);
    event OperatorExecuted(address indexed caller, address indexed target, bytes data);
    event OperatorSet(address indexed caller, address indexed target, bool authorized, bytes4 selector, IAuthHook authHook);

    function execute(address target, bytes calldata data) external returns (bytes memory);
    function epochLength() external view returns (uint256);
    function startTime() external view returns (uint256);
    function voter() external view returns (address);
    function ownershipTransferDeadline() external view returns (uint256);
    function pendingOwner() external view returns (address);
    function setOperatorPermissions(
        address caller,
        address target,
        bytes4 selector,
        bool authorized,
        IAuthHook authHook
    ) external;
    function setVoter(address newVoter) external;
    function operatorPermissions(address caller, address target, bytes4 selector) external view returns (bool authorized, IAuthHook hook);
}
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";
IAuthHook.sol 7 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

interface IAuthHook {
    function preHook(address operator, address target, bytes calldata data) external returns (bool);
    function postHook(bytes memory result, address operator, address target, bytes calldata data) external returns (bool);
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

Read Contract

CRV 0x945c9142 → address
CVX 0x759cb53b → address
EXCHANGE_PRECISION 0xc0a7e892 → uint256
LIQ_PRECISION 0x9fe34bdc → uint256
LTV_PRECISION 0xb78294dd → uint256
PAIR_DECIMALS 0xc49bb2d1 → uint256
RATE_PRECISION 0x2b3ba681 → uint256
SHARE_REFACTOR_PRECISION 0x146a29eb → uint256
borrowLimit 0xe551d11d → uint256
claimableFees 0xc3192f14 → uint256
claimableOtherFees 0x2164e85b → uint256
claimable_reward 0x33fd6f74 → uint256
collateral 0xd8dfeb45 → address
convexBooster 0x2cdacb50 → address
convexPid 0x86993bef → uint256
core 0xf2f4eb26 → address
currentRateInfo 0x95d14ca8 → uint64, uint64, uint128
currentRewardEpoch 0x56ecf28b → uint256
currentUtilization 0xfff5d9da → uint256
epochLength 0x57d775f8 → uint256
exchangeRateInfo 0xfbbbf94c → address, uint96, uint256
getConstants 0x9a295e73 → uint256, uint256, uint256, uint256
getEpoch 0x757991a8 → uint256
getPairAccounting 0xcdd72d52 → uint256, uint128, uint128, uint256
global_reward_integral 0xe69bc271 → uint256
lastFeeEpoch 0xca51bb59 → uint256
liquidationFee 0xa36a3630 → uint256
maxLTV 0xf384bd05 → uint256
maxRewards 0xd619658b → uint256
minimumBorrowAmount 0x08c7e366 → uint256
minimumLeftoverDebt 0x92bbcaed → uint256
minimumRedemption 0x8f50ea92 → uint256
mintFee 0x13966db5 → uint256
name 0x06fdde03 → string
owner 0x8da5cb5b → address
previewAddInterest 0xcacf3b58 → uint256, tuple, uint256, tuple
protocolRedemptionFee 0xab7cfaf9 → uint256
rateCalculator 0x0df8dfac → address
redemptionWriteOff 0x42f43d0b → address
registry 0x7b103999 → address
rewardLength 0xb95c5746 → uint256
rewardMap 0x83d44339 → uint256
rewardRedirect 0xe509b9d9 → address
reward_integral_for 0x91ebebbd → uint256
rewards 0xf301af42 → address, bool, uint256
startTime 0x78e97925 → uint256
swappers 0x8cad7fbe → bool
toBorrowAmount 0x7ec4b571 → uint256
toBorrowShares 0x93f46f64 → uint256
totalBorrow 0x8285ef40 → uint128, uint128
totalCollateral 0x4ac8eb5f → uint256
totalDebtAvailable 0x93ae0df9 → uint256
underlying 0x6f307dc3 → address
userBorrowShares 0x4fd422df → uint256
userRewardEpoch 0xb5b54547 → uint256
version 0x54fd4d50 → uint256, uint256, uint256

Write Contract 36 functions

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

addCollateral 0xcadac479
uint256 _amount
address _borrower
addCollateralVault 0x60c52d05
uint256 _collateralAmount
address _borrower
addExtraReward 0x5e43c47b
address _token
addInterest 0x1c6c9597
bool _returnAccounting
returns: uint256, tuple, uint256, tuple
borrow 0xd6bda0c0
uint256 _borrowAmount
uint256 _underlyingAmount
address _receiver
returns: uint256
earned 0x008cc262
address _account
returns: tuple[]
getReward 0x6b091695
address _account
address _forwardTo
getReward 0xc00007b0
address _account
getUserSnapshot 0xb68d0a09
address _address
returns: uint256, uint256
invalidateReward 0xf8112eed
address _token
leveragedPosition 0xa053db68
address _swapperAddress
uint256 _borrowAmount
uint256 _initialUnderlyingAmount
uint256 _amountCollateralOutMin
address[] _path
returns: uint256
liquidate 0x2f865568
address _borrower
returns: uint256
pause 0x8456cb59
No parameters
redeemCollateral 0xfd6d0526
address _caller
uint256 _amount
uint256 _totalFeePct
address _receiver
returns: address, uint256
removeCollateral 0xd41ddc96
uint256 _collateralAmount
address _receiver
removeCollateralVault 0x8049d971
uint256 _collateralAmount
address _receiver
repay 0xacb70815
uint256 _shares
address _borrower
returns: uint256
repayWithCollateral 0x0cab937d
address _swapperAddress
uint256 _collateralToSwap
uint256 _amountOutMin
address[] _path
returns: uint256
setBorrowLimit 0xe7a33174
uint256 _limit
setConvexPool 0x7e929684
uint256 pid
setLiquidationFees 0x730a7514
uint256 _newLiquidationFee
setMaxLTV 0x08a0c375
uint256 _newMaxLTV
setMinimumBorrowAmount 0x4cae6513
uint256 _min
setMinimumLeftoverDebt 0x8214ba48
uint256 _min
setMinimumRedemption 0xd870ce66
uint256 _min
setMintFees 0x06b6f7e9
uint256 _newMintFee
setOracle 0x7adbf973
address _newOracle
setProtocolRedemptionFee 0x6551f16c
uint256 _fee
setRateCalculator 0x48741376
address _newRateCalculator
bool _updateInterest
setRewardRedirect 0x75a41014
address _to
setSwapper 0x3f2617cb
address _swapper
bool _approval
unpause 0x3f4ba83a
No parameters
updateExchangeRate 0x02ce728f
No parameters
returns: uint256
userCollateralBalance 0xb5af3062
address _account
returns: uint256
user_checkpoint 0xf5c7f899
address _account
uint256 _epochloops
returns: bool
withdrawFees 0x476343ee
No parameters
returns: uint256, uint256

Recent Transactions

No transactions found for this address