Cryo Explorer Ethereum Mainnet

Address Contract Partially Verified

Address 0x1C91da0223c763d2e0173243eAdaA0A2ea47E704
Balance 0 ETH
Nonce 1
Code Size 22533 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

22533 bytes
0x6003361161000c576131ba565b5f3560e01c6323cfed03811861005257606436106156b4575f546002146156b45760025f55346102c052606060046102e03760016103405261004c61424e565b60035f55005b63bc61ea23811861023f5760c436106156b4576064358060a01c6156b4576106405260843560040160058135116156b4578035602082018160051b8082610680375050806106605250505f546002146156b45760025f5560206156e55f395f5163a9059cbb610720526106405161074052602435610760526020610720604461073c5f855af16100e4573d5f5f3e3d5ffd5b60203d106156b457610720518060011c6156b457610780526107805050610640516040527fe62214fe00000000000000000000000000000000000000000000000000000000606052336080525f60a0526040600460c037610660518060051b806101208261068060045afa5050806101005250610162610740613f23565b61074060408101905051610720525f6102c052600435610720518082018281106156b457905090506102e05260406024610300375f610340526101a361424e565b600435604052346060526101b5613c80565b60206157055f395f516323b872dd61074052610640516107605260206157455f395f5161078052610720516107a0526020610740606461075c5f855af16101fe573d5f5f3e3d5ffd5b3d61021557803b156156b45760016107c05261022e565b60203d106156b457610740518060011c6156b4576107c0525b6107c0905051156156b45760035f55005b636f972f12811861025c57602436106156b457336105205261027f565b6324049e5781186102d157604436106156b4576024358060a01c6156b457610520525b5f546002146156b45760025f55600435610298576102cb565b6004356102c0525f6102e05261052051610300525f610320526102b9614665565b600435604052346060526102cb613c80565b60035f55005b63dd171e7c81186103b457604436106156b4575f546002146156b45760025f556024356102fd576103ae565b604060046102c03733610300525f61032052610317614665565b68010000000000000006546024358082018281106156b457905090506801000000000000000655600435156103585760043560405234606052610358613c80565b60206156e55f395f5163a9059cbb610520523361054052602435610560526020610520604461053c5f855af1610390573d5f5f3e3d5ffd5b60203d106156b457610520518060011c6156b4576105805261058050505b60035f55005b635457ff7b81186103f257602436106156b4576004358060a01c6156b457604052346156b45760026040516020525f5260405f205460605260206060f35b63e1ec3c68811861042957602436106156b457346156b45760043567fffffffffffffffe81116156b4576005015460405260206040f35b637128f3b8811861046f57602436106156b4576004358060a01c6156b457604052346156b457680100000000000000046040516020525f5260405f205460605260206060f35b636cce39be811861049357346156b457680100000000000000055460405260206040f35b634f02c42081186104b757346156b457680100000000000000065460405260206040f35b63e231bff081186104db57346156b457680100000000000000075460405260206040f35b63adfae4ce81186104ff57346156b457680100000000000000085460405260206040f35b63627d2b83811861052357346156b457680100000000000000095460405260206040f35b635449b9cb811861054757346156b4576801000000000000000a5460405260206040f35b63c45a0155811861056957346156b45760206156c55f395f5160405260206040f35b632a943945811861058b57346156b45760206157455f395f5160405260206040f35b632621db2f81186105ad57346156b45760206157055f395f5160405260206040f35b639b6c56ec81186105f257602436106156b4576004358060a01c6156b45760c052346156b4575f546002146156b457602060c0516040526105ee60e0613443565b60e0f35b63a21adb9e811861063b57602436106156b4576004358060a01c6156b457604052346156b4575f546002146156b45760016040516020525f5260405f2054151560605260206060f35b6331dc3ca881186106c257346156b45760206157455f395f5163095a0fc6606052602060606004607c845afa610673573d5f5f3e3d5ffd5b60203d106156b45760609050516040526003546060526004546080526060516040518082028115838383041417156156b4579050905060805180156156b4578082049050905060a052602060a0f35b639a49719681186106df57604436106156b4575f6101e0526106fa565b631cf1f947811861083357606436106156b4576044356101e0525b346156b4575f546002146156b45760043560206157255f395f518082028115838383041417156156b457905090506040526024356060526801000000000000000a5460805261074a6102206134d7565b61022051610200526001670de0b6b3a76400006102005161076c610240613a90565b610240518082028115838383041417156156b4579050905004600181811860018311021890500361022052670de0b6b3a764000061022051670de05bc096e9c000810281670de05bc096e9c0008204186156b457905004610220526102205160206156e55f395f516370a082316102405230610260526020610240602461025c845afa6107fb573d5f5f3e3d5ffd5b60203d106156b4576102409050516101e0518082018281106156b4579050905080828118828410021890509050610280526020610280f35b63a7573206811861095e57604436106156b457346156b4575f546002146156b457670de05bc096e9c00060206157255f395f51600435670de0b6b3a7640000810281670de0b6b3a76400008204186156b45790506108926101e0613a90565b6101e05180156156b45780820490509050670de0b6b3a7640000810281670de0b6b3a76400008204186156b4579050670de0b6b3a76400006040526024356060526801000000000000000a546080526108ec6102006134d7565b6102005180156156b457808204905090506024356024356107d081018181106156b45790508082028115838383041417156156b457905090508082018281106156b4579050905004670de0b6b3a7640000810281670de0b6b3a76400008204186156b457905004610220526020610220f35b63720fb254811861099957606436106156b457346156b4575f546002146156b457602060606004610120376109946102c06135f8565b6102c0f35b63d14ff5b681186109b757602436106156b4576001610520526109da565b632e4af52a8118610a3657604436106156b4576024358060011c6156b457610520525b346156b4575f546002146156b45760025f556004356109f857610a30565b6004356102c0525f6102e0523361030052600161032052610a17614665565b3360405260043560605261052051608052610a30613dc9565b60035f55005b63371fd8e68118610a7e57602436106156b457336102c0527f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6102e052600161030052610b3d565b63acb708158118610ad057604436106156b4576024358060a01c6156b4576102c0527f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6102e052600161030052610b3d565b63b4440df48118610b0457606436106156b4576024358060a01c6156b4576102c0526044356102e052600161030052610b3d565b6337671f93811861120957608436106156b4576024358060a01c6156b4576102c0526044356102e0526064358060011c6156b457610300525b346156b4575f546002146156b45760025f55600435610b5b57611203565b604036610320376102c05160a052610b746103606133c5565b6103608051610320526020810151610340525061032051610bf4576012610360527f4c6f616e20646f65736e277420657869737400000000000000000000000000006103805261036050610360518061038001601f825f031636823750506308c379a061032052602061034052601f19601f61036051011660440161033cfd5b6103205160043580828118828410021890509050610360526103605161032051036103205261032051610d9b5760206157455f395f5163f3fef3a36103c0526102c0516103e052670de0b6b3a76400006104005260406103c060446103dc5f855af1610c62573d5f5f3e3d5ffd5b60403d106156b4576103c0905080516103805260208101516103a052506103805115610cf957336102c051186156b45760206156e55f395f516323b872dd6103c05260206157455f395f516103e0526102c05161040052610380516104205260206103c060646103dc5f855af1610cdb573d5f5f3e3d5ffd5b60203d106156b4576103c0518060011c6156b4576104405261044050505b6103a05115610d1f576102c0516040526103a05160605261030051608052610d1f613dc9565b6102c0517feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f260a0366103c03760a06103c0a26102c0517f77c6871227e5d2dec8dadd5354f78453203e22e669cd0ec4c19d9a8c5edb31d06103a0516103c052610360516103e05260406103c0a26102c05160405261110d614a92565b60206157455f395f5163c16ef2646103a05260206103a060046103bc845afa610dc6573d5f5f3e3d5ffd5b60203d106156b4576103a0905051610380526102e05161038051136156b45760206157455f395f5163b461100d6103e0526102c0516104005260406103e060246103fc845afa610e18573d5f5f3e3d5ffd5b60403d106156b4576103e0905080516103a05260208101516103c0525060016103a0516103c05103015f81126156b4576103e05260026102c0516020525f5260405f205461040052610380516103a05113610f1a576102c0517feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6104205261032051610440526103a051610460526103c05161048052610400516104a05260a0610420a26102c0517f77c6871227e5d2dec8dadd5354f78453203e22e669cd0ec4c19d9a8c5edb31d05f6104205261036051610440526040610420a26110d3565b60206157455f395f5163f3fef3a3610460526102c05161048052670de0b6b3a76400006104a0526040610460604461047c5f855af1610f5b573d5f5f3e3d5ffd5b60403d106156b457610460905080516104205260208101516104405250610440516101205261032051610140526103e05161016052610f9b6104806135f8565b6104805161046052610460516103a0516103c051038082018281125f8312186156b457905090506104805260206157455f395f5163ab047e006104a0526102c0516104c052610440516104e05261046051610500526104805161052052803b156156b4575f6104a060846104bc5f855af1611018573d5f5f3e3d5ffd5b50336102c05118611047576801000000000000000954610400526104005160026102c0516020525f5260405f20555b6102c0517feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f2610440516104a052610320516104c052610460516104e0526104805161050052610400516105205260a06104a0a26102c0517f77c6871227e5d2dec8dadd5354f78453203e22e669cd0ec4c19d9a8c5edb31d05f6104a052610360516104c05260406104a0a25b336102c0511461110d5760016102c051604052610320516060525f6080526104005160a052611103610420614b66565b61042051126156b4575b60206156e55f395f516323b872dd61038052336103a052306103c052610360516103e0526020610380606461039c5f855af161114b573d5f5f3e3d5ffd5b60203d106156b457610380518060011c6156b4576104005261040050506801000000000000000754610360518082018281106156b45790509050680100000000000000075560016102c0516020525f5260405f2061032051815561034051600182015550600354610340518082028115838383041417156156b4579050905060045480156156b45780820490509050610380526103605161038051610360518082811882841102189050905003600355610340516004555b60035f55005b63152f65cb811861199d57606436106156b4576004358060a01c6156b4576106405260243560040160058135116156b4578035602082018160051b808261068037505080610660525050346156b4575f546002146156b45760025f5560206157455f395f5163b461100d6107605233610780526040610760602461077c845afa611295573d5f5f3e3d5ffd5b60403d106156b45761076090508051610720526020810151610740525060206157455f395f5163f3fef3a36107a052336107c052670de0b6b3a76400006107e05260406107a060446107bc5f855af16112f0573d5f5f3e3d5ffd5b60403d106156b4576107a09050805161076052602081015161078052506040366107a0373360a0526113236107e06133c5565b6107e080516107a05260208101516107c0525060206157055f395f516323b872dd6107e05260206157455f395f51610800526106405161082052610780516108405260206107e060646107fc5f855af161137f573d5f5f3e3d5ffd5b3d61139657803b156156b4576001610860526113af565b60203d106156b4576107e0518060011c6156b457610860525b6108605050610640516040527fef67dc7400000000000000000000000000000000000000000000000000000000606052336080526107605160a0526107805160c0526107a05160e052610660518060051b806101208261068060045afa505080610100525061141f610840613f23565b61084080516107e0526020810151610800526040810151610820525061080051610760518082018281106156b457905090506108405261084051156156b4575f610860526107a0516108405110156116ba576001610720516107405103015f81126156b457610880526107e0516107205113156156b4576108005161086052610800516107a051036107a05261082051610120526107a0516101405261088051610160526114ce6108c06135f8565b6108c0516108a0526108a0516107205161074051038082018281125f8312186156b457905090506108c05260206157455f395f5163ab047e006108e052336109005261082051610920526108a051610940526108c05161096052803b156156b4575f6108e060846108fc5f855af1611548573d5f5f3e3d5ffd5b5068010000000000000009546108e0526108e0516002336020525f5260405f205560206157055f395f516323b872dd61090052610640516109205260206157455f395f516109405261082051610960526020610900606461091c5f855af16115b2573d5f5f3e3d5ffd5b3d6115c957803b156156b4576001610980526115e2565b60203d106156b457610900518060011c6156b457610980525b610980905051156156b45760206156e55f395f516323b872dd610900526106405161092052306109405261080051610960526020610900606461091c5f855af161162e573d5f5f3e3d5ffd5b60203d106156b457610900518060011c6156b457610980526109805050337feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f261082051610900526107a051610920526108a051610940526108c051610960526108e0516109805260a0610900a261078051610820518082038281116156b45790509050610780526118c8565b6107a051610860525f6107a052336040526116d3614a92565b610800511561173b5760206156e55f395f516323b872dd61088052610640516108a052306108c052610800516108e0526020610880606461089c5f855af161171d573d5f5f3e3d5ffd5b60203d106156b457610880518060011c6156b4576109005261090050505b61076051156117a85760206156e55f395f516323b872dd6108805260206157455f395f516108a052306108c052610760516108e0526020610880606461089c5f855af161178a573d5f5f3e3d5ffd5b60203d106156b457610880518060011c6156b4576109005261090050505b610860516108405111156118125760206156e55f395f5163a9059cbb61088052336108a0526108605161084051036108c0526020610880604461089c5f855af16117f4573d5f5f3e3d5ffd5b60203d106156b457610880518060011c6156b4576108e0526108e050505b61082051156118985760206157055f395f516323b872dd61088052610640516108a052336108c052610820516108e0526020610880606461089c5f855af161185c573d5f5f3e3d5ffd5b3d61187357803b156156b45760016109005261188c565b60203d106156b457610880518060011c6156b457610900525b610900905051156156b4575b337feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f260a0366108803760a0610880a25b337f77c6871227e5d2dec8dadd5354f78453203e22e669cd0ec4c19d9a8c5edb31d06107805161088052610860516108a0526040610880a26801000000000000000754610860518082018281106156b4579050905068010000000000000007556001336020525f5260405f206107a05181556107c0516001820155506003546107c0518082028115838383041417156156b4579050905060045480156156b457808204905090506108805261086051610880516108605180828118828411021890509050036003556107c05160045560035f55005b630b8db68181186119ba57608436106156b4575f610300526119d5565b6322c714538118611f365760a436106156b457608435610300525b6004358060a01c6156b4576102c0526064358060011c6156b4576102e052346156b4575f546002146156b45760206157455f395f5163b461100d610360526102c051610380526040610360602461037c845afa611a34573d5f5f3e3d5ffd5b60403d106156b4576103609050805161032052602081015161034052506102c051604052611a63610380613443565b610380518060ff1c6156b4576103605261030051610380525f6103a0526103605115611ac15760026102c0516020525f5260405f20548060ff1c6156b4576103a0526001610320516103405103015f81126156b45761038052611afe565b68010000000000000009548060ff1c6156b4576103a0527f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610320525b6060366103c037610360516044358082018281125f8312186156b45790509050610360526001610360511215611b93576011610420527f4e6f6e2d706f73697469766520646562740000000000000000000000000000006104405261042050610420518061044001601f825f031636823750506308c379a06103e052602061040052601f19601f6104205101166044016103fcfd5b60206157455f395f5163c16ef264610440526020610440600461045c845afa611bbe573d5f5f3e3d5ffd5b60203d106156b45761044090505161042052610420516103205113611c5557610320516103c05260206157455f395f516362ca4b18610440526102c051610460526020610440602461045c845afa611c18573d5f5f3e3d5ffd5b60203d106156b457610440905051670de0b6b3a7640000810281670de0b6b3a76400008204186156b45790508060ff1c6156b45761040052611d32565b60206157455f395f5163544fb5c1610440526102c051610460526040610440602461045c845afa611c88573d5f5f3e3d5ffd5b60403d106156b4576104409050602081019050518060ff1c6156b4576024358082018281125f8312186156b457905090506103e0526103e0515f81126156b45761012052610360515f81126156b457610140526103805161016052611cee6104406135f8565b610440516103c0526103e05160206157255f395f518060ff1c6156b45780820281191515600160ff1b84141517821584848405141716156156b457905090506103e0525b60206157455f395f51632eb858e7610460526103c051610480526020610460602461047c845afa611d65573d5f5f3e3d5ffd5b60203d106156b4576104609050518060ff1c6156b4576104405261042051610320511315611de8576103e0515f81126156b457604052610380516060525f608052611db16104606134d7565b610460518060ff1c6156b4576104405180820281191515600160ff1b84141517821584848405141716156156b45790509050610400525b6103605161040051056104605261046051670de0b6b3a7640000610460516103a05180820281191515600160ff1b84141517821584848405141716156156b45790509050058082038281135f8312186156b45790509050670de0b6b3a764000081038181136156b4579050610460526102e05115611f2f57610420516103c0511315611f2f576104405160206157455f395f516386fc88d36104a05260206104a060046104bc845afa611e9d573d5f5f3e3d5ffd5b60203d106156b4576104a09050518060ff1c6156b45780828118828413021890509050610440518082038281135f8312186156b457905090506104805260016104805112611f2f576104605161036051610480516103e05180820281191515600160ff1b84141517821584848405141716156156b45790509050058082018281125f8312186156b45790509050610460525b6020610460f35b63bcbaf4878118611f5457604436106156b45760016109c052611f77565b633ecdb8288118611ffd57606436106156b4576044358060011c6156b4576109c0525b6004358060a01c6156b4576109a052346156b4575f546002146156b45760025f555f6109e052336109a05114611fbc5760026109a0516020525f5260405f20546109e0525b6109a05161064052602435610660526109e05161068052670de0b6b3a76400006106a0526109c0516106c0526040366106e037611ff7614eb9565b60035f55005b63036aed88811861210e5760e436106156b4576004358060a01c6156b4576109a0526064358060011c6156b4576109c0526084358060a01c6156b4576109e05260a43560040160058135116156b4578035602082018160051b8082610a2037505080610a00525050346156b4575f546002146156b45760025f555f610ac052336109a0511461209b5760026109a0516020525f5260405f2054610ac0525b6109a0516106405260243561066052610ac05161068052604435670de0b6b3a7640000818118670de0b6b3a76400008310021890506106a0526109c0516106c0526109e0516106e052610a00518060051b8061072082610a2060045afa5050806107005250612108614eb9565b60035f55005b631b25cdaf811861213257602436106156b457670de0b6b3a764000060e05261214c565b63546e040d811861226757604436106156b45760243560e0525b6004358060a01c6156b45760c052346156b4575f546002146156b4575f610100523360c0511461218a57600260c0516020525f5260405f2054610100525b670de0b6b3a764000060206157455f395f5163544fb5c16101405260c051610160526040610140602461015c845afa6121c5573d5f5f3e3d5ffd5b60403d106156b45761014090505160e051604052610100516060526121eb6101a0614e4c565b6101a0518082028115838383041417156156b457905090500461012052670de0b6b3a764000060c051604052612222610160613443565b6101605160e0518082028115838383041417156156b4579050905004610140526101205161014051610120518082811882841102189050905003610160526020610160f35b63e2d8ebee811861228457602436106156b4575f610200526122a7565b638908ea82811861233057604436106156b4576024358060011c6156b457610200525b6004358060a01c6156b4576101e052346156b4575f546002146156b45760206101e051610260526101e0516040526122e0610220613443565b6102205161028052610200516102a05260026101e0516020525f5260405f20546102c05261026051604052610280516060526102a0516080526102c05160a05261232b610240614b66565b610240f35b627c98ab8118612346576040366101e037612386565b6380e8f6ec811861236a57602436106156b4576004356101e0525f61020052612386565b6390f8667d81186125fb57604436106156b457604060046101e0375b346156b4575f546002146156b4576801000000000000000554610220526102005161024052610200516123bc5761022051610240525b6101e051610260525f610280525f620f4240905b80620273a052610220516102605110156123f45761024051620273a05118156123f7565b60015b156124015761256c565b6102605167fffffffffffffffe81116156b45760050154620273c052620273c05160405261243162027400613443565b6202740051620273e052620273c051604052620273e05160605260016080526002620273c0516020525f5260405f205460a05261247062027420614b66565b620274205162027400527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff62027400511361254c5760206157455f395f5163544fb5c16202746052620273c051620274805260406202746060246202747c845afa6124dd573d5f5f3e3d5ffd5b60403d106156b457620274609050805162027420526020810151620274405250610280516103e781116156b45760a081026102a001620273c05181526202742051602082015262027440516040820152620273e051606082015262027400516080820152506001810161028052505b61026051600181018181106156b4579050610260526001018181186123d0575b5050602080620273a05280620273a0015f6102805180835260a081025f826103e881116156b45780156125e557905b60a08102602087010160a082026102a0018051825260208101516020830152604081015160408301526060810151606083015260808101516080830152505060010181811861259b575b50508201602001915050905081019050620273a0f35b63d9f11a64811861264357346156b457602060206157455f395f5163f2388acb604052602060406004605c845afa612635573d5f5f3e3d5ffd5b60203d106156b45760409050f35b632c5089c3811861278e57602436106156b4576004358060a01c6156b457604052346156b4575f546002146156b45760206157455f395f5163e8dd1ef1606052604051608052602060606024607c845afa6126a0573d5f5f3e3d5ffd5b60203d106156b4576060518060011c6156b45760a05260a0905051156156b45760206157455f395f5163b461100d60a05260405160c052604060a0602460bc845afa6126ee573d5f5f3e3d5ffd5b60403d106156b45760a09050805160605260208101516080525060206157455f395f51632eb858e760a05260605160c052602060a0602460bc845afa612736573d5f5f3e3d5ffd5b60203d106156b45760a09050516101205260206157455f395f516324299b7a60e05260805161010052602060e0602460fc845afa612776573d5f5f3e3d5ffd5b60203d106156b45760e0905051610140526040610120f35b63ec74d0a881186128a057602436106156b4576004358060a01c6156b45760c052346156b4575f546002146156b45760206157455f395f5163544fb5c16101205260c051610140526040610120602461013c845afa6127ef573d5f5f3e3d5ffd5b60403d106156b4576101209050805160e0526020810151610100525060206157455f395f5163b461100d6101605260c051610180526040610160602461017c845afa61283d573d5f5f3e3d5ffd5b60403d106156b457610160905080516101205260208101516101405250610100516101805260e0516101a05260c05160405261287a610160613443565b610160516101c0526001610120516101405103015f81126156b4576101e0526080610180f35b634189617d81186129ba57602436106156b457346156b45760206156c55f395f5163f851a440604052602060406004605c845afa6128e0573d5f5f3e3d5ffd5b60203d106156b4576040518060a01c6156b457608052608090505133186156b45767016345785d8a00006004351115612919575f612923565b620f424060043510155b6129825760036040527f466565000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f825f031636823750506308c379a05f526020602052601f19601f6040510116604401601cfd5b60206157455f395f51631aa02d59604052600435606052803b156156b4575f60406024605c5f855af16129b7573d5f5f3e3d5ffd5b50005b63a5b4804a8118612ac057602436106156b457346156b45760206156c55f395f5163f851a440604052602060406004605c845afa6129fa573d5f5f3e3d5ffd5b60203d106156b4576040518060a01c6156b457608052608090505133186156b457670de0b6b3a76400006004351115612a885760086040527f486967682066656500000000000000000000000000000000000000000000000060605260405060405180606001601f825f031636823750506308c379a05f526020602052601f19601f6040510116604401601cfd5b60206157455f395f51633217902f604052600435606052803b156156b4575f60406024605c5f855af1612abd573d5f5f3e3d5ffd5b50005b6381d2f1b78118612bab57602436106156b4576004358060a01c6156b457604052346156b4575f546002146156b45760025f5560206156c55f395f5163f851a440606052602060606004607c845afa612b1b573d5f5f3e3d5ffd5b60203d106156b4576060518060a01c6156b45760a05260a090505133186156b457604051680100000000000000085560405163e91f2f4c606052602060606004607c5f855af1612b6d573d5f5f3e3d5ffd5b60203d106156b457606050507f51fabb88f7860c9dbcc2a5a9b69a8b9476d63b87124591f97254e29f0e8daaeb60405160605260206060a160035f55005b632a0c35868118612c9557604436106156b457346156b4575f546002146156b45760025f5560206156c55f395f5163f851a440604052602060406004605c845afa612bf8573d5f5f3e3d5ffd5b60203d106156b4576040518060a01c6156b457608052608090505133186156b45760243560043511156156b457662386f26fc10000602435106156b4576706f05b59d3b20000600435116156b45760243568010000000000000009556004356801000000000000000a557fe2750bf9a7458977fcc01c1a0b615d12162f63b18cad78441bd64c590b337eca6040600460403760406040a160035f55005b63cc1891c78118612d4d57602436106156b4576004358060a01c6156b457604052346156b4575f546002146156b45760025f5560206156c55f395f5163f851a440606052602060606004607c845afa612cf0573d5f5f3e3d5ffd5b60203d106156b4576060518060a01c6156b45760a05260a090505133186156b45760206157455f395f5163cc1891c7606052604051608052803b156156b4575f60606024607c5f855af1612d46573d5f5f3e3d5ffd5b5060035f55005b631b1800e38118612e1557346156b45760206157455f395f5163095a0fc6606052602060606004607c845afa612d85573d5f5f3e3d5ffd5b60203d106156b45760609050516040526003546060526004546080526060516040518082028115838383041417156156b4579050905060805180156156b4578082049050905068010000000000000007548082018281106156b45790509050606052680100000000000000065460a05260a05160605160a051808281188284110218905090500360c052602060c0f35b631e0cfcef81186131b857346156b4575f546002146156b45760025f5560206156c55f395f5163cab4d3db60c052602060c0600460dc845afa612e5a573d5f5f3e3d5ffd5b60203d106156b45760c0518060a01c6156b4576101005261010090505160a05260206157455f395f5163d1fea73360e052602060e0600460fc845afa612ea2573d5f5f3e3d5ffd5b60203d106156b45760e090505160c05260206157455f395f516389960ba7610100526020610100600461011c845afa612edd573d5f5f3e3d5ffd5b60203d106156b45761010090505160e05260c05115612f5b5760206156e55f395f516323b872dd6101005260206157455f395f516101205260a0516101405260c051610160526020610100606461011c5f855af1612f3d573d5f5f3e3d5ffd5b60203d106156b457610100518060011c6156b4576101805261018050505b60e05115612fe65760206157055f395f516323b872dd6101005260206157455f395f516101205260a0516101405260e051610160526020610100606461011c5f855af1612faa573d5f5f3e3d5ffd5b3d612fc157803b156156b457600161018052612fda565b60203d106156b457610100518060011c6156b457610180525b610180905051156156b4575b60206157455f395f5163822fe50761010052803b156156b4575f610100600461011c5f855af1613018573d5f5f3e3d5ffd5b50613024610120613334565b6101205161010052600354610120526004546101405261012051610100518082028115838383041417156156b457905090506101405180156156b4578082049050905061012052610100516101405261012051600355610140516004556101205168010000000000000007548082018281106156b4579050905061016052680100000000000000065461018052610180516101605111613101577f5393ab6ef9bb40d91d1b04bbbeb707fbf3d1eb73f46744e2d179e4996026283f5f6101a052610120516101c05260406101a0a15f6101a05260206101a06131b2565b6101605168010000000000000006556101805161016051036101605260206156e55f395f5163a9059cbb6101a05260a0516101c052610160516101e05260206101a060446101bc5f855af1613158573d5f5f3e3d5ffd5b60203d106156b4576101a0518060011c6156b4576102005261020050507f5393ab6ef9bb40d91d1b04bbbeb707fbf3d1eb73f46744e2d179e4996026283f610160516101a052610120516101c05260406101a0a160206101605b60035f55f35b505b34156131cf5760206157e55f395f51156156b4575b366156b457005b670de0b6b3a7640000604051106060525f60805260405160a052606051156132195760a05180156156b457806ec097ce7bc90715b34b9f100000000004905060a0525b608060c0525f6008905b8060e05260c05160020a61010052670de0b6b3a7640000610100510260a05110613267576101005160a0510460a052608051670de0b6b3a764000060c05102016080525b60c05160011c60c052600101818118613223575050670de0b6b3a764000060e0525f6022905b8061010052671bc16d674ec8000060a051106132b75760e0516080510160805260a05160011c60a0525b670de0b6b3a764000060a05160a051020460a05260e05160011c60e05260010181811861328d5750506060516132fa576080518060ff1c6156b457815250613332565b6080518060ff1c6156b4577f800000000000000000000000000000000000000000000000000000000000000081146156b4575f038152505b565b680100000000000000085463e91f2f4c606052602060606004607c5f855af161335f573d5f5f3e3d5ffd5b60203d106156b4576060905051640a3c2abcef818118640a3c2abcef83100218905060405260206157455f395f5163d4387a99606052604051608052602060606024607c5f855af16133b3573d5f5f3e3d5ffd5b60203d106156b4576060905051815250565b6133cf60e0613334565b60e05160c052600160a0516020525f5260405f20805460e0526001810154610100525060e05161340a575f815260c051602082015250613441565b60e05160c0518082028115838383041417156156b457905090506101005180156156b45780820490509050815260c0516020820152505b565b60206157455f395f5163095a0fc6608052602060806004609c845afa61346b573d5f5f3e3d5ffd5b60203d106156b457608090505160605260016040516020525f5260405f208054608052600181015460a052506080516134a7575f8152506134d5565b6080516060518082028115838383041417156156b4579050905060a05180156156b457808204905090508152505b565b60405160805160405160605180156156b457808204905090506103e88181186103e883110218905080156156b45780683635c9adc5dea000000490508082018281106156b45790509050670de0b6b3a7640000818118670de0b6b3a7640000831002189050670de0b6b3a7640000038082028115838383041417156156b4579050905060206157c55f395f516060518082028115838383041417156156b4579050905080156156b4578082049050905060a05260a05160c052600160318101905b8060e05260605160e051186135ac576135ee565b60206157655f395f5160a05160206157855f395f518082028115838383041417156156b457905090500460a05260a05160c0510160c052600101818118613598575b505060c051815250565b61014051613665576007610180527f4e6f206c6f616e000000000000000000000000000000000000000000000000006101a0526101805061018051806101a001601f825f031636823750506308c379a061014052602061016052601f19601f61018051011660440161015cfd5b60206157455f395f51638f8654c56101a05260206101a060046101bc845afa613690573d5f5f3e3d5ffd5b60203d106156b4576101a09050516101805260206157455f395f51632eb858e76101c052610180516101e05260206101c060246101dc845afa6136d5573d5f5f3e3d5ffd5b60203d106156b4576101c09050516101a0526101205160206157255f395f518082028115838383041417156156b45790509050604052610160516060526801000000000000000a5460805261372b6101e06134d7565b6101e0516101c0526101c0516101a0518082028115838383041417156156b4579050905061014051600181018181106156b457905080156156b457808204905090506101c0526101c0516137de57600e6101e0527f416d6f756e7420746f6f206c6f77000000000000000000000000000000000000610200526101e0506101e0518061020001601f825f031636823750506308c379a06101a05260206101c052601f19601f6101e05101166044016101bcfd5b6101c0516040526137f06102006131d6565b610200516101e0527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101e05113613853576101e05160206157a55f395f51600181038181136156b45790508082038281135f8312186156b457905090506101e0525b6101e05160206157a55f395f5180156156b457808205600160ff1b8314155f1983141517156156b457905090506101e0526101e051610160518060ff1c6156b457806104000361040081135f8312186156b457905080828118828412021890509050610180518082018281125f8312186156b457905090506101e052610180516101e051136139a35760206157455f395f5163ec654706610200526101e051600181038181136156b4579050610220526020610200602461021c845afa61391c573d5f5f3e3d5ffd5b60203d106156b457610200518060011c6156b457610240526102409050516139a357600d610260527f4465627420746f6f2068696768000000000000000000000000000000000000006102805261026050610260518061028001601f825f031636823750506308c379a061022052602061024052601f19601f61026051011660440161023cfd5b60206157455f395f516386fc88d3610240526020610240600461025c845afa6139ce573d5f5f3e3d5ffd5b60203d106156b45761024090505160206157455f395f51632eb858e7610200526101e051610220526020610200602461021c845afa613a0f573d5f5f3e3d5ffd5b60203d106156b45761020090505110613a8757600d610280527f4465627420746f6f2068696768000000000000000000000000000000000000006102a0526102805061028051806102a001601f825f031636823750506308c379a061024052602061026052601f19601f61028051011660440161025cfd5b6101e051815250565b60206157455f395f516386fc88d3610140526020610140600461015c845afa613abb573d5f5f3e3d5ffd5b60203d106156b4576101409050516101205260206157a55f395f5160206157455f395f5163a7db79a5610160526020610160600461017c845afa613b01573d5f5f3e3d5ffd5b60203d106156b457610160905051670de0b6b3a7640000810281670de0b6b3a76400008204186156b45790506101205180156156b45780820490509050604052613b4c6101a06131d6565b6101a05105600581018181126156b45790506101405260206157455f395f51632eb858e761018052610140516101a0526020610180602461019c845afa613b95573d5f5f3e3d5ffd5b60203d106156b4576101809050516101605260206157455f395f5163c16ef2646101a05260206101a060046101bc845afa613bd2573d5f5f3e3d5ffd5b60203d106156b4576101a0905051610180525f610401905b806101a05261014051600181038181136156b457905061014052610180516101405113613c1657613c74565b610160516101c05260206157855f395f516101605160206157655f395f518082028115838383041417156156b45790509050046101605261012051610160511115613c69576101c0518352505050613c7e565b600101818118613bea575b5050610160518152505b565b60206157e55f395f51613c95576060516156b4575b6040516060518082038281116156b4579050905060805260605115613d445760206157055f395f5163d0e30db060a052803b156156b4575f60a0600460bc606051855af1613ce5573d5f5f3e3d5ffd5b5060206157055f395f5163a9059cbb60a05260206157455f395f5160c05260605160e052602060a0604460bc5f855af1613d21573d5f5f3e3d5ffd5b60203d106156b45760a0518060011c6156b45761010052610100905051156156b4575b60805115613dc75760206157055f395f516323b872dd60a0523360c05260206157455f395f5160e05260805161010052602060a0606460bc5f855af1613d8c573d5f5f3e3d5ffd5b3d613da357803b156156b457600161012052613dbb565b60203d106156b45760a0518060011c6156b457610120525b610120905051156156b4575b565b608051613dd6575f613de0565b60206157e55f395f515b613e655760206157055f395f516323b872dd60a05260206157455f395f5160c05260405160e05260605161010052602060a0606460bc5f855af1613e26573d5f5f3e3d5ffd5b3d613e3d57803b156156b457600161012052613e55565b60203d106156b45760a0518060011c6156b457610120525b610120905051156156b457613f21565b60206157055f395f516323b872dd60a05260206157455f395f5160c0523060e05260605161010052602060a0606460bc5f855af1613ea5573d5f5f3e3d5ffd5b60203d106156b45760a0518060011c6156b45761012052610120905051156156b45760206157055f395f51632e1a7d4d60a05260605160c052803b156156b4575f60a0602460bc5f855af1613efc573d5f5f3e3d5ffd5b505f60a05260a0505f5f60a05160c0606051604051612710f1613f21573d5f5f3e3d5ffd5b565b60206157055f395f51604051146156b4576060366101c03760206157455f395f51638f8654c5610220526020610220600461023c845afa613f66573d5f5f3e3d5ffd5b60203d106156b4576102209050516101c05260206157455f395f5163ebcb0067610240526101c051610260526020610240602461025c845afa613fab573d5f5f3e3d5ffd5b60203d106156b4576102409050516102205260206157455f395f516331f7e306610260526101c051610280526020610260602461027c845afa613ff0573d5f5f3e3d5ffd5b60203d106156b457610260905051610240525f60605181610460015260048101905060a06080516102e05260a0516103005260c0516103205260e051610340528061036052806102e0015f610100518083528060051b5f82600581116156b457801561407657905b8060051b61012001518160051b602088010152600101818118614058575b505082016020019150509050810190506102c0526102c080516020820183610460018281848460045afa50505080830192505050806104405261044050506040610600610440516104605f6040515af16140d2573d5f5f3e3d5ffd5b3d604081183d60401002186105e0526105e080516020820181610280838360045afa505080610260525050610260516020116156b457610280516102e05260206102c0526102c06020810151815160200360031b1c90506101e052610260516040116156b4576102a0516102e05260206102c0526102c06020810151815160200360031b1c90506102005260206157455f395f51638f8654c56102c05260206102c060046102dc845afa614188573d5f5f3e3d5ffd5b60203d106156b4576102c09050516101c051186156b45760206157455f395f5163ebcb00676102c0526101c0516102e05260206102c060246102dc845afa6141d2573d5f5f3e3d5ffd5b60203d106156b4576102c090505161022051186156b45760206157455f395f516331f7e3066102c0526101c0516102e05260206102c060246102dc845afa61421c573d5f5f3e3d5ffd5b60203d106156b4576102c090505161024051186156b4576101c05181526101e051602082015261020051604082015250565b6001336020525f5260405f2054156142c5576014610360527f4c6f616e20616c726561647920637265617465640000000000000000000000006103805261036050610360518061038001601f825f031636823750506308c379a061032052602061034052601f19601f61036051011660440161033cfd5b600461032051101561433657600f610360527f4e656564206d6f7265207469636b7300000000000000000000000000000000006103805261036050610360518061038001601f825f031636823750506308c379a061032052602061034052601f19601f61036051011660440161033cfd5b60326103205111156143a757600f610360527f4e656564206c657373207469636b7300000000000000000000000000000000006103805261036050610360518061038001601f825f031636823750506308c379a061032052602061034052601f19601f61036051011660440161033cfd5b6102e05161012052610300516101405261032051610160526143ca6103806135f8565b61038051610360526103605161032051600181038181116156b45790508060ff1c6156b4578082018281125f8312186156b45790509050610380526144106103c0613334565b6103c0516103a0526001336020525f5260405f206103005181556103a05160018201555068010000000000000009546103c0526103c0516002336020525f5260405f205568010000000000000005546103e052336103e05167fffffffffffffffe81116156b457600501556103e05168010000000000000004336020525f5260405f205560016103e0510168010000000000000005556003546103a0518082028115838383041417156156b4579050905060045480156156b45780820490509050610300518082018281106156b457905090506003556103a05160045560206157455f395f5163ab047e006104005233610420526102e0516104405261036051610460526103805161048052803b156156b4575f610400608461041c5f855af161453c573d5f5f3e3d5ffd5b506801000000000000000654610300518082018281106156b45790509050680100000000000000065561034051156145db576102e0516040526102c051606052614584613c80565b60206156e55f395f5163a9059cbb61040052336104205261030051610440526020610400604461041c5f855af16145bd573d5f5f3e3d5ffd5b60203d106156b457610400518060011c6156b4576104605261046050505b337feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f26102e051610400526103005161042052610360516104405261038051610460526103c0516104805260a0610400a2337fe1979fe4c35e0cef342fef5668e2c8e7a7e9f5d5d1ca8fee0ac6c427fa4153af6102e0516104005261030051610420526040610400a2565b604036610340376103005160a05261467e6103806133c5565b61038080516103405260208101516103605250610340516146fe576012610380527f4c6f616e20646f65736e277420657869737400000000000000000000000000006103a0526103805061038051806103a001601f825f031636823750506308c379a061034052602061036052601f19601f61038051011660440161035cfd5b610340516102e0518082018281106156b457905090506103405260206157455f395f5163b461100d6103c052610300516103e05260406103c060246103dc845afa61474b573d5f5f3e3d5ffd5b60403d106156b4576103c0905080516103805260208101516103a052506001610380516103a05103015f81126156b4576103c05260206157455f395f5163f3fef3a3610420526103005161044052670de0b6b3a7640000610460526040610420604461043c5f855af16147c0573d5f5f3e3d5ffd5b60403d106156b457610420905080516103e052602081015161040052506103e0511561484b57601a610420527f416c726561647920696e20756e6465727761746572206d6f64650000000000006104405261042050610420518061044001601f825f031636823750506308c379a06103e052602061040052601f19601f6104205101166044016103fcfd5b6103205161487257610400516102c0518082018281106156b457905090506104005261488d565b610400516102c0518082038281116156b45790509050610400525b610400516101205261034051610140526103c051610160526148b06104406135f8565b610440516104205261042051610380516103a051038082018281125f8312186156b457905090506104405260206157455f395f5163ab047e00610460526103005161048052610400516104a052610420516104c052610440516104e052803b156156b4575f610460608461047c5f855af161492d573d5f5f3e3d5ffd5b506001610300516020525f5260405f2061034051815561036051600182015550680100000000000000095461046052610460516002610300516020525f5260405f20556102e051156149c157600354610360518082028115838383041417156156b4579050905060045480156156b457808204905090506102e0518082018281106156b45790509050600355610360516004555b61032051614a0957610300517fe1979fe4c35e0cef342fef5668e2c8e7a7e9f5d5d1ca8fee0ac6c427fa4153af6102c051610480526102e0516104a0526040610480a2614a3d565b610300517fe25410a4059619c9594dc6f022fe231b02aaea733f689e7ab0cd21b3d4d0eb546102c051610480526020610480a25b610300517feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f26104005161048052610340516104a052610420516104c052610440516104e052610460516105005260a0610480a2565b6801000000000000000554600181038181116156b4579050606052680100000000000000046040516020525f5260405f205460805260405160805167fffffffffffffffe81116156b45760050154186156b4575f680100000000000000046040516020525f5260405f20556060516080511015614b565760605167fffffffffffffffe81116156b4576005015460a05260a05160805167fffffffffffffffe81116156b457600501556080516801000000000000000460a0516020525f5260405f20555b6060516801000000000000000555565b606051614bc957601260c0527f4c6f616e20646f65736e2774206578697374000000000000000000000000000060e05260c05060c0518060e001601f825f031636823750506308c379a0608052602060a052601f19601f60c0510116604401609cfd5b60a0518060ff1c6156b45780670de0b6b3a764000003670de0b6b3a764000081135f8312186156b457905060c0526060518060ff1c6156b45760206157455f395f516362ca4b1860e05260405161010052602060e0602460fc845afa614c31573d5f5f3e3d5ffd5b60203d106156b45760e09050518060ff1c6156b45760c05180820281191515600160ff1b84141517821584848405141716156156b4579050905005670de0b6b3a764000081038181136156b457905060c05260805115614e445760206157455f395f5163b461100d61010052604051610120526040610100602461011c845afa614cbd573d5f5f3e3d5ffd5b60403d106156b45761010090505160e05260206157455f395f51638f8654c5610100526020610100600461011c845afa614cf9573d5f5f3e3d5ffd5b60203d106156b45761010090505160e0511315614e445760206157455f395f516386fc88d3610160526020610160600461017c845afa614d3b573d5f5f3e3d5ffd5b60203d106156b4576101609050516101405260206157455f395f51632eb858e76101805260e0516101a0526020610180602461019c845afa614d7f573d5f5f3e3d5ffd5b60203d106156b4576101809050516101605261016051610140511115614e445760c05160605161016051610140510360206157455f395f5163544fb5c1610180526040516101a0526040610180602461019c845afa614de0573d5f5f3e3d5ffd5b60403d106156b4576101809050602081019050518082028115838383041417156156b4579050905060206157255f395f518082028115838383041417156156b45790509050048060ff1c6156b4578082018281125f8312186156b4579050905060c0525b60c051815250565b670de0b6b3a7640000608052670de0b6b3a763ffff60405111614eb157606051670de0b6b3a764000001604051670de0b6b3a76400000360605160011c670de0b6b3a7640000010204608052670de0b6b3a76400006040516040516080510102046080525b608051815250565b6040366107c0376106405160a052614ed26108006133c5565b61080080516107c05260208101516107e052506106805115614fa3577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610640516040526107c05160605260016080526106805160a052614f34610800614b66565b610800511315614fa357600f610820527f4e6f7420656e6f7567682072656b7400000000000000000000000000000000006108405261082050610820518061084001601f825f031636823750506308c379a06107e052602061080052601f19601f6108205101166044016107fcfd5b6107c05161080052670de0b6b3a76400006107c0516106a0518082028115838383041417156156b45790509050046107c0526107c051156156b4576107c05161080051036108005260206157455f395f5163f3fef3a361088052610640516108a0526106a0516040526106805160605261501e610860614e4c565b610860516108c0526040610880604461089c5f855af1615040573d5f5f3e3d5ffd5b60403d106156b457610880905080516108205260208101516108405250610660516108205110156150d0576008610860527f536c6970706167650000000000000000000000000000000000000000000000006108805261086050610860518061088001601f825f031636823750506308c379a061082052602061084052601f19601f61086051011660440161083cfd5b610820516107c051808281188284100218905090506108605261086051156151565760206156e55f395f516323b872dd6108805260206157455f395f516108a052306108c052610860516108e0526020610880606461089c5f855af1615138573d5f5f3e3d5ffd5b60203d106156b457610880518060011c6156b4576109005261090050505b610820516107c051116151f85733604052610840516060526106c05160805261517d613dc9565b6107c0516108205111156155505760206156e55f395f516323b872dd6108805260206157455f395f516108a052336108c0526107c05161082051036108e0526020610880606461089c5f855af16151d6573d5f5f3e3d5ffd5b60203d106156b457610880518060011c6156b457610900526109005050615550565b610820516107c05103610880526106e0516152875733604052610840516060526106c051608052615227613dc9565b60206156e55f395f516323b872dd6108a052336108c052306108e052610880516109005260206108a060646108bc5f855af1615265573d5f5f3e3d5ffd5b60203d106156b4576108a0518060011c6156b457610920526109205050615550565b61084051156153155760206157055f395f516323b872dd6108a05260206157455f395f516108c0526106e0516108e052610840516109005260206108a060646108bc5f855af16152d9573d5f5f3e3d5ffd5b3d6152f057803b156156b457600161092052615309565b60203d106156b4576108a0518060011c6156b457610920525b610920905051156156b4575b6106e0516040527f4ea696bb00000000000000000000000000000000000000000000000000000000606052610640516080526108205160a0526108405160c0526107c05160e052610700518060051b806101208261072060045afa5050806101005250615383610900613f23565b61090080516108a05260208101516108c05260408101516108e05250610880516108c0511015615412576013610900527f6e6f7420656e6f7567682070726f6365656473000000000000000000000000006109205261090050610900518061092001601f825f031636823750506308c379a06108c05260206108e052601f19601f6109005101166044016108dcfd5b610880516108c05111156154845760206156e55f395f516323b872dd610900526106e051610920523361094052610880516108c05103610960526020610900606461091c5f855af1615466573d5f5f3e3d5ffd5b60203d106156b457610900518060011c6156b4576109805261098050505b60206156e55f395f516323b872dd610900526106e05161092052306109405261088051610960526020610900606461091c5f855af16154c5573d5f5f3e3d5ffd5b60203d106156b457610900518060011c6156b4576109805261098050506108e051156155505760206157055f395f516323b872dd610900526106e0516109205233610940526108e051610960526020610900606461091c5f855af161552c573d5f5f3e3d5ffd5b60203d106156b457610900518060011c6156b45761098052610980905051156156b4575b68010000000000000007546107c0518082018281106156b4579050905068010000000000000007556001610640516020525f5260405f206108005181556107e051600182015550610640517f77c6871227e5d2dec8dadd5354f78453203e22e669cd0ec4c19d9a8c5edb31d061084051610880526107c0516108a0526040610880a261064051337f642dd4d37ddd32036b9797cec464c0045dd2118c549066ae6b0f88e32240c2d06108405161088052610820516108a0526107c0516108c0526060610880a36108005161565f57610640517feec6b7095a637e006c79c1819d696e353a8f703db2c49fc0219e17a8fd04f7f260a0366108803760a0610880a26106405160405261565f614a92565b6003546107e0518082028115838383041417156156b4579050905060045480156156b45780820490509050610880526107c051610880516107c05180828118828411021890509050036003556107e051600455565b5f80fda165767970657283000309000b000000000000000000000000c9332fdcb1c491dcc683bae86fe3cb70360738bc000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e00000000000000000000000018084fba666a33d37592fa2633fd49a74dd93a880000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f9bd9da2427a50908c4c6d1599d8e62837c2bcb000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000063000000000000000000000000000000000000000000000000003383482309faa60000000000000000000000000000000000000000000000000df29c916c5c292b0000000000000000000000000000000000000000000000000000000000000000

Verified Source Code Partial Match

Compiler: v0.3.9+commit.66b96705
crvUSDController.vy 1327 lines
# @version 0.3.9
"""
@title crvUSD Controller
@author Curve.Fi
@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved
"""

interface LLAMMA:
    def A() -> uint256: view
    def get_p() -> uint256: view
    def get_base_price() -> uint256: view
    def active_band() -> int256: view
    def active_band_with_skip() -> int256: view
    def p_oracle_up(n: int256) -> uint256: view
    def p_oracle_down(n: int256) -> uint256: view
    def deposit_range(user: address, amount: uint256, n1: int256, n2: int256): nonpayable
    def read_user_tick_numbers(_for: address) -> int256[2]: view
    def get_sum_xy(user: address) -> uint256[2]: view
    def withdraw(user: address, frac: uint256) -> uint256[2]: nonpayable
    def get_x_down(user: address) -> uint256: view
    def get_rate_mul() -> uint256: view
    def set_rate(rate: uint256) -> uint256: nonpayable
    def set_fee(fee: uint256): nonpayable
    def set_admin_fee(fee: uint256): nonpayable
    def price_oracle() -> uint256: view
    def can_skip_bands(n_end: int256) -> bool: view
    def set_price_oracle(price_oracle: PriceOracle): nonpayable
    def admin_fees_x() -> uint256: view
    def admin_fees_y() -> uint256: view
    def reset_admin_fees(): nonpayable
    def has_liquidity(user: address) -> bool: view
    def bands_x(n: int256) -> uint256: view
    def bands_y(n: int256) -> uint256: view
    def set_callback(user: address): nonpayable

interface ERC20:
    def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable
    def transfer(_to: address, _value: uint256) -> bool: nonpayable
    def decimals() -> uint256: view
    def approve(_spender: address, _value: uint256) -> bool: nonpayable
    def balanceOf(_from: address) -> uint256: view

interface WETH:
    def deposit(): payable
    def withdraw(_amount: uint256): nonpayable

interface MonetaryPolicy:
    def rate_write() -> uint256: nonpayable

interface Factory:
    def stablecoin() -> address: view
    def admin() -> address: view
    def fee_receiver() -> address: view
    def WETH() -> address: view

interface PriceOracle:
    def price() -> uint256: view
    def price_w() -> uint256: nonpayable


event UserState:
    user: indexed(address)
    collateral: uint256
    debt: uint256
    n1: int256
    n2: int256
    liquidation_discount: uint256

event Borrow:
    user: indexed(address)
    collateral_increase: uint256
    loan_increase: uint256

event Repay:
    user: indexed(address)
    collateral_decrease: uint256
    loan_decrease: uint256

event RemoveCollateral:
    user: indexed(address)
    collateral_decrease: uint256

event Liquidate:
    liquidator: indexed(address)
    user: indexed(address)
    collateral_received: uint256
    stablecoin_received: uint256
    debt: uint256

event SetMonetaryPolicy:
    monetary_policy: address

event SetBorrowingDiscounts:
    loan_discount: uint256
    liquidation_discount: uint256

event CollectFees:
    amount: uint256
    new_supply: uint256


struct Loan:
    initial_debt: uint256
    rate_mul: uint256

struct Position:
    user: address
    x: uint256
    y: uint256
    debt: uint256
    health: int256

struct CallbackData:
    active_band: int256
    stablecoins: uint256
    collateral: uint256


FACTORY: immutable(Factory)
STABLECOIN: immutable(ERC20)
MAX_LOAN_DISCOUNT: constant(uint256) = 5 * 10**17
MIN_LIQUIDATION_DISCOUNT: constant(uint256) = 10**16 # Start liquidating when threshold reached
MAX_TICKS: constant(int256) = 50
MAX_TICKS_UINT: constant(uint256) = 50
MIN_TICKS: constant(int256) = 4
MAX_SKIP_TICKS: constant(uint256) = 1024
MAX_P_BASE_BANDS: constant(int256) = 5

MAX_RATE: constant(uint256) = 43959106799  # 400% APY

loan: HashMap[address, Loan]
liquidation_discounts: public(HashMap[address, uint256])
_total_debt: Loan

loans: public(address[2**64 - 1])  # Enumerate existing loans
loan_ix: public(HashMap[address, uint256])  # Position of the loan in the list
n_loans: public(uint256)  # Number of nonzero loans

minted: public(uint256)
redeemed: public(uint256)

monetary_policy: public(MonetaryPolicy)
liquidation_discount: public(uint256)
loan_discount: public(uint256)

COLLATERAL_TOKEN: immutable(ERC20)
COLLATERAL_PRECISION: immutable(uint256)

AMM: immutable(LLAMMA)
A: immutable(uint256)
Aminus1: immutable(uint256)
LOG2_A_RATIO: immutable(int256)  # log(A / (A - 1))
SQRT_BAND_RATIO: immutable(uint256)

MAX_ADMIN_FEE: constant(uint256) = 10**18  # 100%
MIN_FEE: constant(uint256) = 10**6  # 1e-12, still needs to be above 0
MAX_FEE: constant(uint256) = 10**17  # 10%

USE_ETH: immutable(bool)

CALLBACK_DEPOSIT: constant(bytes4) = method_id("callback_deposit(address,uint256,uint256,uint256,uint256[])", output_type=bytes4)
CALLBACK_REPAY: constant(bytes4) = method_id("callback_repay(address,uint256,uint256,uint256,uint256[])", output_type=bytes4)
CALLBACK_LIQUIDATE: constant(bytes4) = method_id("callback_liquidate(address,uint256,uint256,uint256,uint256[])", output_type=bytes4)

DEAD_SHARES: constant(uint256) = 1000

MAX_ETH_GAS: constant(uint256) = 10000  # Forward this much gas to ETH transfers (2300 is what send() does)


@external
def __init__(
        collateral_token: address,
        monetary_policy: address,
        loan_discount: uint256,
        liquidation_discount: uint256,
        amm: address):
    """
    @notice Controller constructor deployed by the factory from blueprint
    @param collateral_token Token to use for collateral
    @param monetary_policy Address of monetary policy
    @param loan_discount Discount of the maximum loan size compare to get_x_down() value
    @param liquidation_discount Discount of the maximum loan size compare to
           get_x_down() for "bad liquidation" purposes
    @param amm AMM address (Already deployed from blueprint)
    """
    FACTORY = Factory(msg.sender)
    stablecoin: ERC20 = ERC20(Factory(msg.sender).stablecoin())
    STABLECOIN = stablecoin
    assert stablecoin.decimals() == 18

    self.monetary_policy = MonetaryPolicy(monetary_policy)

    self.liquidation_discount = liquidation_discount
    self.loan_discount = loan_discount
    self._total_debt.rate_mul = 10**18

    AMM = LLAMMA(amm)
    _A: uint256 = LLAMMA(amm).A()
    A = _A
    Aminus1 = _A - 1
    LOG2_A_RATIO = self.log2(_A * 10**18 / unsafe_sub(_A, 1))

    COLLATERAL_TOKEN = ERC20(collateral_token)
    COLLATERAL_PRECISION = pow_mod256(10, 18 - ERC20(collateral_token).decimals())

    SQRT_BAND_RATIO = isqrt(unsafe_div(10**36 * _A, unsafe_sub(_A, 1)))

    stablecoin.approve(msg.sender, max_value(uint256))

    if Factory(msg.sender).WETH() == collateral_token:
        USE_ETH = True


@payable
@external
def __default__():
    if msg.value > 0:
        assert USE_ETH
    assert len(msg.data) == 0


@internal
@pure
def log2(_x: uint256) -> int256:
    """
    @notice int(1e18 * log2(_x / 1e18))
    """
    # adapted from: https://medium.com/coinmonks/9aef8515136e
    # and vyper log implementation
    # Might use more optimal solmate's log
    inverse: bool = _x < 10**18
    res: uint256 = 0
    x: uint256 = _x
    if inverse:
        x = 10**36 / x
    t: uint256 = 2**7
    for i in range(8):
        p: uint256 = pow_mod256(2, t)
        if x >= unsafe_mul(p, 10**18):
            x = unsafe_div(x, p)
            res = unsafe_add(unsafe_mul(t, 10**18), res)
        t = unsafe_div(t, 2)
    d: uint256 = 10**18
    for i in range(34):  # 10 decimals: math.log(10**10, 2) == 33.2. Need more?
        if (x >= 2 * 10**18):
            res = unsafe_add(res, d)
            x = unsafe_div(x, 2)
        x = unsafe_div(unsafe_mul(x, x), 10**18)
        d = unsafe_div(d, 2)
    if inverse:
        return -convert(res, int256)
    else:
        return convert(res, int256)


@external
@view
def factory() -> Factory:
    """
    @notice Address of the factory
    """
    return FACTORY


@external
@view
def amm() -> LLAMMA:
    """
    @notice Address of the AMM
    """
    return AMM


@external
@view
def collateral_token() -> ERC20:
    """
    @notice Address of the collateral token
    """
    return COLLATERAL_TOKEN


@internal
def _rate_mul_w() -> uint256:
    """
    @notice Getter for rate_mul (the one which is 1.0+) from the AMM
    """
    rate: uint256 = min(self.monetary_policy.rate_write(), MAX_RATE)
    return AMM.set_rate(rate)


@internal
def _debt(user: address) -> (uint256, uint256):
    """
    @notice Get the value of debt and rate_mul and update the rate_mul counter
    @param user User address
    @return (debt, rate_mul)
    """
    rate_mul: uint256 = self._rate_mul_w()
    loan: Loan = self.loan[user]
    if loan.initial_debt == 0:
        return (0, rate_mul)
    else:
        return (loan.initial_debt * rate_mul / loan.rate_mul, rate_mul)


@internal
@view
def _debt_ro(user: address) -> uint256:
    """
    @notice Get the value of debt without changing the state
    @param user User address
    @return Value of debt
    """
    rate_mul: uint256 = AMM.get_rate_mul()
    loan: Loan = self.loan[user]
    if loan.initial_debt == 0:
        return 0
    else:
        return loan.initial_debt * rate_mul / loan.rate_mul


@external
@view
@nonreentrant('lock')
def debt(user: address) -> uint256:
    """
    @notice Get the value of debt without changing the state
    @param user User address
    @return Value of debt
    """
    return self._debt_ro(user)


@external
@view
@nonreentrant('lock')
def loan_exists(user: address) -> bool:
    """
    @notice Check whether there is a loan of `user` in existence
    """
    return self.loan[user].initial_debt > 0


# No decorator because used in monetary policy
@external
@view
def total_debt() -> uint256:
    """
    @notice Total debt of this controller
    """
    rate_mul: uint256 = AMM.get_rate_mul()
    loan: Loan = self._total_debt
    return loan.initial_debt * rate_mul / loan.rate_mul


@internal
@view
def get_y_effective(collateral: uint256, N: uint256, discount: uint256) -> uint256:
    """
    @notice Intermediary method which calculates y_effective defined as x_effective / p_base,
            however discounted by loan_discount.
            x_effective is an amount which can be obtained from collateral when liquidating
    @param collateral Amount of collateral to get the value for
    @param N Number of bands the deposit is made into
    @param discount Loan discount at 1e18 base (e.g. 1e18 == 100%)
    @return y_effective
    """
    # x_effective = sum_{i=0..N-1}(y / N * p(n_{n1+i})) =
    # = y / N * p_oracle_up(n1) * sqrt((A - 1) / A) * sum_{0..N-1}(((A-1) / A)**k)
    # === d_y_effective * p_oracle_up(n1) * sum(...) === y_effective * p_oracle_up(n1)
    # d_y_effective = y / N / sqrt(A / (A - 1))
    # d_y_effective: uint256 = collateral * unsafe_sub(10**18, discount) / (SQRT_BAND_RATIO * N)
    # Make some extra discount to always deposit lower when we have DEAD_SHARES rounding
    d_y_effective: uint256 = collateral * unsafe_sub(
        10**18, min(discount + (DEAD_SHARES * 10**18) / max(collateral / N, DEAD_SHARES), 10**18)
    ) / (SQRT_BAND_RATIO * N)
    y_effective: uint256 = d_y_effective
    for i in range(1, MAX_TICKS_UINT):
        if i == N:
            break
        d_y_effective = unsafe_div(d_y_effective * Aminus1, A)
        y_effective = unsafe_add(y_effective, d_y_effective)
    return y_effective


@internal
@view
def _calculate_debt_n1(collateral: uint256, debt: uint256, N: uint256) -> int256:
    """
    @notice Calculate the upper band number for the deposit to sit in to support
            the given debt. Reverts if requested debt is too high.
    @param collateral Amount of collateral (at its native precision)
    @param debt Amount of requested debt
    @param N Number of bands to deposit into
    @return Upper band n1 (n1 <= n2) to deposit into. Signed integer
    """
    assert debt > 0, "No loan"
    n0: int256 = AMM.active_band()
    p_base: uint256 = AMM.p_oracle_up(n0)

    # x_effective = y / N * p_oracle_up(n1) * sqrt((A - 1) / A) * sum_{0..N-1}(((A-1) / A)**k)
    # === d_y_effective * p_oracle_up(n1) * sum(...) === y_effective * p_oracle_up(n1)
    # d_y_effective = y / N / sqrt(A / (A - 1))
    y_effective: uint256 = self.get_y_effective(collateral * COLLATERAL_PRECISION, N, self.loan_discount)
    # p_oracle_up(n1) = base_price * ((A - 1) / A)**n1

    # We borrow up until min band touches p_oracle,
    # or it touches non-empty bands which cannot be skipped.
    # We calculate required n1 for given (collateral, debt),
    # and if n1 corresponds to price_oracle being too high, or unreachable band
    # - we revert.

    # n1 is band number based on adiabatic trading, e.g. when p_oracle ~ p
    y_effective = y_effective * p_base / (debt + 1)  # Now it's a ratio

    # n1 = floor(log2(y_effective) / self.logAratio)
    # EVM semantics is not doing floor unlike Python, so we do this
    assert y_effective > 0, "Amount too low"
    n1: int256 = self.log2(y_effective)  # <- switch to faster ln() XXX?
    if n1 < 0:
        n1 -= LOG2_A_RATIO - 1  # This is to deal with vyper's rounding of negative numbers
    n1 /= LOG2_A_RATIO

    n1 = min(n1, 1024 - convert(N, int256)) + n0
    if n1 <= n0:
        assert AMM.can_skip_bands(n1 - 1), "Debt too high"

    # Let's not rely on active_band corresponding to price_oracle:
    # this will be not correct if we are in the area of empty bands
    assert AMM.p_oracle_up(n1) < AMM.price_oracle(), "Debt too high"

    return n1


@internal
@view
def max_p_base() -> uint256:
    """
    @notice Calculate max base price including skipping bands
    """
    p_oracle: uint256 = AMM.price_oracle()
    # Should be correct unless price changes suddenly by MAX_P_BASE_BANDS+ bands
    n1: int256 = unsafe_div(self.log2(AMM.get_base_price() * 10**18 / p_oracle), LOG2_A_RATIO) + MAX_P_BASE_BANDS
    p_base: uint256 = AMM.p_oracle_up(n1)
    n_min: int256 = AMM.active_band_with_skip()

    for i in range(MAX_SKIP_TICKS + 1):
        n1 -= 1
        if n1 <= n_min:
            break
        p_base_prev: uint256 = p_base
        p_base = unsafe_div(p_base * A, Aminus1)
        if p_base > p_oracle:
            return p_base_prev

    return p_base


@external
@view
@nonreentrant('lock')
def max_borrowable(collateral: uint256, N: uint256, current_debt: uint256 = 0) -> uint256:
    """
    @notice Calculation of maximum which can be borrowed (details in comments)
    @param collateral Collateral amount against which to borrow
    @param N number of bands to have the deposit into
    @param current_debt Current debt of the user (if any)
    @return Maximum amount of stablecoin to borrow
    """
    # Calculation of maximum which can be borrowed.
    # It corresponds to a minimum between the amount corresponding to price_oracle
    # and the one given by the min reachable band.
    #
    # Given by p_oracle (perhaps needs to be multiplied by (A - 1) / A to account for mid-band effects)
    # x_max ~= y_effective * p_oracle
    #
    # Given by band number:
    # if n1 is the lowest empty band in the AMM
    # xmax ~= y_effective * amm.p_oracle_up(n1)
    #
    # When n1 -= 1:
    # p_oracle_up *= A / (A - 1)

    y_effective: uint256 = self.get_y_effective(collateral * COLLATERAL_PRECISION, N, self.loan_discount)

    x: uint256 = unsafe_sub(max(unsafe_div(y_effective * self.max_p_base(), 10**18), 1), 1)
    x = unsafe_div(x * (10**18 - 10**14), 10**18)  # Make it a bit smaller
    return min(x, STABLECOIN.balanceOf(self) + current_debt)  # Cannot borrow beyond the amount of coins Controller has


@external
@view
@nonreentrant('lock')
def min_collateral(debt: uint256, N: uint256) -> uint256:
    """
    @notice Minimal amount of collateral required to support debt
    @param debt The debt to support
    @param N Number of bands to deposit into
    @return Minimal collateral required
    """
    # Add N**2 to account for precision loss in multiple bands, e.g. N * 1 / (y/N) = N**2 / y
    return unsafe_div(unsafe_div(debt * 10**18 / self.max_p_base() * 10**18 / self.get_y_effective(10**18, N, self.loan_discount) + N * (N + 2 * DEAD_SHARES), COLLATERAL_PRECISION) * 10**18, 10**18 - 10**14)


@external
@view
@nonreentrant('lock')
def calculate_debt_n1(collateral: uint256, debt: uint256, N: uint256) -> int256:
    """
    @notice Calculate the upper band number for the deposit to sit in to support
            the given debt. Reverts if requested debt is too high.
    @param collateral Amount of collateral (at its native precision)
    @param debt Amount of requested debt
    @param N Number of bands to deposit into
    @return Upper band n1 (n1 <= n2) to deposit into. Signed integer
    """
    return self._calculate_debt_n1(collateral, debt, N)


@internal
def _deposit_collateral(amount: uint256, mvalue: uint256):
    """
    Deposits raw ETH, WETH or both at the same time
    """
    if not USE_ETH:
        assert mvalue == 0  # dev: Not accepting ETH
    diff: uint256 = amount - mvalue  # dev: Incorrect ETH amount
    if mvalue > 0:
        WETH(COLLATERAL_TOKEN.address).deposit(value=mvalue)
        assert COLLATERAL_TOKEN.transfer(AMM.address, mvalue)
    if diff > 0:
        assert COLLATERAL_TOKEN.transferFrom(msg.sender, AMM.address, diff, default_return_value=True)


@internal
def _withdraw_collateral(_for: address, amount: uint256, use_eth: bool):
    if use_eth and USE_ETH:
        assert COLLATERAL_TOKEN.transferFrom(AMM.address, self, amount)
        WETH(COLLATERAL_TOKEN.address).withdraw(amount)
        raw_call(_for, b"", value=amount, gas=MAX_ETH_GAS)
    else:
        assert COLLATERAL_TOKEN.transferFrom(AMM.address, _for, amount, default_return_value=True)


@internal
def execute_callback(callbacker: address, callback_sig: bytes4,
                     user: address, stablecoins: uint256, collateral: uint256, debt: uint256,
                     callback_args: DynArray[uint256, 5]) -> CallbackData:
    assert callbacker != COLLATERAL_TOKEN.address

    data: CallbackData = empty(CallbackData)
    data.active_band = AMM.active_band()
    band_x: uint256 = AMM.bands_x(data.active_band)
    band_y: uint256 = AMM.bands_y(data.active_band)

    # Callback
    response: Bytes[64] = raw_call(
        callbacker,
        concat(callback_sig, _abi_encode(user, stablecoins, collateral, debt, callback_args)),
        max_outsize=64
    )
    data.stablecoins = convert(slice(response, 0, 32), uint256)
    data.collateral = convert(slice(response, 32, 32), uint256)

    # Checks after callback
    assert data.active_band == AMM.active_band()
    assert band_x == AMM.bands_x(data.active_band)
    assert band_y == AMM.bands_y(data.active_band)

    return data

@internal
def _create_loan(mvalue: uint256, collateral: uint256, debt: uint256, N: uint256, transfer_coins: bool):
    assert self.loan[msg.sender].initial_debt == 0, "Loan already created"
    assert N > MIN_TICKS-1, "Need more ticks"
    assert N < MAX_TICKS+1, "Need less ticks"

    n1: int256 = self._calculate_debt_n1(collateral, debt, N)
    n2: int256 = n1 + convert(N - 1, int256)

    rate_mul: uint256 = self._rate_mul_w()
    self.loan[msg.sender] = Loan({initial_debt: debt, rate_mul: rate_mul})
    liquidation_discount: uint256 = self.liquidation_discount
    self.liquidation_discounts[msg.sender] = liquidation_discount

    n_loans: uint256 = self.n_loans
    self.loans[n_loans] = msg.sender
    self.loan_ix[msg.sender] = n_loans
    self.n_loans = unsafe_add(n_loans, 1)

    self._total_debt.initial_debt = self._total_debt.initial_debt * rate_mul / self._total_debt.rate_mul + debt
    self._total_debt.rate_mul = rate_mul

    AMM.deposit_range(msg.sender, collateral, n1, n2)
    self.minted += debt

    if transfer_coins:
        self._deposit_collateral(collateral, mvalue)
        STABLECOIN.transfer(msg.sender, debt)

    log UserState(msg.sender, collateral, debt, n1, n2, liquidation_discount)
    log Borrow(msg.sender, collateral, debt)


@payable
@external
@nonreentrant('lock')
def create_loan(collateral: uint256, debt: uint256, N: uint256):
    """
    @notice Create loan
    @param collateral Amount of collateral to use
    @param debt Stablecoin debt to take
    @param N Number of bands to deposit into (to do autoliquidation-deliquidation),
           can be from MIN_TICKS to MAX_TICKS
    """
    self._create_loan(msg.value, collateral, debt, N, True)


@payable
@external
@nonreentrant('lock')
def create_loan_extended(collateral: uint256, debt: uint256, N: uint256, callbacker: address, callback_args: DynArray[uint256,5]):
    """
    @notice Create loan but pass stablecoin to a callback first so that it can build leverage
    @param collateral Amount of collateral to use
    @param debt Stablecoin debt to take
    @param N Number of bands to deposit into (to do autoliquidation-deliquidation),
           can be from MIN_TICKS to MAX_TICKS
    @param callbacker Address of the callback contract
    @param callback_args Extra arguments for the callback (up to 5) such as min_amount etc
    """
    # Before callback
    STABLECOIN.transfer(callbacker, debt)

    # Callback
    # If there is any unused debt, callbacker can send it to the user
    more_collateral: uint256 = self.execute_callback(
        callbacker, CALLBACK_DEPOSIT, msg.sender, 0, collateral, debt, callback_args).collateral

    # After callback
    self._create_loan(0, collateral + more_collateral, debt, N, False)
    self._deposit_collateral(collateral, msg.value)
    assert COLLATERAL_TOKEN.transferFrom(callbacker, AMM.address, more_collateral, default_return_value=True)


@internal
def _add_collateral_borrow(d_collateral: uint256, d_debt: uint256, _for: address, remove_collateral: bool):
    """
    @notice Internal method to borrow and add or remove collateral
    @param d_collateral Amount of collateral to add
    @param d_debt Amount of debt increase
    @param _for Address to transfer tokens to
    @param remove_collateral Remove collateral instead of adding
    """
    debt: uint256 = 0
    rate_mul: uint256 = 0
    debt, rate_mul = self._debt(_for)
    assert debt > 0, "Loan doesn't exist"
    debt += d_debt
    ns: int256[2] = AMM.read_user_tick_numbers(_for)
    size: uint256 = convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)

    xy: uint256[2] = AMM.withdraw(_for, 10**18)
    assert xy[0] == 0, "Already in underwater mode"
    if remove_collateral:
        xy[1] -= d_collateral
    else:
        xy[1] += d_collateral
    n1: int256 = self._calculate_debt_n1(xy[1], debt, size)
    n2: int256 = n1 + unsafe_sub(ns[1], ns[0])

    AMM.deposit_range(_for, xy[1], n1, n2)
    self.loan[_for] = Loan({initial_debt: debt, rate_mul: rate_mul})
    liquidation_discount: uint256 = self.liquidation_discount
    self.liquidation_discounts[_for] = liquidation_discount

    if d_debt != 0:
        self._total_debt.initial_debt = self._total_debt.initial_debt * rate_mul / self._total_debt.rate_mul + d_debt
        self._total_debt.rate_mul = rate_mul

    if remove_collateral:
        log RemoveCollateral(_for, d_collateral)
    else:
        log Borrow(_for, d_collateral, d_debt)
    log UserState(_for, xy[1], debt, n1, n2, liquidation_discount)


@payable
@external
@nonreentrant('lock')
def add_collateral(collateral: uint256, _for: address = msg.sender):
    """
    @notice Add extra collateral to avoid bad liqidations
    @param collateral Amount of collateral to add
    @param _for Address to add collateral for
    """
    if collateral == 0:
        return
    self._add_collateral_borrow(collateral, 0, _for, False)
    self._deposit_collateral(collateral, msg.value)


@external
@nonreentrant('lock')
def remove_collateral(collateral: uint256, use_eth: bool = True):
    """
    @notice Remove some collateral without repaying the debt
    @param collateral Amount of collateral to remove
    @param use_eth Use wrapping/unwrapping if collateral is ETH
    """
    if collateral == 0:
        return
    self._add_collateral_borrow(collateral, 0, msg.sender, True)
    self._withdraw_collateral(msg.sender, collateral, use_eth)


@payable
@external
@nonreentrant('lock')
def borrow_more(collateral: uint256, debt: uint256):
    """
    @notice Borrow more stablecoins while adding more collateral (not necessary)
    @param collateral Amount of collateral to add
    @param debt Amount of stablecoin debt to take
    """
    if debt == 0:
        return
    self._add_collateral_borrow(collateral, debt, msg.sender, False)
    self.minted += debt
    if collateral != 0:
        self._deposit_collateral(collateral, msg.value)
    STABLECOIN.transfer(msg.sender, debt)


@internal
def _remove_from_list(_for: address):
    last_loan_ix: uint256 = self.n_loans - 1
    loan_ix: uint256 = self.loan_ix[_for]
    assert self.loans[loan_ix] == _for  # dev: should never fail but safety first
    self.loan_ix[_for] = 0
    if loan_ix < last_loan_ix:  # Need to replace
        last_loan: address = self.loans[last_loan_ix]
        self.loans[loan_ix] = last_loan
        self.loan_ix[last_loan] = loan_ix
    self.n_loans = last_loan_ix


@external
@nonreentrant('lock')
def repay(_d_debt: uint256, _for: address = msg.sender, max_active_band: int256 = 2**255-1, use_eth: bool = True):
    """
    @notice Repay debt (partially or fully)
    @param _d_debt The amount of debt to repay. If higher than the current debt - will do full repayment
    @param _for The user to repay the debt for
    @param max_active_band Don't allow active band to be higher than this (to prevent front-running the repay)
    @param use_eth Use wrapping/unwrapping if collateral is ETH
    """
    if _d_debt == 0:
        return
    # Or repay all for MAX_UINT256
    # Withdraw if debt become 0
    debt: uint256 = 0
    rate_mul: uint256 = 0
    debt, rate_mul = self._debt(_for)
    assert debt > 0, "Loan doesn't exist"
    d_debt: uint256 = min(debt, _d_debt)
    debt = unsafe_sub(debt, d_debt)

    if debt == 0:
        # Allow to withdraw all assets even when underwater
        xy: uint256[2] = AMM.withdraw(_for, 10**18)
        if xy[0] > 0:
            # Only allow full repayment when underwater for the sender to do
            assert _for == msg.sender
            STABLECOIN.transferFrom(AMM.address, _for, xy[0])
        if xy[1] > 0:
            self._withdraw_collateral(_for, xy[1], use_eth)
        log UserState(_for, 0, 0, 0, 0, 0)
        log Repay(_for, xy[1], d_debt)
        self._remove_from_list(_for)

    else:
        active_band: int256 = AMM.active_band_with_skip()
        assert active_band <= max_active_band

        ns: int256[2] = AMM.read_user_tick_numbers(_for)
        size: uint256 = convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)
        liquidation_discount: uint256 = self.liquidation_discounts[_for]

        if ns[0] > active_band:
            # Not in liquidation - can move bands
            xy: uint256[2] = AMM.withdraw(_for, 10**18)
            n1: int256 = self._calculate_debt_n1(xy[1], debt, size)
            n2: int256 = n1 + unsafe_sub(ns[1], ns[0])
            AMM.deposit_range(_for, xy[1], n1, n2)
            if _for == msg.sender:
                # Update liquidation discount only if we are that same user. No rugs
                liquidation_discount = self.liquidation_discount
                self.liquidation_discounts[_for] = liquidation_discount
            log UserState(_for, xy[1], debt, n1, n2, liquidation_discount)
            log Repay(_for, 0, d_debt)
        else:
            # Underwater - cannot move band but can avoid a bad liquidation
            log UserState(_for, max_value(uint256), debt, ns[0], ns[1], liquidation_discount)
            log Repay(_for, 0, d_debt)

        if _for != msg.sender:
            # Doesn't allow non-sender to repay in a way which ends with unhealthy state
            # full = False to make this condition non-manipulatable (and also cheaper on gas)
            assert self._health(_for, debt, False, liquidation_discount) > 0

    # If we withdrew already - will burn less!
    STABLECOIN.transferFrom(msg.sender, self, d_debt)  # fail: insufficient funds
    self.redeemed += d_debt

    self.loan[_for] = Loan({initial_debt: debt, rate_mul: rate_mul})
    total_debt: uint256 = self._total_debt.initial_debt * rate_mul / self._total_debt.rate_mul
    self._total_debt.initial_debt = unsafe_sub(max(total_debt, d_debt), d_debt)
    self._total_debt.rate_mul = rate_mul


@external
@nonreentrant('lock')
def repay_extended(callbacker: address, callback_args: DynArray[uint256,5]):
    """
    @notice Repay loan but get a stablecoin for that from callback (to deleverage)
    @param callbacker Address of the callback contract
    @param callback_args Extra arguments for the callback (up to 5) such as min_amount etc
    """
    # Before callback
    ns: int256[2] = AMM.read_user_tick_numbers(msg.sender)
    xy: uint256[2] = AMM.withdraw(msg.sender, 10**18)
    debt: uint256 = 0
    rate_mul: uint256 = 0
    debt, rate_mul = self._debt(msg.sender)
    COLLATERAL_TOKEN.transferFrom(AMM.address, callbacker, xy[1], default_return_value=True)

    cb: CallbackData = self.execute_callback(
        callbacker, CALLBACK_REPAY, msg.sender, xy[0], xy[1], debt, callback_args)

    # After callback
    total_stablecoins: uint256 = cb.stablecoins + xy[0]
    assert total_stablecoins > 0  # dev: no coins to repay

    # d_debt: uint256 = min(debt, total_stablecoins)

    d_debt: uint256 = 0

    # If we have more stablecoins than the debt - full repayment and closing the position
    if total_stablecoins >= debt:
        d_debt = debt
        debt = 0
        self._remove_from_list(msg.sender)

        # Transfer debt to self, everything else to sender
        if cb.stablecoins > 0:
            STABLECOIN.transferFrom(callbacker, self, cb.stablecoins)
        if xy[0] > 0:
            STABLECOIN.transferFrom(AMM.address, self, xy[0])
        if total_stablecoins > d_debt:
            STABLECOIN.transfer(msg.sender, unsafe_sub(total_stablecoins, d_debt))
        if cb.collateral > 0:
            assert COLLATERAL_TOKEN.transferFrom(callbacker, msg.sender, cb.collateral, default_return_value=True)

        log UserState(msg.sender, 0, 0, 0, 0, 0)

    # Else - partial repayment -> deleverage, but only if we are not underwater
    else:
        size: uint256 = convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)
        assert ns[0] > cb.active_band
        d_debt = cb.stablecoins  # cb.stablecoins <= total_stablecoins < debt
        debt = unsafe_sub(debt, cb.stablecoins)

        # Not in liquidation - can move bands
        n1: int256 = self._calculate_debt_n1(cb.collateral, debt, size)
        n2: int256 = n1 + unsafe_sub(ns[1], ns[0])
        AMM.deposit_range(msg.sender, cb.collateral, n1, n2)
        liquidation_discount: uint256 = self.liquidation_discount
        self.liquidation_discounts[msg.sender] = liquidation_discount

        assert COLLATERAL_TOKEN.transferFrom(callbacker, AMM.address, cb.collateral, default_return_value=True)
        # Stablecoin is all spent to repay debt -> all goes to self
        STABLECOIN.transferFrom(callbacker, self, cb.stablecoins)
        # We are above active band, so xy[0] is 0 anyway

        log UserState(msg.sender, cb.collateral, debt, n1, n2, liquidation_discount)
        xy[1] -= cb.collateral

        # No need to check _health() because it's the sender

    # Common calls which we will do regardless of whether it's a full repay or not
    log Repay(msg.sender, xy[1], d_debt)
    self.redeemed += d_debt
    self.loan[msg.sender] = Loan({initial_debt: debt, rate_mul: rate_mul})
    total_debt: uint256 = self._total_debt.initial_debt * rate_mul / self._total_debt.rate_mul
    self._total_debt.initial_debt = unsafe_sub(max(total_debt, d_debt), d_debt)
    self._total_debt.rate_mul = rate_mul


@internal
@view
def _health(user: address, debt: uint256, full: bool, liquidation_discount: uint256) -> int256:
    """
    @notice Returns position health normalized to 1e18 for the user.
            Liquidation starts when < 0, however devaluation of collateral doesn't cause liquidation
    @param user User address to calculate health for
    @param debt The amount of debt to calculate health for
    @param full Whether to take into account the price difference above the highest user's band
    @param liquidation_discount Liquidation discount to use (can be 0)
    @return Health: > 0 = good.
    """
    assert debt > 0, "Loan doesn't exist"
    health: int256 = 10**18 - convert(liquidation_discount, int256)
    health = unsafe_div(convert(AMM.get_x_down(user), int256) * health, convert(debt, int256)) - 10**18

    if full:
        ns0: int256 = AMM.read_user_tick_numbers(user)[0] # ns[1] > ns[0]
        if ns0 > AMM.active_band():  # We are not in liquidation mode
            p: uint256 = AMM.price_oracle()
            p_up: uint256 = AMM.p_oracle_up(ns0)
            if p > p_up:
                health += convert(unsafe_div(unsafe_sub(p, p_up) * AMM.get_sum_xy(user)[1] * COLLATERAL_PRECISION, debt), int256)

    return health


@external
@view
@nonreentrant('lock')
def health_calculator(user: address, d_collateral: int256, d_debt: int256, full: bool, N: uint256 = 0) -> int256:
    """
    @notice Health predictor in case user changes the debt or collateral
    @param user Address of the user
    @param d_collateral Change in collateral amount (signed)
    @param d_debt Change in debt amount (signed)
    @param full Whether it's a 'full' health or not
    @param N Number of bands in case loan doesn't yet exist
    @return Signed health value
    """
    ns: int256[2] = AMM.read_user_tick_numbers(user)
    debt: int256 = convert(self._debt_ro(user), int256)
    n: uint256 = N
    ld: int256 = 0
    if debt != 0:
        ld = convert(self.liquidation_discounts[user], int256)
        n = convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)
    else:
        ld = convert(self.liquidation_discount, int256)
        ns[0] = max_value(int256)  # This will trigger a "re-deposit"

    n1: int256 = 0
    collateral: int256 = 0
    x_eff: int256 = 0
    debt += d_debt
    assert debt > 0, "Non-positive debt"

    active_band: int256 = AMM.active_band_with_skip()

    if ns[0] > active_band:  # re-deposit
        collateral = convert(AMM.get_sum_xy(user)[1], int256) + d_collateral
        n1 = self._calculate_debt_n1(convert(collateral, uint256), convert(debt, uint256), n)
        collateral *= convert(COLLATERAL_PRECISION, int256)  # now has 18 decimals
    else:
        n1 = ns[0]
        x_eff = convert(AMM.get_x_down(user) * 10**18, int256)

    p0: int256 = convert(AMM.p_oracle_up(n1), int256)
    if ns[0] > active_band:
        x_eff = convert(self.get_y_effective(convert(collateral, uint256), n, 0), int256) * p0

    health: int256 = unsafe_div(x_eff, debt)
    health = health - unsafe_div(health * ld, 10**18) - 10**18

    if full:
        if n1 > active_band:  # We are not in liquidation mode
            p_diff: int256 = max(p0, convert(AMM.price_oracle(), int256)) - p0
            if p_diff > 0:
                health += unsafe_div(p_diff * collateral, debt)

    return health


@internal
@view
def _get_f_remove(frac: uint256, health_limit: uint256) -> uint256:
    # f_remove = ((1 + h / 2) / (1 + h) * (1 - frac) + frac) * frac
    f_remove: uint256 = 10 ** 18
    if frac < 10 ** 18:
        f_remove = unsafe_div(unsafe_mul(unsafe_add(10 ** 18, unsafe_div(health_limit, 2)), unsafe_sub(10 ** 18, frac)), unsafe_add(10 ** 18, health_limit))
        f_remove = unsafe_div(unsafe_mul(unsafe_add(f_remove, frac), frac), 10 ** 18)

    return f_remove

@internal
def _liquidate(user: address, min_x: uint256, health_limit: uint256, frac: uint256, use_eth: bool,
               callbacker: address, callback_args: DynArray[uint256,5]):
    """
    @notice Perform a bad liquidation of user if the health is too bad
    @param user Address of the user
    @param min_x Minimal amount of stablecoin withdrawn (to avoid liquidators being sandwiched)
    @param health_limit Minimal health to liquidate at
    @param frac Fraction to liquidate; 100% = 10**18
    @param use_eth Use wrapping/unwrapping if collateral is ETH
    @param callbacker Address of the callback contract
    @param callback_args Extra arguments for the callback (up to 5) such as min_amount etc
    """
    debt: uint256 = 0
    rate_mul: uint256 = 0
    debt, rate_mul = self._debt(user)

    if health_limit != 0:
        assert self._health(user, debt, True, health_limit) < 0, "Not enough rekt"

    final_debt: uint256 = debt
    debt = unsafe_div(debt * frac, 10**18)
    assert debt > 0
    final_debt = unsafe_sub(final_debt, debt)

    # Withdraw sender's stablecoin and collateral to our contract
    # When frac is set - we withdraw a bit less for the same debt fraction
    # f_remove = ((1 + h/2) / (1 + h) * (1 - frac) + frac) * frac
    # where h is health limit.
    # This is less than full h discount but more than no discount
    xy: uint256[2] = AMM.withdraw(user, self._get_f_remove(frac, health_limit))  # [stable, collateral]

    # x increase in same block -> price up -> good
    # x decrease in same block -> price down -> bad
    assert xy[0] >= min_x, "Slippage"

    min_amm_burn: uint256 = min(xy[0], debt)
    if min_amm_burn != 0:
        STABLECOIN.transferFrom(AMM.address, self, min_amm_burn)

    if debt > xy[0]:
        to_repay: uint256 = unsafe_sub(debt, xy[0])

        if callbacker == empty(address):
            # Withdraw collateral if no callback is present
            self._withdraw_collateral(msg.sender, xy[1], use_eth)
            # Request what's left from user
            STABLECOIN.transferFrom(msg.sender, self, to_repay)

        else:
            # Move collateral to callbacker, call it and remove everything from it back in
            if xy[1] > 0:
                assert COLLATERAL_TOKEN.transferFrom(AMM.address, callbacker, xy[1], default_return_value=True)
            # Callback
            cb: CallbackData = self.execute_callback(
                callbacker, CALLBACK_LIQUIDATE, user, xy[0], xy[1], debt, callback_args)
            assert cb.stablecoins >= to_repay, "not enough proceeds"
            if cb.stablecoins > to_repay:
                STABLECOIN.transferFrom(callbacker, msg.sender, unsafe_sub(cb.stablecoins, to_repay))
            STABLECOIN.transferFrom(callbacker, self, to_repay)
            if cb.collateral > 0:
                assert COLLATERAL_TOKEN.transferFrom(callbacker, msg.sender, cb.collateral)

    else:
        # Withdraw collateral
        self._withdraw_collateral(msg.sender, xy[1], use_eth)
        # Return what's left to user
        if xy[0] > debt:
            STABLECOIN.transferFrom(AMM.address, msg.sender, unsafe_sub(xy[0], debt))

    self.redeemed += debt
    self.loan[user] = Loan({initial_debt: final_debt, rate_mul: rate_mul})
    log Repay(user, xy[1], debt)
    log Liquidate(msg.sender, user, xy[1], xy[0], debt)
    if final_debt == 0:
        log UserState(user, 0, 0, 0, 0, 0)  # Not logging partial removeal b/c we have not enough info
        self._remove_from_list(user)

    d: uint256 = self._total_debt.initial_debt * rate_mul / self._total_debt.rate_mul
    self._total_debt.initial_debt = unsafe_sub(max(d, debt), debt)
    self._total_debt.rate_mul = rate_mul


@external
@nonreentrant('lock')
def liquidate(user: address, min_x: uint256, use_eth: bool = True):
    """
    @notice Peform a bad liquidation (or self-liquidation) of user if health is not good
    @param min_x Minimal amount of stablecoin to receive (to avoid liquidators being sandwiched)
    @param use_eth Use wrapping/unwrapping if collateral is ETH
    """
    discount: uint256 = 0
    if user != msg.sender:
        discount = self.liquidation_discounts[user]
    self._liquidate(user, min_x, discount, 10**18, use_eth, empty(address), [])


@external
@nonreentrant('lock')
def liquidate_extended(user: address, min_x: uint256, frac: uint256, use_eth: bool,
                       callbacker: address, callback_args: DynArray[uint256,5]):
    """
    @notice Peform a bad liquidation (or self-liquidation) of user if health is not good
    @param min_x Minimal amount of stablecoin to receive (to avoid liquidators being sandwiched)
    @param frac Fraction to liquidate; 100% = 10**18
    @param use_eth Use wrapping/unwrapping if collateral is ETH
    @param callbacker Address of the callback contract
    @param callback_args Extra arguments for the callback (up to 5) such as min_amount etc
    """
    discount: uint256 = 0
    if user != msg.sender:
        discount = self.liquidation_discounts[user]
    self._liquidate(user, min_x, discount, min(frac, 10**18), use_eth, callbacker, callback_args)


@view
@external
@nonreentrant('lock')
def tokens_to_liquidate(user: address, frac: uint256 = 10 ** 18) -> uint256:
    """
    @notice Calculate the amount of stablecoins to have in liquidator's wallet to liquidate a user
    @param user Address of the user to liquidate
    @param frac Fraction to liquidate; 100% = 10**18
    @return The amount of stablecoins needed
    """
    health_limit: uint256 = 0
    if user != msg.sender:
        health_limit = self.liquidation_discounts[user]
    stablecoins: uint256 = unsafe_div(AMM.get_sum_xy(user)[0] * self._get_f_remove(frac, health_limit), 10 ** 18)
    debt: uint256 = unsafe_div(self._debt_ro(user) * frac, 10 ** 18)

    return unsafe_sub(max(debt, stablecoins), stablecoins)


@view
@external
@nonreentrant('lock')
def health(user: address, full: bool = False) -> int256:
    """
    @notice Returns position health normalized to 1e18 for the user.
            Liquidation starts when < 0, however devaluation of collateral doesn't cause liquidation
    """
    return self._health(user, self._debt_ro(user), full, self.liquidation_discounts[user])


@view
@external
@nonreentrant('lock')
def users_to_liquidate(_from: uint256=0, _limit: uint256=0) -> DynArray[Position, 1000]:
    """
    @notice Returns a dynamic array of users who can be "hard-liquidated".
            This method is designed for convenience of liquidation bots.
    @param _from Loan index to start iteration from
    @param _limit Number of loans to look over
    @return Dynamic array with detailed info about positions of users
    """
    n_loans: uint256 = self.n_loans
    limit: uint256 = _limit
    if _limit == 0:
        limit = n_loans
    ix: uint256 = _from
    out: DynArray[Position, 1000] = []
    for i in range(10**6):
        if ix >= n_loans or i == limit:
            break
        user: address = self.loans[ix]
        debt: uint256 = self._debt_ro(user)
        health: int256 = self._health(user, debt, True, self.liquidation_discounts[user])
        if health < 0:
            xy: uint256[2] = AMM.get_sum_xy(user)
            out.append(Position({
                user: user,
                x: xy[0],
                y: xy[1],
                debt: debt,
                health: health
            }))
        ix += 1
    return out


# AMM has a nonreentrant decorator
@view
@external
def amm_price() -> uint256:
    """
    @notice Current price from the AMM
    """
    return AMM.get_p()


@view
@external
@nonreentrant('lock')
def user_prices(user: address) -> uint256[2]:  # Upper, lower
    """
    @notice Lowest price of the lower band and highest price of the upper band the user has deposit in the AMM
    @param user User address
    @return (upper_price, lower_price)
    """
    assert AMM.has_liquidity(user)
    ns: int256[2] = AMM.read_user_tick_numbers(user) # ns[1] > ns[0]
    return [AMM.p_oracle_up(ns[0]), AMM.p_oracle_down(ns[1])]


@view
@external
@nonreentrant('lock')
def user_state(user: address) -> uint256[4]:
    """
    @notice Return the user state in one call
    @param user User to return the state for
    @return (collateral, stablecoin, debt, N)
    """
    xy: uint256[2] = AMM.get_sum_xy(user)
    ns: int256[2] = AMM.read_user_tick_numbers(user) # ns[1] > ns[0]
    return [xy[1], xy[0], self._debt_ro(user), convert(unsafe_add(unsafe_sub(ns[1], ns[0]), 1), uint256)]


# AMM has nonreentrant decorator
@external
def set_amm_fee(fee: uint256):
    """
    @notice Set the AMM fee (factory admin only)
    @param fee The fee which should be no higher than MAX_FEE
    """
    assert msg.sender == FACTORY.admin()
    assert fee <= MAX_FEE and fee >= MIN_FEE, "Fee"
    AMM.set_fee(fee)


# AMM has nonreentrant decorator
@external
def set_amm_admin_fee(fee: uint256):
    """
    @notice Set AMM's admin fee
    @param fee New admin fee (not higher than MAX_ADMIN_FEE)
    """
    assert msg.sender == FACTORY.admin()
    assert fee <= MAX_ADMIN_FEE, "High fee"
    AMM.set_admin_fee(fee)


@nonreentrant('lock')
@external
def set_monetary_policy(monetary_policy: address):
    """
    @notice Set monetary policy contract
    @param monetary_policy Address of the monetary policy contract
    """
    assert msg.sender == FACTORY.admin()
    self.monetary_policy = MonetaryPolicy(monetary_policy)
    MonetaryPolicy(monetary_policy).rate_write()
    log SetMonetaryPolicy(monetary_policy)


@nonreentrant('lock')
@external
def set_borrowing_discounts(loan_discount: uint256, liquidation_discount: uint256):
    """
    @notice Set discounts at which we can borrow (defines max LTV) and where bad liquidation starts
    @param loan_discount Discount which defines LTV
    @param liquidation_discount Discount where bad liquidation starts
    """
    assert msg.sender == FACTORY.admin()
    assert loan_discount > liquidation_discount
    assert liquidation_discount >= MIN_LIQUIDATION_DISCOUNT
    assert loan_discount <= MAX_LOAN_DISCOUNT
    self.liquidation_discount = liquidation_discount
    self.loan_discount = loan_discount
    log SetBorrowingDiscounts(loan_discount, liquidation_discount)


@external
@nonreentrant('lock')
def set_callback(cb: address):
    """
    @notice Set liquidity mining callback
    """
    assert msg.sender == FACTORY.admin()
    AMM.set_callback(cb)


@external
@view
def admin_fees() -> uint256:
    """
    @notice Calculate the amount of fees obtained from the interest
    """
    rate_mul: uint256 = AMM.get_rate_mul()
    loan: Loan = self._total_debt
    loan.initial_debt = loan.initial_debt * rate_mul / loan.rate_mul + self.redeemed
    minted: uint256 = self.minted
    return unsafe_sub(max(loan.initial_debt, minted), minted)


@external
@nonreentrant('lock')
def collect_fees() -> uint256:
    """
    @notice Collect the fees charged as interest
    """
    _to: address = FACTORY.fee_receiver()
    # AMM-based fees
    borrowed_fees: uint256 = AMM.admin_fees_x()
    collateral_fees: uint256 = AMM.admin_fees_y()
    if borrowed_fees > 0:
        STABLECOIN.transferFrom(AMM.address, _to, borrowed_fees)
    if collateral_fees > 0:
        assert COLLATERAL_TOKEN.transferFrom(AMM.address, _to, collateral_fees, default_return_value=True)
    AMM.reset_admin_fees()

    # Borrowing-based fees
    rate_mul: uint256 = self._rate_mul_w()
    loan: Loan = self._total_debt
    loan.initial_debt = loan.initial_debt * rate_mul / loan.rate_mul
    loan.rate_mul = rate_mul
    self._total_debt = loan

    # Amount which would have been redeemed if all the debt was repaid now
    to_be_redeemed: uint256 = loan.initial_debt + self.redeemed
    # Amount which was minted when borrowing + all previously claimed admin fees
    minted: uint256 = self.minted
    # Difference between to_be_redeemed and minted amount is exactly due to interest charged
    if to_be_redeemed > minted:
        self.minted = to_be_redeemed
        to_be_redeemed = unsafe_sub(to_be_redeemed, minted)  # Now this is the fees to charge
        STABLECOIN.transfer(_to, to_be_redeemed)
        log CollectFees(to_be_redeemed, loan.initial_debt)
        return to_be_redeemed
    else:
        log CollectFees(0, loan.initial_debt)
        return 0

Read Contract

admin_fees 0x1b1800e3 → uint256
amm 0x2a943945 → address
amm_price 0xd9f11a64 → uint256
calculate_debt_n1 0x720fb254 → int256
collateral_token 0x2621db2f → address
debt 0x9b6c56ec → uint256
factory 0xc45a0155 → address
health 0xe2d8ebee → int256
health 0x8908ea82 → int256
health_calculator 0x0b8db681 → int256
health_calculator 0x22c71453 → int256
liquidation_discount 0x627d2b83 → uint256
liquidation_discounts 0x5457ff7b → uint256
loan_discount 0x5449b9cb → uint256
loan_exists 0xa21adb9e → bool
loan_ix 0x7128f3b8 → uint256
loans 0xe1ec3c68 → address
max_borrowable 0x9a497196 → uint256
max_borrowable 0x1cf1f947 → uint256
min_collateral 0xa7573206 → uint256
minted 0x4f02c420 → uint256
monetary_policy 0xadfae4ce → address
n_loans 0x6cce39be → uint256
redeemed 0xe231bff0 → uint256
tokens_to_liquidate 0x1b25cdaf → uint256
tokens_to_liquidate 0x546e040d → uint256
total_debt 0x31dc3ca8 → uint256
user_prices 0x2c5089c3 → uint256[2]
user_state 0xec74d0a8 → uint256[4]
users_to_liquidate 0x007c98ab → tuple[]
users_to_liquidate 0x80e8f6ec → tuple[]
users_to_liquidate 0x90f8667d → tuple[]

Write Contract 21 functions

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

add_collateral 0x6f972f12
uint256 collateral
add_collateral 0x24049e57
uint256 collateral
address _for
borrow_more 0xdd171e7c
uint256 collateral
uint256 debt
collect_fees 0x1e0cfcef
No parameters
returns: uint256
create_loan 0x23cfed03
uint256 collateral
uint256 debt
uint256 N
create_loan_extended 0xbc61ea23
uint256 collateral
uint256 debt
uint256 N
address callbacker
uint256[] callback_args
liquidate 0xbcbaf487
address user
uint256 min_x
liquidate 0x3ecdb828
address user
uint256 min_x
bool use_eth
liquidate_extended 0x036aed88
address user
uint256 min_x
uint256 frac
bool use_eth
address callbacker
uint256[] callback_args
remove_collateral 0xd14ff5b6
uint256 collateral
remove_collateral 0x2e4af52a
uint256 collateral
bool use_eth
repay 0x371fd8e6
uint256 _d_debt
repay 0xacb70815
uint256 _d_debt
address _for
repay 0xb4440df4
uint256 _d_debt
address _for
int256 max_active_band
repay 0x37671f93
uint256 _d_debt
address _for
int256 max_active_band
bool use_eth
repay_extended 0x152f65cb
address callbacker
uint256[] callback_args
set_amm_admin_fee 0xa5b4804a
uint256 fee
set_amm_fee 0x4189617d
uint256 fee
set_borrowing_discounts 0x2a0c3586
uint256 loan_discount
uint256 liquidation_discount
set_callback 0xcc1891c7
address cb
set_monetary_policy 0x81d2f1b7
address monetary_policy

Recent Transactions

No transactions found for this address