Forkchoice Ethereum Mainnet

Address Contract Partially Verified

Address 0x52d809071f261b01242153c2C9D1F8a2EdeABa2E
Balance 0 ETH
Nonce 1
Code Size 15303 bytes
Indexed Transactions 0 (1 on-chain, 0% indexed)
External Etherscan · Sourcify

Contract Bytecode

15303 bytes
0x60806040526004361015610011575f80fd5b5f5f3560e01c806303011dab1461229b57806305a363de146122815780631529a6391461221557806318c9a7f1146121f85780631b11d0ff1461174f57806320cd116f146117345780632b3297f91461170c578063302cdc4a146116f05780633237c158146115e357806336d2b5aa146114b7578063370587491461148857806338f5847a1461144457806342f8b5021461142b5780634b836fb0146111a757806359bfe9f7146111395780635ba1c1a91461111b5780635ccbbdf1146110ad5780635dc565c214611090578063640e7fd51461107557806368cf06e4146110525780636b09de4514610f2557806374692c2514610ebd57806376f0229614610e9b5780637d41e55114610e7f578063816b1e8f14610e4757806382fee08f14610e285780638bb3271114610e0c578063904b513b14610bbc578063915541f514610b775780639ce110d714610b4e578063a03e4bc314610b09578063a0c1f15e14610ac4578063a217d5ce14610a7f578063a919802d14610295578063aabaecd614610a17578063aaf5eb6814610a5c578063b2016bd414610a17578063bcc46e83146108f0578063bd1f10aa1461081b578063c0177bd5146107fc578063c24506f914610730578063ccb6a27014610689578063ccee305e146105e7578063d3205c11146105b2578063d3286d7114610458578063da2635d3146103ca578063dd7dc9a9146103ac578063e486473114610371578063e86211491461034e578063ea7f52d114610313578063eea6d43a146102f6578063f0055bed146102da578063f8d89898146102955763fbfa77cf1461026c575f80fd5b34610292578060031936011261029257546040516001600160a01b039091168152602090f35b80fd5b50346102925780600319360112610292576040517f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b03168152602090f35b5034610292578060031936011261029257602060405160698152f35b503461029257806003193601126102925760206040516127108152f35b503461029257806003193601126102925760206040517f0000000000000000000000000000000000000000000000000000000000001d4c8152f35b503461029257806003193601126102925760206103696133e0565b604051908152f35b503461029257806003193601126102925760206040517f0000000000000000000000000000000000000000000000000000000000001f408152f35b50346102925780600319360112610292576020604051620f42408152f35b503461029257806003193601126102925780546001600160a01b0316330361044a576004541561043b57600380546001600160a01b0319811690915560048290556001600160a01b03167f17fe3249297002b4c17de74c86ca3049b2240dc53e88668b5c70e4a9c1bb1dab8280a280f35b630bacc2dd60e31b8152600490fd5b6282b42960e81b8152600490fd5b50346102925780600319360112610292576040516370a0823160e01b81523060048201526020816024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9081156105a7578291610575575b50158015906104d3575b6020906040519015158152f35b506040516370a0823160e01b8152306004820152906020826024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa9081156105695790610532575b6020915015156104c6565b506020813d602011610561575b8161054c602093836122e1565b8101031261055d5760209051610527565b5f80fd5b3d915061053f565b604051903d90823e3d90fd5b90506020813d60201161059f575b81610590602093836122e1565b8101031261055d57515f6104bc565b3d9150610583565b6040513d84823e3d90fd5b5034610292576020670de0b6b3a76400006105de6105d96105d2366122cb565b9190613849565b6124c3565b04604051908152f35b50346102925780600319360112610292576040516370a0823160e01b8152306004820152906020826024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa9081156105695790610656575b602090604051908152f35b506020813d602011610681575b81610670602093836122e1565b8101031261055d576020905161064b565b3d9150610663565b5034610292576020366003190112610292576106a36122b5565b8154906001600160a01b038216610721576001600160a01b031690811561072157600154906001600160a01b0382163303610713576001600160a01b031990811683178455166001557f733be0fdaf77621eb46ef87502d63fea13e94bc406be6ae17d75d783e0ceaa808280a280f35b6282b42960e81b8452600484fd5b63e6c4247b60e01b8352600483fd5b50346102925760203660031901126102925761074a6122b5565b81546001600160a01b031633036107ee576001600160a01b031680156107df57600380546001600160a01b03191682179055426202a30081019081106107cb5760048190556002546040519182526001600160a01b0316907f4ecdeb1f5ee7ffbc69f0081bf0315681558893f4cb77a3b4ab09cfa45d33742d90602090a380f35b634e487b7160e01b83526011600452602483fd5b63e6c4247b60e01b8252600482fd5b6282b42960e81b8252600482fd5b503461029257602036600319011261029257602061036960043561357e565b503461029257806003193601126102925780546001600160a01b0316330361044a5760018060a01b036002541660045480156108e1578042106108d25762093a8081018091116107cb5742116108c357600380546001600160a01b03198082169092556004849055600280546001600160a01b03909216919092168117909155907ffb7a5f1d35a7022d9d6343bfc9a25035829d0ea72da06978793c945b1d94a17f8380a380f35b633d37e55360e11b8252600482fd5b637378c19d60e01b8352600483fd5b630bacc2dd60e31b8352600483fd5b5034610292576020366003190112610292578054600435906001600160a01b031633036107ee578015610a085761092561397c565b816001600160a01b037f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5998116907f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e21661097f84828461367f565b803b15610a04576109ad839291839260405194858094819363617ba03760e01b83528a309160048501612e5e565b03925af180156105a7576109eb575b507f206d5f22994381488f76c20f193ebf898daf960461a61d40e68e1641f62d8c5d602083604051908152a180f35b816109f5916122e1565b610a0057815f6109bc565b5080fd5b8280fd5b631f2a200560e01b8252600482fd5b50346102925780600319360112610292576040517f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b03168152602090f35b50346102925780600319360112610292576020604051670de0b6b3a76400008152f35b50346102925780600319360112610292576040517f000000000000000000000000f4030086522a5beea4988f8ca5b36dbc97bee88c6001600160a01b03168152602090f35b50346102925780600319360112610292576040517f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03168152602090f35b50346102925780600319360112610292576040517f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b03168152602090f35b50346102925780600319360112610292576001546040516001600160a01b039091168152602090f35b50346102925780600319360112610292576040517f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03168152602090f35b503461029257606036600319011261029257805460043590602435906001600160a01b03163303610dfe578115610def57610bf561397c565b6001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e281169084907f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59916610c5085848361367f565b823b15610a005781610c79916040518093819263617ba03760e01b835289309160048501612e5e565b038183875af180156105a757610dda575b508215159182610d5e575b5050610cd6575b7f0979c8e7bdf69725109f345f602e8ee65a6404e4d6dd2415d189c745ee002e7e916060916040519182526020820152836040820152a180f35b60405163640e7fd560e01b8152602081600481305afa8015610d53578490610d19575b670f43fc2c04ee000091501215610c9c5763dc50e57160e01b8352600483fd5b506020813d602011610d4b575b81610d33602093836122e1565b8101031261055d57670f43fc2c04ee00009051610cf9565b3d9150610d26565b6040513d86823e3d90fd5b803b15610a005760405163a415bcad60e01b81529082908290818381610db2308b7f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b031660048501612e87565b03925af180156105a75715610c955781610dcb916122e1565b610dd657835f610c95565b8380fd5b81610de4916122e1565b610dd657835f610c8a565b631f2a200560e01b8352600483fd5b6282b42960e81b8352600483fd5b5034610292578060031936011261029257610e2561397c565b80f35b5034610292576020366003190112610292576020610369600435613849565b503461029257604036600319011261029257610e616122b5565b81546001600160a01b031633036107ee57610e2590602435906131e2565b5034610292578060031936011261029257602060405160018152f35b5034610292576040366003190112610292576020610369602435600435613063565b503461029257604036600319011261029257610ed76122b5565b81546001600160a01b031633036107ee576001600160a01b038116156107df57610e2590602435907f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec761392f565b5034610292576020366003190112610292578054600435906001600160a01b031633036107ee578015610a0857610f5a61397c565b816020610fd86001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e28116907f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec716610fb986838361367f565b60405194858094819363573ade8160e01b835289309160048501612476565b03925af1801561104757611016575b5060207f9a7851747cd7ffb3fe0a32caf3da48b31f27cebe131267051640f8b72fc4718691604051908152a180f35b6020813d60201161103f575b8161102f602093836122e1565b8101031261055d57506020610fe7565b3d9150611022565b6040513d85823e3d90fd5b50346102925780600319360112610292576020604051670f43fc2c04ee00008152f35b50346102925780600319360112610292576020610369612eb7565b50346102925780600319360112610292576020604051610e108152f35b50346102925780600319360112610292576040516370a0823160e01b8152306004820152906020826024817f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b03165afa908115610569579061065657602090604051908152f35b503461029257806003193601126102925760206040516202a3008152f35b50346102925780600319360112610292576040516370a0823160e01b8152306004820152906020826024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa908115610569579061065657602090604051908152f35b503461055d576111b6366122cb565b5f546001600160a01b0316330361141d5781158080611415575b611406576111dc61397c565b1561134a575b801515806112a0575b611223575b7f896ee67286c219be947f084e014cc41a00d44857ba43f2426bc64108255946b79160409182519182526020820152a180f35b60405163640e7fd560e01b8152602081600481305afa8015610d53578490611266575b670f43fc2c04ee0000915012156111f05763dc50e57160e01b8352600483fd5b506020813d602011611298575b81611280602093836122e1565b8101031261055d57670f43fc2c04ee00009051611246565b3d9150611273565b837f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b0316803b15610a005760405163a415bcad60e01b8152908290829081838161131f308b7f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b031660048501612e87565b03925af180156105a757611335575b50506111eb565b8161133f916122e1565b610dd657835f61132e565b6001600160a01b037f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5998116907f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2166113a384828461367f565b803b1561055d576113d15f9291839260405194858094819363617ba03760e01b83528a309160048501612e5e565b03925af180156113fb576113e6575b506111e2565b6113f39193505f906122e1565b5f915f6113e0565b6040513d5f823e3d90fd5b631f2a200560e01b5f5260045ffd5b5081156111d0565b6282b42960e81b5f5260045ffd5b3461055d57602061036961143e366122cb565b50612df5565b3461055d575f36600319011261055d576040517f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d6001600160a01b03168152602090f35b3461055d57602036600319011261055d575f546001600160a01b0316330361141d576114b5600435612527565b005b3461055d575f36600319011261055d576040516370a0823160e01b81523060048201526020816024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9081156113fb575f916115b1575b506040516370a0823160e01b8152306004820152906020826024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa80156113fb575f9061157e575b6040809350519182526020820152f35b506020823d6020116115a9575b81611598602093836122e1565b8101031261055d576040915161156e565b3d915061158b565b90506020813d6020116115db575b816115cc602093836122e1565b8101031261055d57518161151a565b3d91506115bf565b3461055d57602036600319011261055d575f54600435906001600160a01b0316330361141d57801561140657604051631a4ca37b60e21b81526020818061165830867f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b0316600485016124a0565b03815f7f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b03165af180156113fb576116bf575b7fffaf0d1bd2e66efeb20cda45a11e8ae661eade07c330ba5a386e3e6c0f3800fb602083604051908152a1005b6020813d6020116116e8575b816116d8602093836122e1565b8101031261055d57506020611692565b3d91506116cb565b3461055d575f36600319011261055d57602060405161251c8152f35b3461055d575f36600319011261055d576002546040516001600160a01b039091168152602090f35b3461055d575f36600319011261055d57602060405160028152f35b3461055d5760a036600319011261055d576117686122b5565b6064356001600160a01b0381169081900361055d57608435906001600160401b03821161055d573660238301121561055d5760048201356001600160401b03811161055d57820136602482011161055d577f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b0316913383900361141d57300361141d576001600160a01b037f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec781169416849003611f31576040908390031261055d5760448201359160240135821515830361055d57611852604435602435612469565b6040516370a0823160e01b81523060048201529093907f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b031690602081602481855afa9081156113fb575f916121c6575b506040516370a0823160e01b81523060048201526020816024818b5afa9081156113fb575f91612194575b508082101561218d57505b80612121575b506020602491604051928380926370a0823160e01b82523060048301525afa9081156113fb575f916120ef575b508061202b575b5015611fa15750604051631a4ca37b60e21b81527f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b031660048201525f1960248201523060448201526020816064815f865af180156113fb57611f72575b505b6040516370a0823160e01b8152306004820152602081602481875afa9081156113fb575f91611f40575b50828110611a3e575b506040516370a0823160e01b8152306004820152602081602481875afa80156113fb5783915f91611a09575b50106119fa576119ef9261367f565b602060405160018152f35b632b66034160e11b5f5260045ffd5b9150506020813d602011611a36575b81611a25602093836122e1565b8101031261055d57829051856119e0565b3d9150611a18565b6002546001600160a01b031615611f3157611a5c611a619184612327565b61357e565b606981029080820460691490151715611ccc576064900460018101809111611ccc576040516370a0823160e01b81523060048201527f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599916001600160a01b03831691602081602481865afa9081156113fb575f91611eff575b5080821015611ef857505b80611d7b575b506040516370a0823160e01b8152306004820152602081602481895afa80156113fb5785915f91611d46575b5010611b24575b506119b4565b6020602491604051928380926370a0823160e01b82523060048301525afa9081156113fb575f91611d14575b508015611b1e576040516370a0823160e01b815230600482015290602082602481895afa9182156113fb575f92611ce0575b50611b8c81613849565b61251c81029080820461251c1490151715611ccc5781611bc05f93612710602094049660018060a01b03600254169061392f565b6002546040516362f4faeb60e01b815260048101929092529092839160249183916001600160a01b03165af180156113fb57611c9d575b506040516370a0823160e01b8152306004820152602081602481895afa9081156113fb575f91611c6b575b5081811115611c545790611c3591612327565b9080821015611b1e575b63fa6145e560e01b5f5260045260245260445ffd5b8263fa6145e560e01b5f526004525f60245260445ffd5b90506020813d602011611c95575b81611c86602093836122e1565b8101031261055d575186611c22565b3d9150611c79565b6020813d602011611cc4575b81611cb6602093836122e1565b8101031261055d5751611bf7565b3d9150611ca9565b634e487b7160e01b5f52601160045260245ffd5b9091506020813d602011611d0c575b81611cfc602093836122e1565b8101031261055d57519086611b82565b3d9150611cef565b90506020813d602011611d3e575b81611d2f602093836122e1565b8101031261055d575185611b50565b3d9150611d22565b9150506020813d602011611d73575b81611d62602093836122e1565b8101031261055d5784905187611b17565b3d9150611d55565b6040516370a0823160e01b81523060048201526020816024818a5afa9081156113fb575f91611ec6575b50611daf82613849565b61251c81029080820461251c1490151715611ccc5760206127105f920493611de28160018060a01b03600254168961392f565b6002546040516362f4faeb60e01b815260048101929092529092839160249183916001600160a01b03165af180156113fb57611e97575b506040516370a0823160e01b81523060048201526020816024818b5afa9081156113fb575f91611e65575b5081811115611c545790611e5791612327565b90808210611c3f5750611aeb565b90506020813d602011611e8f575b81611e80602093836122e1565b8101031261055d575188611e44565b3d9150611e73565b6020813d602011611ebe575b81611eb0602093836122e1565b8101031261055d5751611e19565b3d9150611ea3565b90506020813d602011611ef0575b81611ee1602093836122e1565b8101031261055d575187611da5565b3d9150611ed4565b9050611ae5565b90506020813d602011611f29575b81611f1a602093836122e1565b8101031261055d575187611ada565b3d9150611f0d565b63e6c4247b60e01b5f5260045ffd5b90506020813d602011611f6a575b81611f5b602093836122e1565b8101031261055d5751846119ab565b3d9150611f4e565b611f939060203d602011611f9a575b611f8b81836122e1565b810190612318565b508361197f565b503d611f81565b80611fad575b50611981565b604051631a4ca37b60e21b81529060209082908190611ffb9030907f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b0316600485016124a0565b03815f865af180156113fb5715611fa7576120249060203d602011611f9a57611f8b81836122e1565b5083611fa7565b6040516370a0823160e01b81523060048201526020816024818a5afa80156113fb5782915f916120ba575b501061191a5760208161206d61208a93878a61367f565b6040518093819263573ade8160e01b835230908b60048501612476565b03815f885af180156113fb571561191a576120b39060203d602011611f9a57611f8b81836122e1565b508561191a565b9150506020813d6020116120e7575b816120d6602093836122e1565b8101031261055d5781905188612056565b3d91506120c9565b90506020813d602011612119575b8161210a602093836122e1565b8101031261055d575186611913565b3d91506120fd565b9060208261213361215094888b61367f565b6040518094819263573ade8160e01b835230908c60048501612476565b03815f895af19081156113fb57602492602092612170575b5091506118e6565b61218690833d8511611f9a57611f8b81836122e1565b5088612168565b90506118e0565b90506020813d6020116121be575b816121af602093836122e1565b8101031261055d5751886118d5565b3d91506121a2565b90506020813d6020116121f0575b816121e1602093836122e1565b8101031261055d5751876118aa565b3d91506121d4565b3461055d575f36600319011261055d57602060405162015f908152f35b3461055d575f36600319011261055d576040516370a0823160e01b81523060048201526020816024817f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b03165afa80156113fb575f9061065657602090604051908152f35b3461055d575f36600319011261055d5760206040515f8152f35b3461055d575f36600319011261055d576020610369612334565b600435906001600160a01b038216820361055d57565b604090600319011261055d576004359060243590565b601f909101601f19168101906001600160401b0382119082101761230457604052565b634e487b7160e01b5f52604160045260245ffd5b9081602091031261055d575190565b91908203918211611ccc57565b6040516370a0823160e01b81523060048201526020816024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9081156113fb575f91612437575b506040516370a0823160e01b81523060048201526020816024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa9081156113fb575f91612405575b5081156123ff576123eb9061357e565b808211156123ff576123fc91612327565b90565b50505f90565b90506020813d60201161242f575b81612420602093836122e1565b8101031261055d57515f6123db565b3d9150612413565b90506020813d602011612461575b81612452602093836122e1565b8101031261055d57515f612387565b3d9150612445565b91908201809211611ccc57565b6001600160a01b039182168152602081019290925260026040830152909116606082015260800190565b6001600160a01b0391821681526020810192909252909116604082015260600190565b81810292918115918404141715611ccc57565b81156124e0570490565b634e487b7160e01b5f52601260045260245ffd5b9081602091031261055d5751801515810361055d5790565b6001600160a01b039091168152602081019190915260400190565b5f19811461253361397c565b6040516370a0823160e01b81523060048201525f926020826024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9182156113fb575f92612dc1575b506040516370a0823160e01b81523060048201527f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03169290602081602481875afa9081156113fb575f91612d8f575b5081158080612d87575b612d7e57828491875f14612d56575050505b6040516370a0823160e01b81523060048201527f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b03169290602081602481875afa9081156113fb575f91612d24575b5080831015612d1e5750815b82612c92575b6040516370a0823160e01b8152306004820152926020846024818a5afa9384156113fb575f94612c5e575b5080821115612c55576126a091612327565b905b845f91885f14612b84575050508115155b6129b95750505090915f1461290057506020602491604051928380926370a0823160e01b82523060048301525afa9081156105a75782916128ce575b506128bf578054604051631a4ca37b60e21b81526001600160a01b037f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599811660048301525f1960248301529182166044820152906020908290606490829086907f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2165af180156105a7576128a0575b505b6040516370a0823160e01b81523060048201527f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b0316602082602481845afa91821561104757839261286c575b50816127df57505050565b825460405163a9059cbb60e01b8152926020928492909183918791839161281391906001600160a01b03166004840161250c565b03925af19081156105a757829161283d575b501561282e5750565b6312171d8360e31b8152600490fd5b61285f915060203d602011612865575b61285781836122e1565b8101906124f4565b5f612825565b503d61284d565b9091506020813d602011612898575b81612888602093836122e1565b8101031261055d5751905f6127d4565b3d915061287b565b6128b89060203d602011611f9a57611f8b81836122e1565b505f61277e565b63d56c4e6d60e01b8152600490fd5b90506020813d6020116128f8575b816128e9602093836122e1565b8101031261055d57515f6126ef565b3d91506128dc565b80915061290e575b50612780565b8154604051631a4ca37b60e21b81529160209183918291612960916001600160a01b03918216917f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59916600485016124a0565b0381857f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b03165af180156105a75715612908576129b29060203d602011611f9a57611f8b81836122e1565b505f612908565b8515612b7c5750915b61283c83029280840461283c1490151715611ccc5760405192602084019182528515156040850152604084526129f96060856122e1565b7f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b031690813b1561055d575f60c4819561271095604051988997889687946310ac2ddf60e21b8652306004870152602486015204604484015260a060648401525180918160a48501528484015e83838284010152836084830152601f801991011681010301925af180156113fb57612b67575b5081612afd575b506128bf576040516370a0823160e01b81523060048201527f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b0316602082602481845afa91821561104757839261286c5750816127df57505050565b6040516370a0823160e01b81523060048201529150602090829060249082905afa9081156105a7578291612b35575b5015155f612a9a565b90506020813d602011612b5f575b81612b50602093836122e1565b8101031261055d57515f612b2c565b3d9150612b43565b612b749193505f906122e1565b5f915f612a93565b9050916129c2565b620f4240841115612b995750505060016126b3565b84612ba6575b50506126b3565b81811115612c4a57612bc091612bbb91612327565b613a32565b612bf4612bcc85613ad4565b917f0000000000000000000000000000000000000000000000000000000000001f40906124c3565b655af3107a4000810290808204655af3107a40001490151715611ccc57670de0b6b3a7640000820291808304670de0b6b3a76400001490151715611ccc5710612c3f575b845f612b9f565b506001955085612c38565b5050612bc05f613a32565b50505f906126a2565b9093506020813d602011612c8a575b81612c7a602093836122e1565b8101031261055d5751925f61268e565b3d9150612c6d565b7f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b0316612cc784828761367f565b6020604051809263573ade8160e01b8252815f81612cea308b8d60048501612476565b03925af180156113fb57612cff575b50612663565b612d179060203d602011611f9a57611f8b81836122e1565b505f612cf9565b9161265d565b90506020813d602011612d4e575b81612d3f602093836122e1565b8101031261055d57515f612651565b3d9150612d32565b91929091612d7557612d7092612d6b916124c3565b6124d6565b6125fb565b5050505f6125fb565b50505050505050565b5081156125e9565b90506020813d602011612db9575b81612daa602093836122e1565b8101031261055d57515f6125df565b3d9150612d9d565b9091506020813d602011612ded575b81612ddd602093836122e1565b8101031261055d5751905f612588565b3d9150612dd0565b8015612e5957612e049061357e565b6127108102908082046127101490151715611ccc57612e447f0000000000000000000000000000000000000000000000000000000000001d4c8092612469565b5f19810191908211611ccc576123fc916124d6565b505f90565b6001600160a01b039182168152602081019290925290911660408201525f606082015260800190565b6001600160a01b0391821681526020810192909252600260408301525f6060830152909116608082015260a00190565b6040516370a0823160e01b81523060048201526020816024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9081156113fb575f91613021575b506040516370a0823160e01b8152306004820152906020826024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa9182156113fb575f92612fed575b50801580612fe5575b612fd757612f78612f7e91613a32565b91613ad4565b908115612fd757612fb0907f0000000000000000000000000000000000000000000000000000000000001f40906124c3565b90655af3107a4000820291808304655af3107a40001490151715611ccc576123fc916124d6565b506001600160ff1b03919050565b508115612f68565b9091506020813d602011613019575b81613009602093836122e1565b8101031261055d5751905f612f5f565b3d9150612ffc565b90506020813d60201161304b575b8161303c602093836122e1565b8101031261055d57515f612f0a565b3d915061302f565b600160ff1b8114611ccc575f0390565b6040516370a0823160e01b815230600482015290916020826024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9182156113fb575f926131ae575b506040516370a0823160e01b81523060048201529282906020856024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa9485156113fb575f9561317a575b5084935f82121561316a57509061312861312e92613053565b90612327565b925b5f82121561315a57509061312861314692613053565b905b8115612fd757612f78612f7e91613a32565b6131649250612469565b90613148565b6131749250612469565b92613130565b9094506020813d6020116131a6575b81613196602093836122e1565b8101031261055d5751935f61310f565b3d9150613189565b9091506020813d6020116131da575b816131ca602093836122e1565b8101031261055d5751905f6130b8565b3d91506131bd565b906001600160a01b03821615611f31576040516370a0823160e01b81523060048201527f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b03169190602081602481865afa9081156113fb575f916133ae575b508181101561335057806132da575b508061326257505050565b6020916132836040519485938493631a4ca37b60e21b8552600485016124a0565b03815f7f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b03165af180156113fb576132bf5750565b6132d79060203d602011611f9a57611f8b81836122e1565b50565b60405163a9059cbb60e01b8152602081806132f985896004840161250c565b03815f885af19081156113fb575f91613331575b50156133225761331c91612327565b5f613257565b6312171d8360e31b5f5260045ffd5b61334a915060203d6020116128655761285781836122e1565b5f61330d565b50602091613375935f60405180968195829463a9059cbb60e01b84526004840161250c565b03925af19081156113fb575f9161338f575b501561332257565b6133a8915060203d6020116128655761285781836122e1565b5f613387565b90506020813d6020116133d8575b816133c9602093836122e1565b8101031261055d57515f613248565b3d91506133bc565b6040516370a0823160e01b81523060048201526020816024817f0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de86001600160a01b03165afa9081156113fb575f9161350b575b506040516370a0823160e01b8152306004820152906020826024817f0000000000000000000000006df1c1e379bc5a00a7b4c6e67a203333772f45a86001600160a01b03165afa9182156113fb575f926134d7575b50801580156134cf575b6123ff5761349f90613849565b80156123ff57670de0b6b3a7640000820291808304670de0b6b3a76400001490151715611ccc576123fc916124d6565b508115613492565b9091506020813d602011613503575b816134f3602093836122e1565b8101031261055d5751905f613488565b3d91506134e6565b90506020813d602011613535575b81613526602093836122e1565b8101031261055d57515f613433565b3d9150613519565b9081526001600160a01b039182166020820152610e106040820152918116606083015262015f90608083015291821660a0820152911660c082015260e00190565b602061362691604051809381926305fd48af60e31b83527f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7907f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599907f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d907f000000000000000000000000f4030086522a5beea4988f8ca5b36dbc97bee88c906004870161353d565b03817366f857d2f8c56a22eccf787a8e150c3a853040025af49081156113fb575f91613650575090565b90506020813d602011613677575b8161366b602093836122e1565b8101031261055d575190565b3d915061365e565b604051636eb1769f60e11b81526001600160a01b03919091169290602081806136ac863060048401613b77565b0381875afa9081156113fb575f91613817575b50106136c9575050565b604051636eb1769f60e11b8152602081806136e8853060048401613b77565b0381865afa9081156113fb575f916137e5575b50613788575b5f91829182604051602081019263095ea7b360e01b845260018060a01b03166024820152811960448201526044815261373b6064826122e1565b51925af16137476138f1565b9015908115613758575b5061332257565b805180151592508261376d575b50505f613751565b61378092506020809183010191016124f4565b155f80613765565b60405163095ea7b360e01b602082019081526001600160a01b03831660248301525f60448084018290528352918291906137c36064826122e1565b519082865af16137d16138f1565b50613701576312171d8360e31b5f5260045ffd5b90506020813d60201161380f575b81613800602093836122e1565b8101031261055d57515f6136fb565b3d91506137f3565b90506020813d602011613841575b81613832602093836122e1565b8101031261055d57515f6136bf565b3d9150613825565b6020613626916040518093819263e749adcb60e01b83527f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7907f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599907f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d907f000000000000000000000000f4030086522a5beea4988f8ca5b36dbc97bee88c906004870161353d565b3d1561392a573d906001600160401b038211612304576040519161391f601f8201601f1916602001846122e1565b82523d5f602084013e565b606090565b5f9291836139596139678295604051928391602083019663a9059cbb60e01b88526024840161250c565b03601f1981018352826122e1565b51926001600160a01b03165af16137476138f1565b7366f857d2f8c56a22eccf787a8e150c3a85304002803b1561055d5760405163195760f160e31b81526001600160a01b037f000000000000000000000000f4030086522a5beea4988f8ca5b36dbc97bee88c81166004830152610e1060248301527f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d16604482015262015f906064820152905f90829060849082905af480156113fb57613a265750565b5f613a30916122e1565b565b604051632559672760e11b815260048101919091526001600160a01b037f000000000000000000000000f4030086522a5beea4988f8ca5b36dbc97bee88c81166024830152610e1060448301527f0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5991660648201526020816084817366f857d2f8c56a22eccf787a8e150c3a853040025af49081156113fb575f91613650575090565b60405163ef74651b60e01b815260048101919091526001600160a01b037f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d8116602483015262015f9060448301527f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec71660648201526020816084817366f857d2f8c56a22eccf787a8e150c3a853040025af49081156113fb575f91613650575090565b6001600160a01b039182168152911660208201526040019056fea26469706673582212207376f0abd7c8100fc12eec211ba7fe4fea27d790940fff519decfb49fec8d5bc64736f6c63430008210033

Verified Source Code Partial Match

Compiler: v0.8.33+commit.64118f21 EVM: cancun Optimization: Yes (1 runs)
IAavePool.sol 29 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

interface IAavePool {
    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)
        external;

    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        uint16 referralCode,
        address onBehalfOf
    ) external;

    function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf)
        external
        returns (uint256);

    function withdraw(address asset, uint256 amount, address to) external returns (uint256);

    function flashLoanSimple(
        address receiverAddress,
        address asset,
        uint256 amount,
        bytes calldata params,
        uint16 referralCode
    ) external;
}
AaveLoanManager.sol 566 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import { IERC20 } from "../interfaces/IERC20.sol";
import { IAavePool } from "../interfaces/IAavePool.sol";
import { IChainlinkOracle } from "../interfaces/IChainlinkOracle.sol";
import { IFlashLoanSimpleReceiver } from "../interfaces/IFlashLoanSimpleReceiver.sol";
import { ILoanManager } from "../interfaces/ILoanManager.sol";
import { ISwapper } from "../interfaces/ISwapper.sol";
import { SafeTransferLib } from "../libraries/SafeTransferLib.sol";
import { TimelockLib } from "../libraries/TimelockLib.sol";
import { OracleLib } from "../libraries/OracleLib.sol";

/// @title AaveLoanManager
/// @notice Manages Aave V3 collateralized borrowing positions for Zenji
/// @dev Uses Chainlink oracles for collateral/debt valuation
contract AaveLoanManager is ILoanManager, IFlashLoanSimpleReceiver {
    using SafeTransferLib for IERC20;
    using TimelockLib for TimelockLib.AddressTimelockData;

    // ============ Constants ============

    uint256 public constant PRECISION = 1e18;
    uint256 public constant MAX_ORACLE_STALENESS = 3600; // 1 hour for BTC/USD
    uint256 public constant MAX_DEBT_ORACLE_STALENESS = 90000; // 25 hours for USDT/USD (Chainlink heartbeat is 24h)
    uint256 public constant AAVE_LTV_SCALE = 1e4;
    uint256 public constant VARIABLE_RATE_MODE = 2;
    uint16 public constant AAVE_REFERRAL_CODE = 0;
    uint256 public constant SWAP_AMOUNT_BUFFER = 105;
    uint256 public constant DUST_BUFFER = 1;
    uint256 public constant DUST_THRESHOLD = 1e6;
    uint256 public constant TIMELOCK_DELAY = 2 days;
    int256 public constant MIN_HEALTH = 1.1e18;
    uint256 public constant MIN_SWAP_OUT_BPS = 9500; // 95% of oracle quote

    // ============ Immutables ============

    IERC20 public immutable collateralToken;
    IERC20 public immutable debtToken;
    IERC20 public immutable aToken;
    IERC20 public immutable variableDebtToken;
    IAavePool public immutable aavePool;
    IChainlinkOracle public immutable collateralOracle;
    IChainlinkOracle public immutable debtOracle;
    address public vault;
    address public initializer;
    ISwapper public swapper;

    // Timelock state
    TimelockLib.AddressTimelockData internal _swapperTimelock;

    // Aave risk params (basis points)
    uint256 public immutable maxLtvBps;
    uint256 public immutable liquidationThresholdBps;

    event SwapperUpdated(address indexed oldSwapper, address indexed newSwapper);
    event SwapperChangeProposed(
        address indexed currentSwapper, address indexed newSwapper, uint256 effectiveTime
    );
    event SwapperChangeCancelled(address indexed cancelledSwapper);
    event VaultInitialized(address indexed vault);

    error InsufficientFlashloanRepayment();
    error HealthTooLow();
    error SwapperUnderperformed(uint256 expected, uint256 received);

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

    modifier onlyVault() {
        if (msg.sender != vault) revert Unauthorized();
        _;
    }

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

    constructor(
        address _collateralAsset,
        address _debtAsset,
        address _aToken,
        address _variableDebtToken,
        address _aavePool,
        address _collateralOracle,
        address _debtOracle,
        address _swapper,
        uint256 _maxLtvBps,
        uint256 _liquidationThresholdBps,
        address _vault
    ) {
        if (
            _collateralAsset == address(0) || _debtAsset == address(0) || _aToken == address(0)
                || _variableDebtToken == address(0) || _aavePool == address(0)
                || _collateralOracle == address(0) || _debtOracle == address(0)
        ) {
            revert InvalidAddress();
        }
        if (_maxLtvBps == 0 || _liquidationThresholdBps == 0) revert InvalidAddress();

        collateralToken = IERC20(_collateralAsset);
        debtToken = IERC20(_debtAsset);
        aToken = IERC20(_aToken);
        variableDebtToken = IERC20(_variableDebtToken);
        aavePool = IAavePool(_aavePool);
        collateralOracle = IChainlinkOracle(_collateralOracle);
        debtOracle = IChainlinkOracle(_debtOracle);
        maxLtvBps = _maxLtvBps;
        liquidationThresholdBps = _liquidationThresholdBps;
        if (_vault != address(0)) {
            vault = _vault;
            initializer = address(0);
        } else {
            initializer = msg.sender;
        }
        swapper = ISwapper(_swapper);
    }

    function initializeVault(address _vault) external {
        if (vault != address(0)) revert InvalidAddress();
        if (_vault == address(0)) revert InvalidAddress();
        if (msg.sender != initializer) revert Unauthorized();

        vault = _vault;
        initializer = address(0);
        emit VaultInitialized(_vault);
    }

    // ============ Loan Management Functions ============

    function createLoan(uint256 collateral, uint256 debt, uint256) external onlyVault {
        if (collateral == 0) revert ZeroAmount();
        _checkOracleFreshness();

        _ensureApprove(address(collateralToken), address(aavePool), collateral);
        aavePool.supply(address(collateralToken), collateral, address(this), AAVE_REFERRAL_CODE);

        if (debt > 0) {
            aavePool.borrow(
                address(debtToken), debt, VARIABLE_RATE_MODE, AAVE_REFERRAL_CODE, address(this)
            );
        }

        if (debt > 0) {
            int256 health = this.getHealth();
            if (health < MIN_HEALTH) revert HealthTooLow();
        }

        emit LoanCreated(collateral, debt, 0);
    }

    function addCollateral(uint256 collateral) external onlyVault {
        if (collateral == 0) revert ZeroAmount();
        _checkOracleFreshness();
        _ensureApprove(address(collateralToken), address(aavePool), collateral);
        aavePool.supply(address(collateralToken), collateral, address(this), AAVE_REFERRAL_CODE);
        emit CollateralAdded(collateral);
    }

    function borrowMore(uint256 collateral, uint256 debt) external onlyVault {
        if (collateral == 0 && debt == 0) revert ZeroAmount();
        _checkOracleFreshness();
        if (collateral > 0) {
            _ensureApprove(address(collateralToken), address(aavePool), collateral);
            aavePool.supply(address(collateralToken), collateral, address(this), AAVE_REFERRAL_CODE);
        }
        if (debt > 0) {
            aavePool.borrow(
                address(debtToken), debt, VARIABLE_RATE_MODE, AAVE_REFERRAL_CODE, address(this)
            );
        }
        if (debt > 0) {
            int256 health = this.getHealth();
            if (health < MIN_HEALTH) revert HealthTooLow();
        }

        emit LoanBorrowedMore(collateral, debt);
    }

    function repayDebt(uint256 amount) external onlyVault {
        if (amount == 0) revert ZeroAmount();
        _checkOracleFreshness();
        _ensureApprove(address(debtToken), address(aavePool), amount);
        aavePool.repay(address(debtToken), amount, VARIABLE_RATE_MODE, address(this));
        emit LoanRepaid(amount);
    }

    function removeCollateral(uint256 amount) external onlyVault {
        if (amount == 0) revert ZeroAmount();
        aavePool.withdraw(address(collateralToken), amount, address(this));
        emit CollateralRemoved(amount);
    }

    function unwindPosition(uint256 collateralNeeded) external onlyVault {
        bool fullyClose = (collateralNeeded == type(uint256).max);
        _checkOracleFreshness();

        uint256 collateral = aToken.balanceOf(address(this));
        uint256 debt = variableDebtToken.balanceOf(address(this));
        if (collateral == 0 && debt == 0) return;

        uint256 debtToRepay =
            fullyClose ? debt : (collateral > 0 ? (debt * collateralNeeded) / collateral : 0);

        uint256 debtBalance = debtToken.balanceOf(address(this));
        uint256 repayNow = debtToRepay < debtBalance ? debtToRepay : debtBalance;
        if (repayNow > 0) {
            _ensureApprove(address(debtToken), address(aavePool), repayNow);
            aavePool.repay(address(debtToken), repayNow, VARIABLE_RATE_MODE, address(this));
        }

        uint256 remainingDebt = variableDebtToken.balanceOf(address(this));
        // For partial unwinds: only flashloan if the proportional debt wasn't fully repaid.
        // The remaining Aave debt stays backed by remaining collateral.
        uint256 unrepaidDebt = debtToRepay > repayNow ? debtToRepay - repayNow : 0;
        bool needFlashloan;
        if (fullyClose) {
            needFlashloan = remainingDebt > 0;
        } else if (!_isDustDebt(unrepaidDebt)) {
            needFlashloan = true;
        } else if (remainingDebt > 0) {
            // Dust unrepaid debt but Aave still has remaining debt.
            // Check if remaining collateral can safely back that debt.
            uint256 remainingCollateral = collateral > collateralNeeded ? collateral - collateralNeeded : 0;
            uint256 remainingCollateralUsd = _getCollateralUsdValue(remainingCollateral);
            uint256 remainingDebtUsd = _getDebtUsdValue(remainingDebt);
            // Health factor: (collateralUsd * liquidationThreshold) / debtUsd must be >= 1e18
            if (remainingCollateralUsd * liquidationThresholdBps * 1e14 < remainingDebtUsd * 1e18) {
                // Withdrawing would violate health factor — escalate to full close
                needFlashloan = true;
                fullyClose = true;
            }
        }
        if (needFlashloan) {
            uint256 flashDebt = fullyClose ? remainingDebt : unrepaidDebt;
            uint256 flashloanAmount = (flashDebt * 10300) / 10000;
            bytes memory data = abi.encode(collateralNeeded, fullyClose);
            aavePool.flashLoanSimple(
                address(this), address(debtToken), flashloanAmount, data, AAVE_REFERRAL_CODE
            );

            if (fullyClose && variableDebtToken.balanceOf(address(this)) > 0) revert DebtNotFullyRepaid();
            uint256 idleCollateral = collateralToken.balanceOf(address(this));
            if (idleCollateral > 0) {
                if (!collateralToken.transfer(vault, idleCollateral)) revert TransferFailed();
            }
            return;
        }

        if (fullyClose) {
            if (variableDebtToken.balanceOf(address(this)) > 0) revert DebtNotFullyRepaid();
            aavePool.withdraw(address(collateralToken), type(uint256).max, vault);
        } else if (collateralNeeded > 0) {
            aavePool.withdraw(address(collateralToken), collateralNeeded, vault);
        }

        uint256 idleAfter = collateralToken.balanceOf(address(this));
        if (idleAfter > 0) {
            if (!collateralToken.transfer(vault, idleAfter)) revert TransferFailed();
        }
    }

    /// @inheritdoc IFlashLoanSimpleReceiver
    function executeOperation(
        address asset_,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata data
    ) external override returns (bool) {
        if (msg.sender != address(aavePool)) revert Unauthorized();
        if (initiator != address(this)) revert Unauthorized();
        if (asset_ != address(debtToken)) revert InvalidAddress();

        (uint256 collateralNeeded, bool fullyClose) = abi.decode(data, (uint256, bool));
        uint256 repaymentNeeded = amount + premium;

        uint256 debt = variableDebtToken.balanceOf(address(this));
        uint256 debtBal = debtToken.balanceOf(address(this));
        uint256 toRepay = debt < debtBal ? debt : debtBal;
        if (toRepay > 0) {
            _ensureApprove(address(debtToken), address(aavePool), toRepay);
            aavePool.repay(address(debtToken), toRepay, VARIABLE_RATE_MODE, address(this));
        }

        uint256 residualDebt = variableDebtToken.balanceOf(address(this));
        if (residualDebt > 0) {
            uint256 remaining = debtToken.balanceOf(address(this));
            if (remaining >= residualDebt) {
                _ensureApprove(address(debtToken), address(aavePool), residualDebt);
                aavePool.repay(address(debtToken), residualDebt, VARIABLE_RATE_MODE, address(this));
            }
        }

        if (fullyClose) {
            aavePool.withdraw(address(collateralToken), type(uint256).max, address(this));
        } else if (collateralNeeded > 0) {
            aavePool.withdraw(address(collateralToken), collateralNeeded, address(this));
        }

        uint256 debtAvailable = debtToken.balanceOf(address(this));
        if (debtAvailable < repaymentNeeded) {
            if (address(swapper) == address(0)) revert InvalidAddress();
            uint256 shortfall = repaymentNeeded - debtAvailable;
            uint256 collateralQuote = _getDebtValue(shortfall);
            uint256 collateralNeededForSwap =
                (collateralQuote * SWAP_AMOUNT_BUFFER) / 100 + DUST_BUFFER;
            uint256 collateralBal = collateralToken.balanceOf(address(this));
            uint256 toSwap =
                collateralNeededForSwap < collateralBal ? collateralNeededForSwap : collateralBal;
            if (toSwap > 0) {
                uint256 debtBefore = debtToken.balanceOf(address(this));
                uint256 expectedDebt = (_getCollateralValue(toSwap) * MIN_SWAP_OUT_BPS) / 10000;
                collateralToken.safeTransfer(address(swapper), toSwap);
                swapper.swapCollateralForDebt(toSwap);
                uint256 debtAfter = debtToken.balanceOf(address(this));
                if (debtAfter <= debtBefore) revert SwapperUnderperformed(expectedDebt, 0);
                uint256 debtDelta = debtAfter - debtBefore;
                if (debtDelta < expectedDebt) revert SwapperUnderperformed(expectedDebt, debtDelta);
            }

            debtAvailable = debtToken.balanceOf(address(this));
            if (debtAvailable < repaymentNeeded) {
                uint256 remainingCollateral = collateralToken.balanceOf(address(this));
                if (remainingCollateral > 0) {
                    uint256 debtBefore = debtToken.balanceOf(address(this));
                    uint256 expectedDebt = (_getCollateralValue(remainingCollateral) * MIN_SWAP_OUT_BPS)
                        / 10000;
                    collateralToken.safeTransfer(address(swapper), remainingCollateral);
                    swapper.swapCollateralForDebt(remainingCollateral);
                    uint256 debtAfter = debtToken.balanceOf(address(this));
                    if (debtAfter <= debtBefore) revert SwapperUnderperformed(expectedDebt, 0);
                    uint256 debtDelta = debtAfter - debtBefore;
                    if (debtDelta < expectedDebt) revert SwapperUnderperformed(expectedDebt, debtDelta);
                }
            }
        }

        if (debtToken.balanceOf(address(this)) < repaymentNeeded) {
            revert InsufficientFlashloanRepayment();
        }

        _ensureApprove(address(debtToken), address(aavePool), repaymentNeeded);
        return true;
    }

    /// @notice Propose a new swapper (requires timelock)
    function proposeSwapper(address newSwapper) external onlyVault {
        if (newSwapper == address(0)) revert InvalidAddress();
        _swapperTimelock.proposeAddress(newSwapper, TIMELOCK_DELAY);
        emit SwapperChangeProposed(address(swapper), newSwapper, _swapperTimelock.timestamp);
    }

    /// @notice Execute pending swapper change after timelock
    function executeSwapper() external onlyVault {
        address oldSwapper = address(swapper);
        swapper = ISwapper(_swapperTimelock.executeAddress());
        emit SwapperUpdated(oldSwapper, address(swapper));
    }

    /// @notice Cancel pending swapper change
    function cancelSwapper() external onlyVault {
        address cancelled = _swapperTimelock.cancelAddress();
        emit SwapperChangeCancelled(cancelled);
    }

    // ============ View Functions ============

    function getCurrentLTV() external view returns (uint256 ltv) {
        uint256 collateral = aToken.balanceOf(address(this));
        uint256 debt = variableDebtToken.balanceOf(address(this));
        if (collateral == 0 || debt == 0) return 0;

        uint256 collateralValue = _getCollateralValue(collateral);
        if (collateralValue == 0) return 0;
        ltv = (debt * PRECISION) / collateralValue;
    }

    function getCurrentCollateral() external view returns (uint256 collateral) {
        return aToken.balanceOf(address(this));
    }

    function getCurrentDebt() external view returns (uint256 debt) {
        return variableDebtToken.balanceOf(address(this));
    }

    function collateralAsset() external view returns (address) {
        return address(collateralToken);
    }

    function debtAsset() external view returns (address) {
        return address(debtToken);
    }

    function getHealth() external view returns (int256 health) {
        uint256 collateral = aToken.balanceOf(address(this));
        uint256 debt = variableDebtToken.balanceOf(address(this));
        if (collateral == 0 && debt == 0) return type(int256).max;

        uint256 collateralUsd = _getCollateralUsdValue(collateral);
        uint256 debtUsd = _getDebtUsdValue(debt);
        if (debtUsd == 0) return type(int256).max;

        uint256 hf = (collateralUsd * liquidationThresholdBps * 1e14) / debtUsd;
        return int256(hf);
    }

    function loanExists() external view returns (bool exists) {
        return aToken.balanceOf(address(this)) > 0 || variableDebtToken.balanceOf(address(this)) > 0;
    }

    function getCollateralValue(uint256 collateralAmount) external view returns (uint256 value) {
        return _getCollateralValue(collateralAmount);
    }

    function getDebtValue(uint256 debtAmount) external view returns (uint256 value) {
        return _getDebtValue(debtAmount);
    }

    function calculateBorrowAmount(uint256 collateral, uint256 targetLtv)
        external
        view
        returns (uint256 borrowAmount)
    {
        uint256 collateralValue = _getCollateralValue(collateral);
        return (collateralValue * targetLtv) / PRECISION;
    }

    function healthCalculator(int256 dCollateral, int256 dDebt)
        external
        view
        returns (int256 health)
    {
        uint256 collateral = aToken.balanceOf(address(this));
        uint256 debt = variableDebtToken.balanceOf(address(this));

        if (dCollateral < 0) {
            collateral -= uint256(-dCollateral);
        } else {
            collateral += uint256(dCollateral);
        }

        if (dDebt < 0) {
            debt -= uint256(-dDebt);
        } else {
            debt += uint256(dDebt);
        }

        if (debt == 0) return type(int256).max;

        uint256 collateralUsd = _getCollateralUsdValue(collateral);
        uint256 debtUsd = _getDebtUsdValue(debt);
        if (debtUsd == 0) return type(int256).max;

        uint256 hf = (collateralUsd * liquidationThresholdBps * 1e14) / debtUsd;
        return int256(hf);
    }

    function minCollateral(uint256 debt_, uint256) external view returns (uint256) {
        if (debt_ == 0) return 0;
        uint256 debtInCollateral = _getDebtValue(debt_);
        return (debtInCollateral * AAVE_LTV_SCALE + maxLtvBps - 1) / maxLtvBps;
    }

    function getPositionValues()
        external
        view
        returns (uint256 collateralValue, uint256 debtValue)
    {
        collateralValue = aToken.balanceOf(address(this));
        debtValue = variableDebtToken.balanceOf(address(this));
    }

    function getNetCollateralValue() external view returns (uint256 value) {
        uint256 collateral = aToken.balanceOf(address(this));
        uint256 debt = variableDebtToken.balanceOf(address(this));
        if (collateral == 0) return 0;

        uint256 debtInCollateral = _getDebtValue(debt);
        return collateral > debtInCollateral ? collateral - debtInCollateral : 0;
    }

    function checkOracleFreshness() external view {
        _checkOracleFreshness();
    }

    // ============ Token Management ============

    function transferCollateral(address to, uint256 amount) external onlyVault {
        if (to == address(0)) revert InvalidAddress();
        uint256 idle = collateralToken.balanceOf(address(this));
        if (idle >= amount) {
            if (!collateralToken.transfer(to, amount)) revert TransferFailed();
            return;
        }
        if (idle > 0) {
            if (!collateralToken.transfer(to, idle)) revert TransferFailed();
            amount -= idle;
        }
        if (amount > 0) {
            aavePool.withdraw(address(collateralToken), amount, to);
        }
    }

    function transferDebt(address to, uint256 amount) external onlyVault {
        if (to == address(0)) revert InvalidAddress();
        debtToken.safeTransfer(to, amount);
    }

    function getCollateralBalance() external view returns (uint256 balance) {
        return collateralToken.balanceOf(address(this));
    }

    function getDebtBalance() external view returns (uint256 balance) {
        return debtToken.balanceOf(address(this));
    }

    // ============ Internal Helpers ============

    function _ensureApprove(address token, address spender, uint256 amount) internal {
        IERC20(token).ensureApproval(spender, amount);
    }

    function _isDustDebt(uint256 debt) internal pure returns (bool) {
        return debt <= DUST_THRESHOLD;
    }

    function _checkOracleFreshness() internal view {
        OracleLib.checkOracleFreshness(
            collateralOracle, MAX_ORACLE_STALENESS, debtOracle, MAX_DEBT_ORACLE_STALENESS
        );
    }

    function _getCollateralValue(uint256 collateralAmount) internal view returns (uint256) {
        return OracleLib.getCollateralValue(
            collateralAmount,
            collateralOracle,
            MAX_ORACLE_STALENESS,
            debtOracle,
            MAX_DEBT_ORACLE_STALENESS,
            collateralToken,
            debtToken
        );
    }

    function _getDebtValue(uint256 debtAmount) internal view returns (uint256) {
        return OracleLib.getDebtValue(
            debtAmount,
            collateralOracle,
            MAX_ORACLE_STALENESS,
            debtOracle,
            MAX_DEBT_ORACLE_STALENESS,
            collateralToken,
            debtToken
        );
    }

    function _getCollateralUsdValue(uint256 collateralAmount) internal view returns (uint256) {
        return OracleLib.getCollateralUsdValue(
            collateralAmount, collateralOracle, MAX_ORACLE_STALENESS, collateralToken
        );
    }

    function _getDebtUsdValue(uint256 debtAmount) internal view returns (uint256) {
        return OracleLib.getDebtUsdValue(
            debtAmount, debtOracle, MAX_DEBT_ORACLE_STALENESS, debtToken
        );
    }
}
IERC20.sol 16 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title IERC20
/// @notice Standard ERC20 interface
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function decimals() external view returns (uint8);
    function symbol() external view returns (string memory);
    function name() external view returns (string memory);
}
IChainlinkOracle.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title IChainlinkOracle
/// @notice Interface for Chainlink price feeds
interface IChainlinkOracle {
    /// @notice Get the latest round data
    /// @return roundId The round ID
    /// @return answer The price answer
    /// @return startedAt Timestamp when the round started
    /// @return updatedAt Timestamp when the round was updated
    /// @return answeredInRound The round in which the answer was computed
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );

    /// @notice Get decimals of the price feed
    /// @return Decimals
    function decimals() external view returns (uint8);

    /// @notice Get description of the price feed
    /// @return Description string
    function description() external view returns (string memory);

    /// @notice Get the latest answer
    /// @return Latest price
    function latestAnswer() external view returns (int256);
}
IFlashLoanSimpleReceiver.sol 12 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

interface IFlashLoanSimpleReceiver {
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}
ILoanManager.sol 149 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ILoanManager
/// @notice Interface for loan managers that handle collateralized borrowing
interface ILoanManager {
    // ============ Events ============
    event LoanCreated(uint256 collateral, uint256 debt, uint256 bands);
    event CollateralAdded(uint256 amount);
    event LoanBorrowedMore(uint256 collateral, uint256 debt);
    event LoanRepaid(uint256 amount);
    event CollateralRemoved(uint256 amount);

    // ============ Errors ============
    error Unauthorized();
    error InvalidAddress();
    error InvalidPrice();
    error StaleOracle();
    error DebtNotFullyRepaid();
    error ZeroAmount();
    error TransferFailed();

    // ============ Loan Management Functions ============

    /// @notice Create a new loan with collateral
    /// @param collateral Amount of collateral asset
    /// @param debt Amount of debt asset to borrow
    /// @param bands Number of bands for the loan (if applicable)
    function createLoan(uint256 collateral, uint256 debt, uint256 bands) external;

    /// @notice Add collateral to an existing loan
    /// @param collateral Amount of collateral asset
    function addCollateral(uint256 collateral) external;

    /// @notice Borrow more against existing collateral
    /// @param collateral Additional collateral (can be 0)
    /// @param debt Additional debt to borrow
    function borrowMore(uint256 collateral, uint256 debt) external;

    /// @notice Repay debt
    /// @param amount Amount of debt asset to repay
    function repayDebt(uint256 amount) external;

    /// @notice Remove collateral from the loan
    /// @param amount Amount of collateral asset to remove
    function removeCollateral(uint256 amount) external;

    /// @notice Unified position unwind for both partial and full withdrawals
    /// @dev Pass type(uint256).max for collateralNeeded to fully close the position.
    /// @param collateralNeeded Amount of collateral to free, or type(uint256).max for full close
    function unwindPosition(uint256 collateralNeeded) external;

    // ============ View Functions ============

    /// @notice Get current LTV ratio
    /// @return ltv Current LTV (1e18 = 100%)
    function getCurrentLTV() external view returns (uint256 ltv);

    /// @notice Get current collateral amount
    /// @return collateral Current collateral amount
    function getCurrentCollateral() external view returns (uint256 collateral);

    /// @notice Get current debt amount
    /// @return debt Current debt amount
    function getCurrentDebt() external view returns (uint256 debt);

    /// @notice Collateral asset used by this loan manager
    function collateralAsset() external view returns (address);

    /// @notice Debt asset used by this loan manager
    function debtAsset() external view returns (address);

    /// @notice Get vault health factor
    /// @return health Health factor (positive = healthy)
    function getHealth() external view returns (int256 health);

    /// @notice Check if loan exists
    /// @return exists True if loan exists
    function loanExists() external view returns (bool exists);

    /// @notice Get collateral value in debt asset terms
    /// @param collateralAmount Amount of collateral asset
    /// @return value Value in debt asset (18 decimals unless specified by implementation)
    function getCollateralValue(uint256 collateralAmount) external view returns (uint256 value);

    /// @notice Get debt asset value in collateral terms
    /// @param debtAmount Amount of debt asset
    /// @return value Value in collateral asset (collateral decimals)
    function getDebtValue(uint256 debtAmount) external view returns (uint256 value);

    /// @notice Calculate borrow amount for given collateral and target LTV
    /// @param collateral Amount of collateral asset
    /// @param targetLtv Target LTV (1e18 = 100%)
    /// @return borrowAmount Amount of debt asset to borrow
    function calculateBorrowAmount(uint256 collateral, uint256 targetLtv)
        external
        view
        returns (uint256 borrowAmount);

    /// @notice Calculate health after hypothetical changes
    /// @param dCollateral Change in collateral
    /// @param dDebt Change in debt
    /// @return health Projected health factor
    function healthCalculator(int256 dCollateral, int256 dDebt)
        external
        view
        returns (int256 health);

    /// @notice Get minimum collateral required for a debt amount
    /// @param debt_ Desired debt amount
    /// @param bands Number of bands
    /// @return Minimum collateral required
    function minCollateral(uint256 debt_, uint256 bands) external view returns (uint256);

    /// @notice Get position values (collateral and debt)
    /// @return collateralValue Current collateral value
    /// @return debtValue Current debt value
    function getPositionValues()
        external
        view
        returns (uint256 collateralValue, uint256 debtValue);

    /// @notice Get net collateral value (collateral - debt in collateral terms)
    /// @return value Net value in collateral asset
    function getNetCollateralValue() external view returns (uint256 value);

    /// @notice Check oracle freshness
    function checkOracleFreshness() external view;

    // ============ Token Management ============

    /// @notice Transfer collateral from this contract
    /// @param to Recipient address
    /// @param amount Amount to transfer
    function transferCollateral(address to, uint256 amount) external;

    /// @notice Transfer debt asset from this contract
    /// @param to Recipient address
    /// @param amount Amount to transfer
    function transferDebt(address to, uint256 amount) external;

    /// @notice Get collateral balance of this contract
    /// @return balance Collateral balance
    function getCollateralBalance() external view returns (uint256 balance);

    /// @notice Get debt balance of this contract
    /// @return balance Debt balance
    function getDebtBalance() external view returns (uint256 balance);
}
ISwapper.sol 29 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ISwapper
/// @notice Optional swapper for collateral/debt conversions
interface ISwapper {
    error SlippageExceeded();
    error InvalidAddress();
    error TransferFailed();

    /// @notice Quote collateral needed for a given debt amount
    /// @param debtAmount Amount of debt asset
    /// @return collateralNeeded Estimated collateral required
    function quoteCollateralForDebt(uint256 debtAmount) external view returns (uint256);

    /// @notice Swap collateral for debt asset
    /// @param collateralAmount Amount of collateral to swap
    /// @return debtReceived Amount of debt asset received
    function swapCollateralForDebt(uint256 collateralAmount)
        external
        returns (uint256 debtReceived);

    /// @notice Swap debt asset for collateral
    /// @param debtAmount Amount of debt asset to swap
    /// @return collateralReceived Amount of collateral received
    function swapDebtForCollateral(uint256 debtAmount)
        external
        returns (uint256 collateralReceived);
}
SafeTransferLib.sol 57 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

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

/// @title SafeTransferLib
/// @notice Gas-efficient safe transfer functions for ERC20 tokens
library SafeTransferLib {
    error TransferFailed();

    /// @notice Safe transfer that reverts on failure
    function safeTransfer(IERC20 token, address to, uint256 amount) internal {
        (bool success, bytes memory data) =
            address(token).call(abi.encodeWithSelector(token.transfer.selector, to, amount));
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }

    /// @notice Safe transferFrom that reverts on failure
    function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
        (bool success, bytes memory data) = address(token).call(
            abi.encodeWithSelector(token.transferFrom.selector, from, to, amount)
        );
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }

    /// @notice Ensure approval is sufficient, set to max if not
    /// @dev Uses safeApprove pattern for USDT compatibility (non-standard ERC20)
    function ensureApproval(IERC20 token, address spender, uint256 amount) internal {
        if (token.allowance(address(this), spender) < amount) {
            safeApprove(token, spender, type(uint256).max);
        }
    }

    /// @notice Safe approve that handles non-standard ERC20 tokens like USDT
    /// @dev USDT requires setting allowance to 0 first before setting a new non-zero value
    ///      and returns void instead of bool
    function safeApprove(IERC20 token, address spender, uint256 amount) internal {
        // For USDT: first reset to 0 if there's existing allowance and we're setting non-zero
        if (amount > 0 && token.allowance(address(this), spender) > 0) {
            (bool resetSuccess,) =
                address(token).call(abi.encodeWithSelector(token.approve.selector, spender, 0));
            // Ignore return value for void-returning tokens like USDT
            if (!resetSuccess) revert TransferFailed();
        }

        (bool success, bytes memory data) =
            address(token).call(abi.encodeWithSelector(token.approve.selector, spender, amount));
        // Handle both standard (returns bool) and non-standard (returns nothing) ERC20
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }
}
TimelockLib.sol 94 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title TimelockLib
/// @notice Library for managing timelocked parameter changes
library TimelockLib {
    struct TimelockData {
        uint256 pendingValue;
        uint256 timestamp;
    }

    /// @notice Expiry window after timelock becomes ready (7 days)
    uint256 internal constant TIMELOCK_EXPIRY = 7 days;

    error NoTimelockPending();
    error TimelockNotReady();
    error TimelockExpired();

    /// @notice Propose a new value with timelock delay
    function propose(TimelockData storage data, uint256 newValue, uint256 delay) internal {
        data.pendingValue = newValue;
        data.timestamp = block.timestamp + delay;
    }

    /// @notice Execute pending value change after timelock
    /// @return newValue The new value that was set
    function execute(TimelockData storage data) internal returns (uint256 newValue) {
        if (data.timestamp == 0) revert NoTimelockPending();
        if (block.timestamp < data.timestamp) revert TimelockNotReady();
        if (block.timestamp > data.timestamp + TIMELOCK_EXPIRY) revert TimelockExpired();

        newValue = data.pendingValue;
        data.pendingValue = 0;
        data.timestamp = 0;
    }

    /// @notice Cancel pending value change
    /// @return cancelledValue The value that was cancelled
    function cancel(TimelockData storage data) internal returns (uint256 cancelledValue) {
        if (data.timestamp == 0) revert NoTimelockPending();
        cancelledValue = data.pendingValue;
        data.pendingValue = 0;
        data.timestamp = 0;
    }

    /// @notice Check if timelock is pending
    function isPending(TimelockData storage data) internal view returns (bool) {
        return data.timestamp != 0;
    }

    /// @notice Check if timelock is ready to execute
    function isReady(TimelockData storage data) internal view returns (bool) {
        return data.timestamp != 0 && block.timestamp >= data.timestamp;
    }

    // ============ Address Timelock Functions ============

    struct AddressTimelockData {
        address pendingValue;
        uint256 timestamp;
    }

    /// @notice Propose a new address value with timelock delay
    function proposeAddress(AddressTimelockData storage data, address newValue, uint256 delay)
        internal
    {
        data.pendingValue = newValue;
        data.timestamp = block.timestamp + delay;
    }

    /// @notice Execute pending address change after timelock
    /// @return newValue The new address that was set
    function executeAddress(AddressTimelockData storage data) internal returns (address newValue) {
        if (data.timestamp == 0) revert NoTimelockPending();
        if (block.timestamp < data.timestamp) revert TimelockNotReady();
        if (block.timestamp > data.timestamp + TIMELOCK_EXPIRY) revert TimelockExpired();

        newValue = data.pendingValue;
        data.pendingValue = address(0);
        data.timestamp = 0;
    }

    /// @notice Cancel pending address change
    /// @return cancelledValue The address that was cancelled
    function cancelAddress(AddressTimelockData storage data)
        internal
        returns (address cancelledValue)
    {
        if (data.timestamp == 0) revert NoTimelockPending();
        cancelledValue = data.pendingValue;
        data.pendingValue = address(0);
        data.timestamp = 0;
    }
}
OracleLib.sol 146 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

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

/// @title OracleLib
/// @notice Unified Chainlink oracle helpers for both Aave and Llama loan managers
library OracleLib {
    error InvalidPrice();
    error StaleOracle();

    function _validatedPrice(IChainlinkOracle oracle, uint256 maxStaleness)
        private
        view
        returns (uint256)
    {
        (uint80 roundId, int256 price,, uint256 updatedAt, uint80 answeredInRound) =
            oracle.latestRoundData();
        if (price <= 0) revert InvalidPrice();
        if (answeredInRound < roundId) revert StaleOracle();
        if (block.timestamp - updatedAt > maxStaleness) revert StaleOracle();
        return uint256(price);
    }

    function checkOracleFreshness(
        IChainlinkOracle collateralOracle,
        uint256 maxCollateralStaleness,
        IChainlinkOracle debtOracle,
        uint256 maxDebtStaleness
    ) external view {
        _validatedPrice(collateralOracle, maxCollateralStaleness);
        _validatedPrice(debtOracle, maxDebtStaleness);
    }

    // ============ Aave overloads (both token decimals explicit) ============

    function getCollateralValue(
        uint256 collateralAmount,
        IChainlinkOracle collateralOracle,
        uint256 maxCollateralStaleness,
        IChainlinkOracle debtOracle,
        uint256 maxDebtStaleness,
        IERC20 collateralToken,
        IERC20 debtToken
    ) external view returns (uint256) {
        if (collateralAmount == 0) return 0;
        uint256 collateralPrice = _validatedPrice(collateralOracle, maxCollateralStaleness);
        uint256 debtPrice = _validatedPrice(debtOracle, maxDebtStaleness);
        uint8 collateralOracleDecimals = collateralOracle.decimals();
        uint8 debtOracleDecimals = debtOracle.decimals();
        uint8 collateralDecimals = collateralToken.decimals();
        uint8 debtDecimals = debtToken.decimals();
        return (
            collateralAmount * collateralPrice * (10 ** debtDecimals) * (10 ** debtOracleDecimals)
        ) / ((10 ** collateralOracleDecimals) * (10 ** collateralDecimals) * debtPrice);
    }

    function getDebtValue(
        uint256 debtAmount,
        IChainlinkOracle collateralOracle,
        uint256 maxCollateralStaleness,
        IChainlinkOracle debtOracle,
        uint256 maxDebtStaleness,
        IERC20 collateralToken,
        IERC20 debtToken
    ) external view returns (uint256) {
        if (debtAmount == 0) return 0;
        uint256 collateralPrice = _validatedPrice(collateralOracle, maxCollateralStaleness);
        uint256 debtPrice = _validatedPrice(debtOracle, maxDebtStaleness);
        uint8 collateralOracleDecimals = collateralOracle.decimals();
        uint8 debtOracleDecimals = debtOracle.decimals();
        uint8 collateralDecimals = collateralToken.decimals();
        uint8 debtDecimals = debtToken.decimals();
        return (
            debtAmount * debtPrice * (10 ** collateralOracleDecimals) * (10 ** collateralDecimals)
        ) / ((10 ** debtOracleDecimals) * collateralPrice * (10 ** debtDecimals));
    }

    // ============ Llama overloads (debt is 1e18 native) ============

    function getCollateralValue(
        uint256 collateralAmount,
        IChainlinkOracle collateralOracle,
        uint256 maxCollateralStaleness,
        IChainlinkOracle debtOracle,
        uint256 maxDebtStaleness,
        IERC20 collateralToken
    ) external view returns (uint256) {
        if (collateralAmount == 0) return 0;
        uint256 collateralPrice = _validatedPrice(collateralOracle, maxCollateralStaleness);
        uint256 debtPrice = _validatedPrice(debtOracle, maxDebtStaleness);
        uint8 collateralOracleDecimals = collateralOracle.decimals();
        uint8 debtOracleDecimals = debtOracle.decimals();
        uint8 collateralDecimals = collateralToken.decimals();
        return (collateralAmount * collateralPrice * 1e18 * (10 ** debtOracleDecimals))
            / ((10 ** collateralOracleDecimals) * (10 ** collateralDecimals) * debtPrice);
    }

    function getDebtValue(
        uint256 debtAmount,
        IChainlinkOracle collateralOracle,
        uint256 maxCollateralStaleness,
        IChainlinkOracle debtOracle,
        uint256 maxDebtStaleness,
        IERC20 collateralToken
    ) external view returns (uint256) {
        if (debtAmount == 0) return 0;
        uint256 collateralPrice = _validatedPrice(collateralOracle, maxCollateralStaleness);
        uint256 debtPrice = _validatedPrice(debtOracle, maxDebtStaleness);
        uint8 collateralOracleDecimals = collateralOracle.decimals();
        uint8 debtOracleDecimals = debtOracle.decimals();
        uint8 collateralDecimals = collateralToken.decimals();
        return (
            debtAmount * debtPrice * (10 ** collateralOracleDecimals) * (10 ** collateralDecimals)
        ) / ((10 ** debtOracleDecimals) * collateralPrice * 1e18);
    }

    // ============ USD value helpers (Aave only) ============

    function getCollateralUsdValue(
        uint256 collateralAmount,
        IChainlinkOracle collateralOracle,
        uint256 maxCollateralStaleness,
        IERC20 collateralToken
    ) external view returns (uint256) {
        if (collateralAmount == 0) return 0;
        uint256 price = _validatedPrice(collateralOracle, maxCollateralStaleness);
        uint8 oracleDecimals = collateralOracle.decimals();
        uint8 tokenDecimals = collateralToken.decimals();
        return (collateralAmount * price * 1e18) / ((10 ** oracleDecimals) * (10 ** tokenDecimals));
    }

    function getDebtUsdValue(
        uint256 debtAmount,
        IChainlinkOracle debtOracle,
        uint256 maxDebtStaleness,
        IERC20 debtToken
    ) external view returns (uint256) {
        if (debtAmount == 0) return 0;
        uint256 price = _validatedPrice(debtOracle, maxDebtStaleness);
        uint8 oracleDecimals = debtOracle.decimals();
        uint8 tokenDecimals = debtToken.decimals();
        return (debtAmount * price * 1e18) / ((10 ** oracleDecimals) * (10 ** tokenDecimals));
    }
}

Read Contract

AAVE_LTV_SCALE 0xeea6d43a → uint256
AAVE_REFERRAL_CODE 0x05a363de → uint16
DUST_BUFFER 0x7d41e551 → uint256
DUST_THRESHOLD 0xdd7dc9a9 → uint256
MAX_DEBT_ORACLE_STALENESS 0x18c9a7f1 → uint256
MAX_ORACLE_STALENESS 0x5dc565c2 → uint256
MIN_HEALTH 0x68cf06e4 → int256
MIN_SWAP_OUT_BPS 0x302cdc4a → uint256
PRECISION 0xaaf5eb68 → uint256
SWAP_AMOUNT_BUFFER 0xf0055bed → uint256
TIMELOCK_DELAY 0x5ba1c1a9 → uint256
VARIABLE_RATE_MODE 0x20cd116f → uint256
aToken 0xa0c1f15e → address
aavePool 0xa03e4bc3 → address
calculateBorrowAmount 0xd3205c11 → uint256
checkOracleFreshness 0x8bb32711
collateralAsset 0xaabaecd6 → address
collateralOracle 0xa217d5ce → address
collateralToken 0xb2016bd4 → address
debtAsset 0xa919802d → address
debtOracle 0x38f5847a → address
debtToken 0xf8d89898 → address
getCollateralBalance 0x1529a639 → uint256
getCollateralValue 0x82fee08f → uint256
getCurrentCollateral 0x59bfe9f7 → uint256
getCurrentDebt 0xccee305e → uint256
getCurrentLTV 0xe8621149 → uint256
getDebtBalance 0x5ccbbdf1 → uint256
getDebtValue 0xc0177bd5 → uint256
getHealth 0x640e7fd5 → int256
getNetCollateralValue 0x03011dab → uint256
getPositionValues 0x36d2b5aa → uint256, uint256
healthCalculator 0x76f02296 → int256
initializer 0x9ce110d7 → address
liquidationThresholdBps 0xe4864731 → uint256
loanExists 0xd3286d71 → bool
maxLtvBps 0xea7f52d1 → uint256
minCollateral 0x42f8b502 → uint256
swapper 0x2b3297f9 → address
variableDebtToken 0x915541f5 → address
vault 0xfbfa77cf → address

Write Contract 13 functions

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

addCollateral 0xbcc46e83
uint256 collateral
borrowMore 0x4b836fb0
uint256 collateral
uint256 debt
cancelSwapper 0xda2635d3
No parameters
createLoan 0x904b513b
uint256 collateral
uint256 debt
uint256
executeOperation 0x1b11d0ff
address asset_
uint256 amount
uint256 premium
address initiator
bytes data
returns: bool
executeSwapper 0xbd1f10aa
No parameters
initializeVault 0xccb6a270
address _vault
proposeSwapper 0xc24506f9
address newSwapper
removeCollateral 0x3237c158
uint256 amount
repayDebt 0x6b09de45
uint256 amount
transferCollateral 0x816b1e8f
address to
uint256 amount
transferDebt 0x74692c25
address to
uint256 amount
unwindPosition 0x37058749
uint256 collateralNeeded

Recent Transactions

This address has 1 on-chain transactions, but only 0% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →