Cryo Explorer Ethereum Mainnet

Address Contract Partially Verified

Address 0x3E11B9aEb9C7dBbda4DD41477223Cc2f3f24b9d7
Balance 0 ETH
Nonce 1
Code Size 22107 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

22107 bytes
0x6080604052600436106101125760003560e01c80633202937e116100a5578063ad20750111610074578063b7791bf211610059578063b7791bf2146105aa578063bb24fe8a146105cc578063f4e7bfd11461060057610119565b8063ad2075011461056b578063b5c736e41461058b57610119565b80633202937e146104d6578063540acabc146104f65780637bae3361146105385780639410ae881461054b57610119565b8063103f2907116100e1578063103f2907146103d95780631593a34b1461043257806322348cc71461045a5780632861c7d1146104a257610119565b806302161887146103305780630931bf2d1461036a57806309f0d8cb1461038b57806310259f26146103ab57610119565b3661011957005b34801561012557600080fd5b506040517f4502d0630000000000000000000000000000000000000000000000000000000081523360048201527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d73ffffffffffffffffffffffffffffffffffffffff1690634502d06390602401602060405180830381865afa1580156101b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d49190614e61565b8061028e57506040517fe04c8e5d0000000000000000000000000000000000000000000000000000000081523060048201523360248201527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d73ffffffffffffffffffffffffffffffffffffffff169063e04c8e5d90604401602060405180830381865afa15801561026a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061028e9190614e61565b6102cd576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792560048201526024015b60405180910390fd5b61032e7f000000000000000000000000076a9fe6051d66b6281e8ac26a8278330274c0046000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061061392505050565b005b34801561033c57600080fd5b5061034561065f565b6040805194855260208501939093529183015260608201526080015b60405180910390f35b61037d610378366004614ea0565b610780565b604051610361929190614ef5565b34801561039757600080fd5b506103456103a6366004614f43565b6108b9565b6103be6103b9366004614ea0565b610d9d565b60408051938452602084019290925290820152606001610361565b3480156103e557600080fd5b5061040d7f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d81565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610361565b610445610440366004614f5c565b610ed8565b60408051928352602083019190915201610361565b34801561046657600080fd5b5061047a610475366004614f5c565b61100c565b604080519586526020860194909452928401919091526060830152608082015260a001610361565b3480156104ae57600080fd5b5061040d7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49781565b3480156104e257600080fd5b5061032e6104f1366004614f8e565b6112cc565b34801561050257600080fd5b5061052a7f000000000000000000000000000000000000000000000000000000000000002d81565b604051908152602001610361565b610445610546366004614fbe565b611362565b34801561055757600080fd5b5061032e61056636600461500e565b6114f0565b34801561057757600080fd5b5061032e61058636600461503a565b611630565b34801561059757600080fd5b5061052a6105a6366004614f43565b5490565b3480156105b657600080fd5b506105bf611723565b60405161036191906150c3565b3480156105d857600080fd5b5061052a7f000000000000000000000000000000000000000000000000000000000000753081565b61034561060e366004614fbe565b611aad565b6060600080835160208501865af43d6040519250601f19601f6020830101168301604052808352806000602085013e81156001810361065657816000803e816000fd5b50505092915050565b6000806000806106706001546108b9565b9296509094509250905067ffffffffffffffff841180610697575067ffffffffffffffff83115b806106a9575067ffffffffffffffff82115b806106bb575067ffffffffffffffff81115b156106f6576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792d60048201526024016102c4565b604083811b8517608084901b1760c083901b17600855600180547ffffffffffffffffffffffffff800000003ffffffffffffffffffffffffffffff1642607a1b179055517fcde545703e0372175cadfff811d67c32910c3dcb33199679b3271c4106afdf9a906107729084908490918252602082015260400190565b60405180910390a190919293565b600b5460009060609073ffffffffffffffffffffffffffffffffffffffff1661dead146107dd576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793d60048201526024016102c4565b600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001633179055604080516020601f36908101829004820283018201909352828252610869927f000000000000000000000000b18937d457c383d49c55ca4dacbb25a063f2b5709291600091908190840183828082843760009201919091525061061392505050565b80602001905181019061087c91906152ea565b91509150600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790559097909650945050505050565b60085460009081908190819067ffffffffffffffff80821690604083901c16836108ee607a8a901c6401ffffffff1642615401565b9050614e207f000000000000000000000000000000000000000000000000000000000000753014806109415750619c407f0000000000000000000000000000000000000000000000000000000000007530145b156109f35764e8d4a51000975067ffffffffffffffff608085901c169550886001166001036109b25761097a6301e13380612710615414565b60018a901c617fff1661098d8389615414565b6109979190615414565b6109a1919061545a565b6109ab9087615495565b9550610b68565b6109c26301e13380612710615414565b60018a901c617fff166109d58389615414565b6109df9190615414565b6109e9919061545a565b6109ab9087615401565b6040517fb5c736e40000000000000000000000000000000000000000000000000000000081527fa1829a9003092132f585b6ccdd167c19fe9774dbdea4260287e8a8e8ca8185d76004820152610acb907f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff169063b5c736e4906024015b602060405180830381865afa158015610aa2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ac691906154a8565b611c23565b50975082881015610b0c576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793260048201526024016102c4565b61271061ffff8a16670de0b6b3a7640000858b670de0b6b3a76400000281610b3657610b3661542b565b04030281610b4657610b4661542b565b670de0b6b3a7640000919004810167ffffffffffffffff608087901c16020495505b6175307f00000000000000000000000000000000000000000000000000000000000075301480610bb95750619c407f0000000000000000000000000000000000000000000000000000000000007530145b15610c655764e8d4a51000965060c084901c9450600160108a901c81169003610c2457610bec6301e13380612710615414565b60118a901c617fff16610bff8388615414565b610c099190615414565b610c13919061545a565b610c1d9086615495565b9450610d92565b610c346301e13380612710615414565b60118a901c617fff16610c478388615414565b610c519190615414565b610c5b919061545a565b610c1d9086615401565b6040517fb5c736e40000000000000000000000000000000000000000000000000000000081527f00000000000000000000000000000000000000000000000000000000000000006004820152610cfb907f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff169063b5c736e490602401610a85565b97505081871015610d3c576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793260048201526024016102c4565b61271061ffff60108b901c16670de0b6b3a7640000848a670de0b6b3a76400000281610d6a57610d6a61542b565b04030281610d7a57610d7a61542b565b670de0b6b3a7640000919004810160c086901c020494505b505050509193509193565b600b546000908190819073ffffffffffffffffffffffffffffffffffffffff1661dead14610dfb576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793d60048201526024016102c4565b600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001633179055604080516020601f36908101829004820283018201909352828252610e87927f000000000000000000000000b18937d457c383d49c55ca4dacbb25a063f2b5709291600091908190840183828082843760009201919091525061061392505050565b806020019051810190610e9a91906154c1565b600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead179055919b909a509098509650505050505050565b600b54600090819073ffffffffffffffffffffffffffffffffffffffff1661dead14610f34576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793d60048201526024016102c4565b600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001633179055604080516020601f36908101829004820283018201909352828252610fc0927f000000000000000000000000f38478d055020bbc173734dc62d88b749eb99b599291600091908190840183828082843760009201919091525061061392505050565b806020019051810190610fd391906154ef565b600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790559097909650945050505050565b600080808080868180600189901c62ffffff168b9003611052575050601a87901c633fffffff169250603887901c6603ffffffffffff166001601989901c8116146110a9565b60008c8152600660205260408120600360028e0181810660550292918491048152602081019190915260400160002054901c600181811c633fffffff169750601f82901c6603ffffffffffff169450908116149150505b600085815260076020526040902054935080156110ec577f80000000000000000000000000000000000000000000000000000000000000009b50600099506112b8565b836003166002036111565761110e82607486901c6603ffffffffffff16611e31565b9150600161111e600f6023615495565b6001901b61112c9190615401565b82146111565760a69390931c633fffffff16600081815260076020526040902054909450926110ec565b836003166003148061118257506001611171600f6023615495565b6001901b61117f9190615401565b82145b156111b3577f80000000000000000000000000000000000000000000000000000000000000009b50600099506112b8565b6111cb8a607486901c6603ffffffffffff1684611ecb565b9950606483048a11156111e85761271061270f8b020499506111ed565b600099505b89156112945783600416600414611210576207ffff600385901c1660000361121b565b6207ffff600385901c165b9b50600061122b8d60020b611f10565b905061271f612710820204633fffffff601687901c811661124c8385615401565b6112569190615414565b611260919061545a565b61126a9082615495565b6112816c010000000000000000000000008e615414565b61128b919061545a565b975050506112b8565b7f80000000000000000000000000000000000000000000000000000000000000009b505b8b8a97509750505050945094509450945094565b60008054906001821690036112e7576001811760005561131d565b6040517f60121cca00000000000000000000000000000000000000000000000000000000815261791960048201526024016102c4565b8215611329578261133b565b6fffffffffffffffffffffffffffffffff5b925061135d6fffffffffffffffffffffffffffffffff600061dead8585612172565b600080fd5b600b54600090819073ffffffffffffffffffffffffffffffffffffffff1661dead146113be576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793d60048201526024016102c4565b600b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000163317905560008054906001821690036112e75760018117600055600061140a3447615401565b905073ffffffffffffffffffffffffffffffffffffffff86161561142e5785611430565b335b9550600061143f8b8b8b613a6b565b905061144e8189898987612172565b80602001905181019061146191906154c1565b91965094509250808510156114a6576040517f60121cca0000000000000000000000000000000000000000000000000000000081526188ba60048201526024016102c4565b60008390556114b482613c4d565b5050600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead179055509097909650945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49716148061156957503373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b16145b6115a3576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793c60048201526024016102c4565b6000546001166000036115e6576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792a60048201526024016102c4565b600b5461162c90839073ffffffffffffffffffffffffffffffffffffffff167f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49784613ca1565b5050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49716146116a3576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792960048201526024016102c4565b6000546001166000036116e6576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792a60048201526024016102c4565b61171d846116f683850185615513565b7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49786613ca1565b50505050565b6117c2604080516102008101825260008082526020808301829052828401829052606083018290526080830182905260a0830182905260c0830182905260e0830182905283518085018552828152808201839052610100840152835180850190945281845283015290610120820190815260006020820181905260408201819052606082018190526080820181905260a0820181905260c09091015290565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497811682527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d81166020808401919091527f000000000000000000000000b18937d457c383d49c55ca4dacbb25a063f2b570821660408401527f000000000000000000000000076a9fe6051d66b6281e8ac26a8278330274c004821660608401527f000000000000000000000000f38478d055020bbc173734dc62d88b749eb99b59821660808401527f0000000000000000000000004ec7b668baf70d4a4b0fc7941a7708a07b6d45be821660a08401527f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497821660c08401527f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b821660e0840152610100830180517f000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee84169052517f0000000000000000000000000000000000000000000000000000000000000000831690820152610120830180517f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4884169052517f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec79092169101527f000000000000000000000000000000000000000000000000000000000000002d6101408201527f00000000000000000000000000000000000000000000000000000000000075306101608201527fa1829a9003092132f585b6ccdd167c19fe9774dbdea4260287e8a8e8ca8185d76101808201527f00000000000000000000000000000000000000000000000000000000000000006101a08201527fcc6f1a2fcba559efe5ab4493b2d3ca55ebd399eefc6ef5dc66e57730fc45eb716101c08201527f66478f32a67300f9ec7d0327e9394ebb12c7744c7aed89aeab790f4c0beb4e196101e082015290565b600b5460009081908190819073ffffffffffffffffffffffffffffffffffffffff1661dead14611b0d576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793d60048201526024016102c4565b600b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000163317905560408051808201909152600080825260208201526000805480835260011690036112e7578051600117600055611b6d3447615401565b602082015273ffffffffffffffffffffffffffffffffffffffff871615611b945786611b96565b335b9650611ba98b8989898560000151612172565b806020019051810190611bbc91906154c1565b83529095509150611bce858b8b613d68565b825160005560208301519195509350611be690613c4d565b50600b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790559299919850965090945092505050565b67ffffffffffffffff605b82901c811690609b83901c16811580611c45575080155b15611c81576040517fd50d75120000000000000000000000000000000000000000000000000000000081526201117160048201526024016102c4565b61ffff8316603a84901c6401ffffffff16428181039160ea87901c617fff16911480611cab575082155b80611cb65750806001145b15611cc357505050915091565b64496cebb80084840283020484019350617fff60db87901c16925082600103611cee57505050915091565b82600116600103611d435760019290921c91826c7e37be2022c0914b268000000081611d1c57611d1c61542b565b049250612710601e87901c613fff166b033b2e3c9fd0803ce8000000850102049250611d70565b60019290921c916305f5e100601e87901c613fff166127108501026b033b2e3c9fd0803ce8000000020492505b80600116600103611da75760011c61271081016b033b2e3c9fd0803ce8000000820281611d9f57611d9f61542b565b049050611ddd565b60011c61271081016b033b2e3c9fd0803ce8000000820281611dcb57611dcb61542b565b046b033b2e3c9fd0803ce80000000390505b760a70c3c40a64e6c51999090b65f67d92400000000000008382026127100261ffff881691900402601087901c613fff16612710030292506801b5a660ea44b8000085840283020485019450505050915091565b6000600f83811c9083901c0281681fffffffffffffffff8211611e55576022611e58565b60235b91821c919050617fff858116908516018101614000811015611e7957600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00001617fff811115611eb857506603ffffffffffff9250611ec5915050565b80600f84901b1793505050505b92915050565b6000617fff838116908316036081811015611f0357600f83811c821b9085901c860281611efa57611efa61542b565b04915050611f09565b60009150505b9392505050565b600060ff82901d80831803617fff811115611f2a57600080fd5b7001000000000000000000000000000000006001821615611f5857506fff9dd7de423466c20352b1246ce4856f5b6002821615611f77576fff3bd55f4488ad277531fa1c725a66d00260801c5b6004821615611f96576ffe78410fd6498b73cb96a6917f8532590260801c5b6008821615611fb5576ffcf2d9987c9be178ad5bfeffaa1232730260801c5b6010821615611fd4576ff9ef02c4529258b057769680fc6601b30260801c5b6020821615611ff3576ff402d288133a85a17784a411f7aba0820260801c5b6040821615612012576fe895615b5beb6386553757b0352bda900260801c5b6080821615612031576fd34f17a00ffa00a8309940a15930391a0260801c5b610100821615612051576fae6b7961714e20548d88ea5123f9a0ff0260801c5b610200821615612071576f76d6461f27082d74e0feed3b388c0ca10260801c5b610400821615612091576f372a3bfe0745d8b6b19d985d9a8b85bb0260801c5b6108008216156120b1576f0be32cbee48979763cf7247dd7bb539d0260801c5b6110008216156120d0576e8d4f70c9ff4924dac37612d1e2921e0260801c5b6120008216156120ee576d4e009ae5519380809a02ca7aec770260801c5b61400082161561210a576b17c45e641b6e95dee056ff100260801c5b600091507f80000000000000000000000000000000000000000000000000000000000000008416612168577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0463ffffffff81161561216857600191505b60201c0192915050565b60606121b46040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6001548152600283901c620fffff16600003612200576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791f60048201526024016102c4565b805161220b906108b9565b909192509091508260600183608001828152508281525050506122886040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6122c16040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000806122fc7f0000000000000000000000004ec7b668baf70d4a4b0fc7941a7708a07b6d45be633fffffff605c8860000151901c16614243565b73ffffffffffffffffffffffffffffffffffffffff1663f3190c896040518163ffffffff1660e01b8152600401602060405180830381865afa158015612346573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061236a91906154a8565b9150760a70c3c40a64e6c51999090b65f67d924000000000000082118061238f575081155b156123ca576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793960048201526024016102c4565b846080015185606001518302816123e3576123e361542b565b049150722cd76fe086b93ce2f768a00b22a0000000000082111561241857722cd76fe086b93ce2f768a00b22a0000000000091505b81760a70c3c40a64e6c51999090b65f67d92400000000000008161243e5761243e61542b565b865191900491506127109060481c6103ff16810182020460608501526b033b2e3c9fd0803ce80000006c010000000000000000000000008302865191900492506103e890602a1c6103ff168302049050612497816146ad565b50602086015284516103e89060341c6103ff1683020490506124b8816146ad565b5060408601526004808816146124da576207ffff600388901c166000036124e5565b6207ffff600388901c165b60c085018190526040860151121561263a576125b67f000000000000000000000000f38478d055020bbc173734dc62d88b749eb99b5988876040015160405160240161253b929190918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f9e3e482100000000000000000000000000000000000000000000000000000000179052610613565b8060200190518101906125c991906154a8565b9650866004166004146125e8576207ffff600388901c166000036125f3565b6207ffff600388901c165b60c085015260008b900361263a5760408051600060208201819052918101919091526060810188905260800160405160208183030381529060405295505050505050613a62565b6127108b108061265957506fffffffffffffffffffffffffffffffff8b115b15612694576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792e60048201526024016102c4565b60028716156126a45760026126a7565b60015b60ff16846101000181815250506126f46040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60c08501518152601688901c633fffffff16808552600090815260076020908152604082205490860181905260741c6603ffffffffffff16606086018190529003612747576603ffffffffc00060608501525b602084015160c41c620fffff169250821561278e5782600116600114612779576207ffff600184901c16600003612784565b6207ffff600184901c165b60808501526127b5565b7f800000000000000000000000000000000000000000000000000000000000000060808501525b856080015164e8d4a510008d02816127cf576127cf61542b565b0480865266ffffffffffffff609a8a901c1660ff60928b901c161b92506127fa633b9aca008461545a565b1115612836576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792e60048201526024016102c4565b88156128c95760025485516fffffffffffffffffffffffffffffffff8216945060809190911c92508311156128ae57845183906128739084615414565b61287d919061545a565b60a0860181905261288e9083615401565b855160808088018290526000885282901b940393841760025591506128c9565b6000600255845183900385526080850183905260a085018290525b85602001518560c00151131561347657845115613476576040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a081018290528190680100000000000000009060008960c0015112612945576101008960c001518161293f5761293f61542b565b05612962565b60016101008a60c001516001018161295f5761295f61542b565b05035b6040820152845161297290611f10565b604086015261010089015160010361299d57604085015160e08a015260c08901516020820152612ac2565b61271f856040015161271002816129b6576129b661542b565b046060860181905260408601510360808601819052602089015160161c633fffffff90811660c0880181905290910260608701519190040160e08a0152845160208b0151600101148015612a0e57508460c001516001145b15612ac2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff215373ffffffffffffffffffffffffffffffffffffffff8f1601612a8c576040517fcc36aa4400000000000000000000000000000000000000000000000000000000815260006004820181905260248201526044016102c4565b6040517f60121cca00000000000000000000000000000000000000000000000000000000815261793560048201526024016102c4565b886101000151600103612b325760c0890180516000908152600560205260408082205460608c01518c5194518452919092206301fffffe8316601a9490941b9390931760389190911b1760011790915566ffffffffffffff602182901c1660ff601983901c161b97509550612b55565b67ffffffffffffffff60348960200151901c16965060ff8716600888901c901b96505b8689602001818151612b679190615495565b90525060e0890151612b866c0100000000000000000000000089615414565b612b90919061545a565b89604001818151612ba19190615495565b90525060c08901516020820151148015612bc057508861010001516001145b80612bcd57506080810151155b15612cfd578060800151600003612bf65760408082015160009081526004602052205460808201525b886101000151600103612c4d57604081015160c08a015160009081036101009283020190910160608301819052608083018051821b90911c908190529003612c4d5760408082015160009081526004602052908120555b608081015115612c7b576001612c668260800151614ac2565b60408301516101000201036020820152612cfd565b89602001518160400151610100021215612cba577f80000000000000000000000000000000000000000000000000000000000000006020820152612cfd565b604080820180517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01908190526000908152600460205220546080820152612c4d565b80602001518860800151138015612d1b575089602001518860800151135b15612d385760808801516101208a015260026101608a0152612d74565b896020015181602001511315612d605760208101516101208a015260016101608a0152612d74565b60208a01516101208a015260036101608a01525b612d8589610120015160020b611f10565b6040860152610160890151600203612e195761271f85604001516127100281612db057612db061542b565b046060860181905260408087015191909103608087019081526020808b015160a61c633fffffff908116600090815260079092529290205460a08b0181905260161c821660c0880181905290510260608701519190040160a086018190526101408a0152612e36565b604085015160a086018190526101408a0152633fffffff60c08601525b6c010000000000000000000000008961014001518a60600151612e599190615414565b612e63919061545a565b612e79906b033b2e3c9fd0803ce8000000615401565b8960e001518a602001518b6101400151612e939190615414565b612e9d919061545a565b8a60200151612eac9190615401565b612ec2906b033b2e3c9fd0803ce8000000615414565b612ecc919061545a565b93506b033b2e3c9fd0803ce8000000896060015185612eeb9190615414565b612ef5919061545a565b925083896020015103612f1057612f0d600185615401565b93505b885184101580612f2557508861016001516003145b156132905760808101516040808301516000908152600460205220558851841061310557885160608a01519094506b033b2e3c9fd0803ce800000090612f6b9086615414565b612f75919061545a565b9250828960400151612f879190615401565b6c01000000000000000000000000858b60200151612fa59190615401565b612faf9190615414565b612fb9919061545a565b9650612fc4876146ad565b60608701528086526101208a0151138015612fe65750633fffffff8560c00151145b15612ffe5784516002018552600160c0860152613118565b84516001018552610160890151600214801561301f57506101208901518551145b61302a576000613030565b8460c001515b9550612710856060015161271f028161304b5761304b61542b565b046040860181905260608601519081900360808701819052908803633fffffff02816130795761307961542b565b0460c08601819052156130aa57633fffffff8560c0015110156130a0578460c001516130ad565b633ffffffe6130ad565b60015b60c086015285158015906130c557508460c001518610155b15613100576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793860048201526024016102c4565b613118565b6101208901516001908101865260c08601525b60208901516131278582615401565b6131319084615414565b61313b919061545a565b9150838960800181815161314f9190615495565b905250602089018051859190613166908390615401565b90525060a08901805184919061317d908390615495565b905250604089018051849190613194908390615401565b90525060608801516131a69083614b78565b60608901526020890151606411156131ee576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793060048201526024016102c4565b8451600013613204578451600190811b1761320d565b845160000360011b5b606089015160208b015191975060741b9060349061323090603860086001614c2b565b901b60168760c00151901b600289901b60a6808d60200151901c901b60011717171717600760008a6000015181526020019081526020016000208190555060168860000151901b600287901b6034808f901c901b60021717179b50613471565b8851849003895260208901516132a68582615401565b6132b09084615414565b6132ba919061545a565b915083896080018181516132ce9190615495565b9052506020890180518591906132e5908390615401565b90525060a0890180518491906132fc908390615495565b905250604089018051849190613313908390615401565b90525060608801516133259083614b78565b606089015261016089015168010000000000000000925060020361344a57633fffffff60a68960200151901c1696508760a00151955060006603ffffffffffff607488901c16905060006133868a6060015183614d5490919063ffffffff16565b6020808c0180518d516000908152600790935260409092207fffffffffffffffffffffffc00000000000000000000000000000000000000000909216607484901b17600217909155998b5298889052506060890181905260c487901c620fffff16978815613420578860011660011461340b576207ffff60018a901c16600003613416565b6207ffff60018a901c165b60808b0152613447565b7f800000000000000000000000000000000000000000000000000000000000000060808b01525b50505b61012089015160c08a01526101608901516101008a015261014089015160e08a0152612ac2565b505050505b64e8d4a51000866080015186608001516134909190615414565b61349a919061545a565b8660a001818152505064e8d4a5100086606001518660a001516134bd9190615414565b6134c7919061545a565b60c087015260a08601518c10156135035760a08601516134e7908d61545a565b8660c001516134f69190615414565b60c087015260a086018c90525b8560a00151600003613545576040517f60121cca00000000000000000000000000000000000000000000000000000000815261793560048201526024016102c4565b8a8660a001518760c00151670de0b6b3a76400006135639190615414565b61356d919061545a565b10156135a9576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792160048201526024016102c4565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff215373ffffffffffffffffffffffffffffffffffffffff8b160161362b5760c086015160a08701516040517fcc36aa44000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016102c4565b6175307f0000000000000000000000000000000000000000000000000000000000007530148061367c5750619c407f0000000000000000000000000000000000000000000000000000000000007530145b6137e1577fffffffffffffffffffffffff11111111111111111111111111111111111111127f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b73ffffffffffffffffffffffffffffffffffffffff16016136e9578560a0015192506136ee565b600092505b7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff1663ad967e15847f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b60008a60a0015161375c90615530565b604080513360208201526000918291016040516020818303038152906040526040518863ffffffff1660e01b815260040161379c969594939291906155cc565b604080518083038185885af11580156137b9573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906137de91906154ef565b50505b614e207f000000000000000000000000000000000000000000000000000000000000753014806138325750619c407f0000000000000000000000000000000000000000000000000000000000007530145b61391b577f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff1663ad967e157f000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8860c001516138a190615530565b604080516000808252602082019092528f9082906040518763ffffffff1660e01b81526004016138d6969594939291906155cc565b60408051808303816000875af11580156138f4573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061391891906154ef565b50505b67ffffffffffffffff605289901c1692508460a0015160ff8416600885901c901b6139469190615401565b925067ffffffffffffffff609289901c169150846080015160ff8316600884901c901b6139739190615401565b9150609261398683603860086001614c2b565b901b605261399985603860086000614c2b565b60c089015160a08a015160408051338152602081019390935282015273ffffffffffffffffffffffffffffffffffffffff8e1660608201527ffffffffffffc00000000000000000000000000000003ffffffffffffffffffff909b16911b1717977f80fd9cc6b1821f4a510e45ffce6852ea3404807b5d3d833ffa85664408afcb669060800160405180910390a160a086015160c08701516040805160208101939093528201526060810189905260800160405160208183030381529060405296505050505050505b95945050505050565b600083158015613a79575082155b15613ab4576040517f60121cca0000000000000000000000000000000000000000000000000000000081526188b960048201526024016102c4565b7f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b73ffffffffffffffffffffffffffffffffffffffff16636876698173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff1614613bb5577f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec773ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14613baf576000613bb7565b84613bb7565b855b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526004810188905260248101879052604481018690526000606482015260840160206040518083038185885af1158015613c20573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613c4591906154a8565b949350505050565b303181811115613c635761162c33838303614dff565b8181101561162c576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791b60048201526024016102c4565b60006040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff841660248201528260448201526020600060648360008a5af13d15601f3d1160016000511416171691505080613d61576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155960048201526024016102c4565b5050505050565b60008080670de0b6b3a7640000613d7f8787615414565b613d89919061545a565b90506000670de0b6b3a7640000613da08887615414565b613daa919061545a565b9050600082118015613dbc5750600081115b15613f5b577f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b73ffffffffffffffffffffffffffffffffffffffff16635b3d38d773eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff1614613ec2577f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec773ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14613ebc576000613ec4565b82613ec4565b835b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b168152600481018b9052602481018690526044810185905260006064820152608401604080518083038185885af1158015613f2c573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190613f5191906154ef565b9094509250614239565b600082118015613f69575080155b156140af577f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b73ffffffffffffffffffffffffffffffffffffffff166330acd6fd73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff161461401857600061401a565b835b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b168152600481018b905260248101869052604481018590526000606482015260840160206040518083038185885af1158015614083573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906140a891906154a8565b9350614239565b811580156140bd5750600081115b15614203577f000000000000000000000000667701e51b4d1ca244f17c78f7ab8744b4c99f9b73ffffffffffffffffffffffffffffffffffffffff166330acd6fd73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec773ffffffffffffffffffffffffffffffffffffffff161461416c57600061416e565b825b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b168152600481018b905260248101869052604481018590526000606482015260840160206040518083038185885af11580156141d7573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906141fc91906154a8565b9250614239565b6040517f60121cca0000000000000000000000000000000000000000000000000000000081526188b960048201526024016102c4565b5050935093915050565b6000606082600003614259576000915050611ec5565b607f8311614322576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527f940000000000000000000000000000000000000000000000000000000000000060218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606086901b16602282015260f884901b7fff000000000000000000000000000000000000000000000000000000000000001660368201526037015b604051602081830303815290604052905061469e565b60ff83116143ff576040517fd70000000000000000000000000000000000000000000000000000000000000060208201527f940000000000000000000000000000000000000000000000000000000000000060218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606086901b1660228201527f8100000000000000000000000000000000000000000000000000000000000000603682015260f884901b7fff0000000000000000000000000000000000000000000000000000000000000016603782015260380161430c565b61ffff83116144dd576040517fd80000000000000000000000000000000000000000000000000000000000000060208201527f940000000000000000000000000000000000000000000000000000000000000060218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606086901b1660228201527f820000000000000000000000000000000000000000000000000000000000000060368201527fffff00000000000000000000000000000000000000000000000000000000000060f085901b16603782015260390161430c565b62ffffff83116145bc576040517fd90000000000000000000000000000000000000000000000000000000000000060208201527f940000000000000000000000000000000000000000000000000000000000000060218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606086901b1660228201527f830000000000000000000000000000000000000000000000000000000000000060368201527fffffff000000000000000000000000000000000000000000000000000000000060e885901b166037820152603a0161430c565b6040517fda0000000000000000000000000000000000000000000000000000000000000060208201527f940000000000000000000000000000000000000000000000000000000000000060218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606086901b1660228201527f840000000000000000000000000000000000000000000000000000000000000060368201527fffffffff0000000000000000000000000000000000000000000000000000000060e085901b166037820152603b0160405160208183030381529060405290505b80516020909101209392505050565b600080630235b88083107473d85bca016a2338b31715f8e13054c005f8b995d3841117156146da57600080fd5b6c01000000000000000000000000831060008161471057506c010000000000000000000000006a52b7d2dcc80cd2e40000008502045b811561473357507652b7d2dcc80cd2e40000000000000000000000000000008490045b6f037af932b2affa9738cc6c38ca527831811061477457614000841793506f037af932b2affa9738cc6c38ca5278316a52b7d2dcc80cd2e400000082020490505b6d010f7a088a76f267264caa114f0a81106147b157612000841793506d010f7a088a76f267264caa114f0a6a52b7d2dcc80cd2e400000082020490505b6b95da74f87f839fc2e0dc5bd981106147ea57611000841793506b95da74f87f839fc2e0dc5bd96a52b7d2dcc80cd2e400000082020490505b6b06f55dedafd8491caed5a1b8811061482357610800841793506b06f55dedafd8491caed5a1b86a52b7d2dcc80cd2e400000082020490505b6b017fdd10ee11e624491b4cc1811061485c57610400841793506b017fdd10ee11e624491b4cc16a52b7d2dcc80cd2e400000082020490505b6ab23131bf0c30217b0a2c69811061489357610200841793506ab23131bf0c30217b0a2c696a52b7d2dcc80cd2e400000082020490505b6a79683edcb9280d797aded781106148ca57610100841793506a79683edcb9280d797aded76a52b7d2dcc80cd2e400000082020490505b6a64366e2f9919f0d9b0dc908110614900576080841793506a64366e2f9919f0d9b0dc906a52b7d2dcc80cd2e400000082020490505b6a5b0bcda5a78850646b0a818110614936576040841793506a5b0bcda5a78850646b0a816a52b7d2dcc80cd2e400000082020490505b6a56c840f992c70f959ae810811061496c576020841793506a56c840f992c70f959ae8106a52b7d2dcc80cd2e400000082020490505b6a54b9cd178695194f9be0a081106149a2576010841793506a54b9cd178695194f9be0a06a52b7d2dcc80cd2e400000082020490505b6a53b7458aff204b5e65d68181106149d8576008841793506a53b7458aff204b5e65d6816a52b7d2dcc80cd2e400000082020490505b6a53372a2f38c240d689e4008110614a0e576004841793506a53372a2f38c240d689e4006a52b7d2dcc80cd2e400000082020490505b6a52f76617a04499e66400008110614a44576002841793506a52f76617a04499e66400006a52b7d2dcc80cd2e400000082020490505b6a52d79660f3dec355c000008110614a7a576001841793506a52d79660f3dec355c000006a52b7d2dcc80cd2e400000082020490505b81614a9257806a52b7d2dcc80cd2e400000086020492505b8115614aae579219926a52d79660f3dec355c000008582020492505b505082811115614abd57600080fd5b915091565b6000816fffffffffffffffffffffffffffffffff831115614ae45760809150811c5b67ffffffffffffffff811115614afc576040918201911c5b63ffffffff811115614b10576020918201911c5b61ffff811115614b22576010918201911c5b60ff811115614b33576008918201911c5b600f811115614b44576004918201911c5b6003811115614b55576002918201911c5b6001811115614b65576001820191505b8015614b72576001820191505b50919050565b6000600f83901c8202816c03ffffffffffffffffffffffff8211614bbf576c01ffffffffffffffffffffffff8211614bb857614bb382614ac2565b614bc2565b6062614bc2565b60635b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdd617fff87168201810194500191821c919050604083111561135d5750600f1b017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00192915050565b600080856fffffffffffffffffffffffffffffffff811115614c4e5760809150811c5b67ffffffffffffffff811115614c66576040918201911c5b63ffffffff811115614c7a576020918201911c5b61ffff811115614c8c576010918201911c5b60ff811115614c9d576008918201911c5b600f811115614cae576004918201911c5b6003811115614cbf576002918201911c5b6001811115614ccf576001820191505b8015614cdc576001820191505b5084811015614ce85750835b848103905085811c60008211841615614d3757600181019050806001871b03614d3757506001908101907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86011b5b6001851b8210614d4657600080fd5b90931b909201949350505050565b600080600f83901c7fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000603186901b1681614d9057614d9061542b565b0490506000604082901c600114614da8576040614dab565b60415b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdd81019290921c91617fff8581166040019250613fdd908716909101018181111561135d5703600f9190911b179050611ec5565b60008060008060008587614e20f1905080614e4b576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155a60048201526024016102c4565b505050565b8015158114614e5e57600080fd5b50565b600060208284031215614e7357600080fd5b8151611f0981614e50565b73ffffffffffffffffffffffffffffffffffffffff81168114614e5e57600080fd5b60008060008060008060c08789031215614eb957600080fd5b863595506020870135945060408701359350606087013592506080870135915060a0870135614ee781614e7e565b809150509295509295509295565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015614f3657845183529383019391830191600101614f1a565b5090979650505050505050565b600060208284031215614f5557600080fd5b5035919050565b60008060008060808587031215614f7257600080fd5b5050823594602084013594506040840135936060013592509050565b60008060408385031215614fa157600080fd5b823591506020830135614fb381614e50565b809150509250929050565b60008060008060008060c08789031215614fd757600080fd5b863595506020870135945060408701359350606087013592506080870135614ffe81614e7e565b915060a0870135614ee781614e50565b6000806040838503121561502157600080fd5b823561502c81614e7e565b946020939093013593505050565b6000806000806060858703121561505057600080fd5b843561505b81614e7e565b935060208501359250604085013567ffffffffffffffff8082111561507f57600080fd5b818701915087601f83011261509357600080fd5b8135818111156150a257600080fd5b8860208285010111156150b457600080fd5b95989497505060200194505050565b815173ffffffffffffffffffffffffffffffffffffffff16815261024081016020830151615109602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040830151615131604084018273ffffffffffffffffffffffffffffffffffffffff169052565b506060830151615159606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080830151615181608084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a08301516151a960a084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c08301516151d160c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e08301516151f960e084018273ffffffffffffffffffffffffffffffffffffffff169052565b5061010083810151805173ffffffffffffffffffffffffffffffffffffffff90811685840152602082015116610120850152505061012083015161014061526581850183805173ffffffffffffffffffffffffffffffffffffffff908116835260209182015116910152565b840151610180848101919091526101608501516101a080860191909152908501516101c080860191909152908501516101e080860191909152908501516102008501529093015161022090920191909152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080604083850312156152fd57600080fd5b8251915060208084015167ffffffffffffffff8082111561531d57600080fd5b818601915086601f83011261533157600080fd5b815181811115615343576153436152bb565b8060051b6040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f83011681018181108582111715615386576153866152bb565b6040529182528482019250838101850191898311156153a457600080fd5b938501935b828510156153c2578451845293850193928501926153a9565b8096505050505050509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115611ec557611ec56153d2565b8082028115828204841417611ec557611ec56153d2565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615490577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b80820180821115611ec557611ec56153d2565b6000602082840312156154ba57600080fd5b5051919050565b6000806000606084860312156154d657600080fd5b8351925060208401519150604084015190509250925092565b6000806040838503121561550257600080fd5b505080516020909101519092909150565b60006020828403121561552557600080fd5b8135611f0981614e7e565b60007f80000000000000000000000000000000000000000000000000000000000000008203615561576155616153d2565b5060000390565b6000815180845260005b8181101561558e57602081850181015186830182015201615572565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b600073ffffffffffffffffffffffffffffffffffffffff8089168352876020840152866040840152808616606084015280851660808401525060c060a083015261561960c0830184615568565b9897505050505050505056fea2646970667358221220a81faba3a766d7ace10d3c55a900910aac6bc920cc4392cf7f423faee479f5ad64736f6c63430008150033

Verified Source Code Partial Match

Compiler: v0.8.21+commit.d9974bed EVM: paris Optimization: Yes (10000000 runs)
IERC721Enumerable.sol 29 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}
IERC721.sol 145 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

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

interface IProxy {
    function setAdmin(address newAdmin_) external;

    function setDummyImplementation(address newDummyImplementation_) external;

    function addImplementation(address implementation_, bytes4[] calldata sigs_) external;

    function removeImplementation(address implementation_) external;

    function getAdmin() external view returns (address);

    function getDummyImplementation() external view returns (address);

    function getImplementationSigs(address impl_) external view returns (bytes4[] memory);

    function getSigsImplementation(bytes4 sig_) external view returns (address);

    function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
}
addressCalcs.sol 35 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

/// @notice implements calculation of address for contracts deployed through CREATE.
/// Accepts contract deployed from which address & nonce
library AddressCalcs {

    /// @notice                         Computes the address of a contract based
    /// @param deployedFrom_            Address from which the contract was deployed
    /// @param nonce_                   Nonce at which the contract was deployed
    /// @return contract_               Address of deployed contract
    function addressCalc(address deployedFrom_, uint nonce_) internal pure returns (address contract_) {
        // @dev based on https://ethereum.stackexchange.com/a/61413

        // nonce of smart contract always starts with 1. so, with nonce 0 there won't be any deployment
        // hence, nonce of vault deployment starts with 1.
        bytes memory data;
        if (nonce_ == 0x00) {
            return address(0);
        } else if (nonce_ <= 0x7f) {
            data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployedFrom_, uint8(nonce_));
        } else if (nonce_ <= 0xff) {
            data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployedFrom_, bytes1(0x81), uint8(nonce_));
        } else if (nonce_ <= 0xffff) {
            data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployedFrom_, bytes1(0x82), uint16(nonce_));
        } else if (nonce_ <= 0xffffff) {
            data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployedFrom_, bytes1(0x83), uint24(nonce_));
        } else {
            data = abi.encodePacked(bytes1(0xda), bytes1(0x94), deployedFrom_, bytes1(0x84), uint32(nonce_));
        }

        return address(uint160(uint256(keccak256(data))));
    }

}
bigMathMinified.sol 156 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

/// @title library that represents a number in BigNumber(coefficient and exponent) format to store in smaller bits.
/// @notice the number is divided into two parts: a coefficient and an exponent. This comes at a cost of losing some precision
/// at the end of the number because the exponent simply fills it with zeroes. This precision is oftentimes negligible and can
/// result in significant gas cost reduction due to storage space reduction.
/// Also note, a valid big number is as follows: if the exponent is > 0, then coefficient last bits should be occupied to have max precision.
/// @dev roundUp is more like a increase 1, which happens everytime for the same number.
/// roundDown simply sets trailing digits after coefficientSize to zero (floor), only once for the same number.
library BigMathMinified {
    /// @dev constants to use for `roundUp` input param to increase readability
    bool internal constant ROUND_DOWN = false;
    bool internal constant ROUND_UP = true;

    /// @dev converts `normal` number to BigNumber with `exponent` and `coefficient` (or precision).
    /// e.g.:
    /// 5035703444687813576399599 (normal) = (coefficient[32bits], exponent[8bits])[40bits]
    /// 5035703444687813576399599 (decimal) => 10000101010010110100000011111011110010100110100000000011100101001101001101011101111 (binary)
    ///                                     => 10000101010010110100000011111011000000000000000000000000000000000000000000000000000
    ///                                                                        ^-------------------- 51(exponent) -------------- ^
    /// coefficient = 1000,0101,0100,1011,0100,0000,1111,1011               (2236301563)
    /// exponent =                                            0011,0011     (51)
    /// bigNumber =   1000,0101,0100,1011,0100,0000,1111,1011,0011,0011     (572493200179)
    ///
    /// @param normal number which needs to be converted into Big Number
    /// @param coefficientSize at max how many bits of precision there should be (64 = uint64 (64 bits precision))
    /// @param exponentSize at max how many bits of exponent there should be (8 = uint8 (8 bits exponent))
    /// @param roundUp signals if result should be rounded down or up
    /// @return bigNumber converted bigNumber (coefficient << exponent)
    function toBigNumber(
        uint256 normal,
        uint256 coefficientSize,
        uint256 exponentSize,
        bool roundUp
    ) internal pure returns (uint256 bigNumber) {
        assembly {
            let lastBit_
            let number_ := normal
            if gt(number_, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                number_ := shr(0x80, number_)
                lastBit_ := 0x80
            }
            if gt(number_, 0xFFFFFFFFFFFFFFFF) {
                number_ := shr(0x40, number_)
                lastBit_ := add(lastBit_, 0x40)
            }
            if gt(number_, 0xFFFFFFFF) {
                number_ := shr(0x20, number_)
                lastBit_ := add(lastBit_, 0x20)
            }
            if gt(number_, 0xFFFF) {
                number_ := shr(0x10, number_)
                lastBit_ := add(lastBit_, 0x10)
            }
            if gt(number_, 0xFF) {
                number_ := shr(0x8, number_)
                lastBit_ := add(lastBit_, 0x8)
            }
            if gt(number_, 0xF) {
                number_ := shr(0x4, number_)
                lastBit_ := add(lastBit_, 0x4)
            }
            if gt(number_, 0x3) {
                number_ := shr(0x2, number_)
                lastBit_ := add(lastBit_, 0x2)
            }
            if gt(number_, 0x1) {
                lastBit_ := add(lastBit_, 1)
            }
            if gt(number_, 0) {
                lastBit_ := add(lastBit_, 1)
            }
            if lt(lastBit_, coefficientSize) {
                // for throw exception
                lastBit_ := coefficientSize
            }
            let exponent := sub(lastBit_, coefficientSize)
            let coefficient := shr(exponent, normal)
            if and(roundUp, gt(exponent, 0)) {
                // rounding up is only needed if exponent is > 0, as otherwise the coefficient fully holds the original number
                coefficient := add(coefficient, 1)
                if eq(shl(coefficientSize, 1), coefficient) {
                    // case were coefficient was e.g. 111, with adding 1 it became 1000 (in binary) and coefficientSize 3 bits
                    // final coefficient would exceed it's size. -> reduce coefficent to 100 and increase exponent by 1.
                    coefficient := shl(sub(coefficientSize, 1), 1)
                    exponent := add(exponent, 1)
                }
            }
            if iszero(lt(exponent, shl(exponentSize, 1))) {
                // if exponent is >= exponentSize, the normal number is too big to fit within
                // BigNumber with too small sizes for coefficient and exponent
                revert(0, 0)
            }
            bigNumber := shl(exponentSize, coefficient)
            bigNumber := add(bigNumber, exponent)
        }
    }

    /// @dev get `normal` number from `bigNumber`, `exponentSize` and `exponentMask`
    function fromBigNumber(
        uint256 bigNumber,
        uint256 exponentSize,
        uint256 exponentMask
    ) internal pure returns (uint256 normal) {
        assembly {
            let coefficient := shr(exponentSize, bigNumber)
            let exponent := and(bigNumber, exponentMask)
            normal := shl(exponent, coefficient)
        }
    }

    /// @dev gets the most significant bit `lastBit` of a `normal` number (length of given number of binary format).
    /// e.g.
    /// 5035703444687813576399599 = 10000101010010110100000011111011110010100110100000000011100101001101001101011101111
    /// lastBit =                   ^---------------------------------   83   ----------------------------------------^
    function mostSignificantBit(uint256 normal) internal pure returns (uint lastBit) {
        assembly {
            let number_ := normal
            if gt(normal, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                number_ := shr(0x80, number_)
                lastBit := 0x80
            }
            if gt(number_, 0xFFFFFFFFFFFFFFFF) {
                number_ := shr(0x40, number_)
                lastBit := add(lastBit, 0x40)
            }
            if gt(number_, 0xFFFFFFFF) {
                number_ := shr(0x20, number_)
                lastBit := add(lastBit, 0x20)
            }
            if gt(number_, 0xFFFF) {
                number_ := shr(0x10, number_)
                lastBit := add(lastBit, 0x10)
            }
            if gt(number_, 0xFF) {
                number_ := shr(0x8, number_)
                lastBit := add(lastBit, 0x8)
            }
            if gt(number_, 0xF) {
                number_ := shr(0x4, number_)
                lastBit := add(lastBit, 0x4)
            }
            if gt(number_, 0x3) {
                number_ := shr(0x2, number_)
                lastBit := add(lastBit, 0x2)
            }
            if gt(number_, 0x1) {
                lastBit := add(lastBit, 1)
            }
            if gt(number_, 0) {
                lastBit := add(lastBit, 1)
            }
        }
    }
}
bigMathVault.sol 205 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { BigMathMinified } from "./bigMathMinified.sol";

/// @title Extended version of BigMathMinified. Implements functions for normal operators (*, /, etc) modified to interact with big numbers.
/// @notice this is an optimized version mainly created by taking Fluid vault's codebase into consideration so it's use is limited for other cases.
// 
// @dev IMPORTANT: for any change here, make sure to uncomment and run the fuzz tests in bigMathVault.t.sol
library BigMathVault {
    uint private constant COEFFICIENT_SIZE_DEBT_FACTOR = 35;
    uint private constant EXPONENT_SIZE_DEBT_FACTOR = 15;
    uint private constant COEFFICIENT_MAX_DEBT_FACTOR = (1 << COEFFICIENT_SIZE_DEBT_FACTOR) - 1;
    uint private constant EXPONENT_MAX_DEBT_FACTOR = (1 << EXPONENT_SIZE_DEBT_FACTOR) - 1;
    uint private constant DECIMALS_DEBT_FACTOR = 16384;
    uint internal constant MAX_MASK_DEBT_FACTOR = (1 << (COEFFICIENT_SIZE_DEBT_FACTOR + EXPONENT_SIZE_DEBT_FACTOR)) - 1;

    // Having precision as 2**64 on vault
    uint internal constant PRECISION = 64;
    uint internal constant TWO_POWER_64 = 1 << PRECISION;
    // Max bit for 35 bits * 35 bits number will be 70
    // why do we use 69 then here instead of 70
    uint internal constant TWO_POWER_69_MINUS_1 = (1 << 69) - 1;

    uint private constant COEFFICIENT_PLUS_PRECISION = COEFFICIENT_SIZE_DEBT_FACTOR + PRECISION; // 99
    uint private constant COEFFICIENT_PLUS_PRECISION_MINUS_1 = COEFFICIENT_PLUS_PRECISION - 1; // 98
    uint private constant TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1 = (1 << COEFFICIENT_PLUS_PRECISION_MINUS_1) - 1; // (1 << 98) - 1;
    uint private constant TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1_MINUS_1 =
        (1 << (COEFFICIENT_PLUS_PRECISION_MINUS_1 - 1)) - 1; // (1 << 97) - 1;

    /// @dev multiplies a `normal` number with a `bigNumber1` and then divides by `bigNumber2`.
    /// @dev For vault's use case MUST always:
    ///      - bigNumbers have exponent size 15 bits
    ///      - bigNumbers have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
    ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
    ///      - bigNumber1 (debt factor) always have exponent >= 1 & <= 16384
    ///      - bigNumber2 (connection factor) always have exponent >= 1 & <= 32767 (15 bits)
    ///      - bigNumber2 always >= bigNumber1 (connection factor can never be < base branch debt factor)
    ///      - as a result of previous points, numbers must never be 0
    ///      - normal is positionRawDebt and is always within 10000 and type(int128).max
    /// @return normal * bigNumber1 / bigNumber2
    function mulDivNormal(uint256 normal, uint256 bigNumber1, uint256 bigNumber2) internal pure returns (uint256) {
        unchecked {
            // exponent2_ - exponent1_
            uint netExponent_ = (bigNumber2 & EXPONENT_MAX_DEBT_FACTOR) - (bigNumber1 & EXPONENT_MAX_DEBT_FACTOR);
            if (netExponent_ < 129) {
                // (normal * coefficient1_) / (coefficient2_ << netExponent_);
                return ((normal * (bigNumber1 >> EXPONENT_SIZE_DEBT_FACTOR)) /
                    ((bigNumber2 >> EXPONENT_SIZE_DEBT_FACTOR) << netExponent_));
            }
            // else:
            // biggest possible nominator: type(int128).max * 35bits max      =  5846006549323611672814739330865132078589370433536
            // smallest possible denominator: 17179869184 << 129 (= 1 << 163) = 11692013098647223345629478661730264157247460343808
            // -> can only ever be 0
            return 0;
        }
    }

    /// @dev multiplies a `bigNumber` with normal `number1` and then divides by `TWO_POWER_64`.
    /// @dev For vault's use case (calculating new branch debt factor after liquidation):
    ///      - number1 is debtFactor, intialized as TWO_POWER_64 and reduced from there, hence it's always <= TWO_POWER_64 and always > 0.
    ///      - bigNumber is branch debt factor, which starts as ((X35 << 15) | (1 << 14)) and reduces from there.
    ///      - bigNumber must have have exponent size 15 bits and be >= 1 & <= 16384
    ///      - bigNumber must have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
    ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
    /// @param bigNumber Coefficient | Exponent.
    /// @param number1 normal number.
    /// @return result bigNumber * number1 / TWO_POWER_64.
    function mulDivBigNumber(uint256 bigNumber, uint256 number1) internal pure returns (uint256 result) {
        // using unchecked as we are only at 1 place in Vault and it won't overflow there.
        unchecked {
            uint256 _resultNumerator = (bigNumber >> EXPONENT_SIZE_DEBT_FACTOR) * number1; // bigNumber coefficient * normal number
            // 99% chances are that most sig bit should be 64 + 35 - 1 or 64 + 35 - 2
            // diff = mostSigBit. Can only ever be >= 35 and <= 98
            uint256 diff = (_resultNumerator > TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1)
                ? COEFFICIENT_PLUS_PRECISION
                : (_resultNumerator > TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1_MINUS_1)
                    ? COEFFICIENT_PLUS_PRECISION_MINUS_1
                    : BigMathMinified.mostSignificantBit(_resultNumerator);

            // diff = difference in bits to make the _resultNumerator 35 bits again
            diff = diff - COEFFICIENT_SIZE_DEBT_FACTOR;
            _resultNumerator = _resultNumerator >> diff;
            // starting exponent is 16384, so exponent should never get 0 here
            result = (bigNumber & EXPONENT_MAX_DEBT_FACTOR) + diff;
            if (result > PRECISION) {
                result = (_resultNumerator << EXPONENT_SIZE_DEBT_FACTOR) + result - PRECISION; // divides by TWO_POWER_64 by reducing exponent by 64
            } else {
                // if number1 is small, e.g. 1e4 and bigNumber is also small e.g. coefficient = 17179869184 & exponent is at 50
                // then: resultNumerator = 171798691840000, diff most significant bit = 48, ending up with diff = 13
                // for exponent in result we end up doing: 50 + 13 - 64 -> underflowing exponent.
                // this should never happen anyway, but if it does better to revert than to continue with unknown effects.
                revert(); // debt factor should never become a BigNumber with exponent <= 0
            }
        }
    }

    /// @dev multiplies a `bigNumber1` with another `bigNumber2`.
    /// @dev For vault's use case (calculating connection factor of merged branches userTickDebtFactor * connectionDebtFactor *... connectionDebtFactor):
    ///      - bigNumbers must have have exponent size 15 bits and be >= 1 & <= 32767
    ///      - bigNumber must have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
    ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
    /// @dev sum of exponents from `bigNumber1` `bigNumber2` should be > 16384.
    /// e.g. res = bigNumber1 * bigNumber2 = [(coe1, exp1) * (coe2, exp2)] >> decimal
    ///          = (coe1*coe2>>overflow, exp1+exp2+overflow-decimal)
    /// @param bigNumber1          BigNumber format with coefficient and exponent.
    /// @param bigNumber2          BigNumber format with coefficient and exponent.
    /// @return                    BigNumber format with coefficient and exponent
    function mulBigNumber(uint256 bigNumber1, uint256 bigNumber2) internal pure returns (uint256) {
        unchecked {
            // coefficient1_ * coefficient2_
            uint resCoefficient_ = (bigNumber1 >> EXPONENT_SIZE_DEBT_FACTOR) *
                (bigNumber2 >> EXPONENT_SIZE_DEBT_FACTOR);
            // res coefficient at min can be 17179869184 * 17179869184 =  295147905179352825856 (= 1 << 68; 69th bit as 1)
            // res coefficient at max can be 34359738367 * 34359738367 = 1180591620648691826689 (X35 * X35 fits in 70 bits)
            uint overflowLen_ = resCoefficient_ > TWO_POWER_69_MINUS_1
                ? COEFFICIENT_SIZE_DEBT_FACTOR
                : COEFFICIENT_SIZE_DEBT_FACTOR - 1;
            // overflowLen_ is either 34 or 35
            resCoefficient_ = resCoefficient_ >> overflowLen_;

            // bigNumber2 is connection factor
            // exponent1_ + exponent2_ + overflowLen_ - decimals
            uint resExponent_ = ((bigNumber1 & EXPONENT_MAX_DEBT_FACTOR) +
                (bigNumber2 & EXPONENT_MAX_DEBT_FACTOR) +
                overflowLen_);
            if (resExponent_ < DECIMALS_DEBT_FACTOR) {
                // for this ever to happen, the debt factors used to calculate connection factors would have to be at extremely
                // unrealistic values. Like e.g.
                // branch3 (debt factor X35 << 15 | 16383) got merged into branch2 (debt factor X35 << 15 | 8190)
                // -> connection factor (divBigNumber): ((coe1<<precision_)/coe2>>overflowLen, exp1+decimal+overflowLen-exp2-precision_) so:
                // coefficient: (X35<<64)/X35 >> 30 = 17179869184
                // exponent: 8190+16384+30-16383-64 = 8157.
                // result: 17179869184 << 15 | 8157
                // and then branch2 into branch1 (debt factor X35 << 15 | 22). -> connection factor:
                // coefficient: (X35<<64)/X35 >> 30 = 17179869184
                // exponent: 22+16384+30-8190-64 = 8182.
                // result: 17179869184 << 15 | 8182
                // connection factors sum up (mulBigNumber): (coe1*coe2>>overflow, exp1+exp2+overflow-decimal)
                // exponent: 8182+8157+35-16384=16374-16384=-10. underflow.
                // this should never happen anyway, but if it does better to revert than to continue with unknown effects.
                revert();
            }
            resExponent_ = resExponent_ - DECIMALS_DEBT_FACTOR;

            if (resExponent_ > EXPONENT_MAX_DEBT_FACTOR) {
                // if resExponent_ is not within limits that means user's got ~100% (something like 99.999999999999...)
                // this situation will probably never happen and this basically means user's position is ~100% liquidated
                return MAX_MASK_DEBT_FACTOR;
            }

            return ((resCoefficient_ << EXPONENT_SIZE_DEBT_FACTOR) | resExponent_);
        }
    }

    /// @dev divides a `bigNumber1` by `bigNumber2`.
    /// @dev For vault's use case (calculating connectionFactor_ = baseBranchDebtFactor / currentBranchDebtFactor) bigNumbers MUST always:
    ///      - have exponent size 15 bits and be >= 1 & <= 16384
    ///      - have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
    ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
    ///      - as a result of previous points, numbers must never be 0
    /// e.g. res = bigNumber1 / bigNumber2 = [(coe1, exp1) / (coe2, exp2)] << decimal
    ///          = ((coe1<<precision_)/coe2, exp1+decimal-exp2-precision_)
    /// @param bigNumber1          BigNumber format with coefficient and exponent
    /// @param bigNumber2          BigNumber format with coefficient and exponent
    /// @return                    BigNumber format with coefficient and exponent
    /// Returned connection factor can only ever be >= baseBranchDebtFactor (c = x*100/y with both x,y > 0 & x,y <= 100: c can only ever be >= x)
    function divBigNumber(uint256 bigNumber1, uint256 bigNumber2) internal pure returns (uint256) {
        unchecked {
            // (coefficient1_ << PRECISION) / coefficient2_
            uint256 resCoefficient_ = ((bigNumber1 >> EXPONENT_SIZE_DEBT_FACTOR) << PRECISION) /
                (bigNumber2 >> EXPONENT_SIZE_DEBT_FACTOR);
            // nominator at min 17179869184 << 64 = 316912650057057350374175801344. at max 34359738367 << 64 = 633825300095667956674642051072.
            // so min value resCoefficient_ 9223372037123211264 (64 bits) vs max 36893488146345361408 (fits in 65 bits)

            // mostSigBit will be PRECISION + 1 or PRECISION
            uint256 overflowLen_ = ((resCoefficient_ >> PRECISION) == 1) ? (PRECISION + 1) : PRECISION;
            // Overflow will be PRECISION - COEFFICIENT_SIZE_DEBT_FACTOR or (PRECISION + 1) - COEFFICIENT_SIZE_DEBT_FACTOR
            // Meaning 64 - 35 = 29 or 65 - 35 = 30
            overflowLen_ = overflowLen_ - COEFFICIENT_SIZE_DEBT_FACTOR;
            resCoefficient_ = resCoefficient_ >> overflowLen_;

            // exponent1_ will always be less than or equal to 16384
            // exponent2_ will always be less than or equal to 16384
            // Even if exponent2_ is 0 (not possible) & resExponent_ = DECIMALS_DEBT_FACTOR then also resExponent_ will be less than max limit, so no overflow
            // result exponent = (exponent1_ + DECIMALS_DEBT_FACTOR + overflowLen_) - (exponent2_ + PRECISION);
            uint256 resExponent_ = ((bigNumber1 & EXPONENT_MAX_DEBT_FACTOR) + // exponent1_
                DECIMALS_DEBT_FACTOR + // DECIMALS_DEBT_FACTOR is 100% as it is percentage value
                overflowLen_); // addition part resExponent_ here min 16414, max 32798
            // reuse overFlowLen_ variable for subtraction sum of exponent
            overflowLen_ = (bigNumber2 & EXPONENT_MAX_DEBT_FACTOR) + PRECISION; // subtraction part overflowLen_ here: min 65, max 16448
            if (resExponent_ > overflowLen_) {
                resExponent_ = resExponent_ - overflowLen_;

                return ((resCoefficient_ << EXPONENT_SIZE_DEBT_FACTOR) | resExponent_);
            }

            // Can happen if bigNumber1 exponent is < 35 (35+16384+29 = 16448) and bigNumber2 exponent is e.g. max 16384.
            // this would mean a branch with a normal big debt factor (bigNumber2) is merged into a base branch with an extremely small
            // debt factor (bigNumber1).
            // this should never happen anyway, but if it does better to revert than to continue with unknown effects.
            revert(); // connection factor should never become a BigNumber with exponent <= 0
        }
    }
}
errorTypes.sol 27 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

library LibsErrorTypes {
    /***********************************|
    |         LiquidityCalcs            | 
    |__________________________________*/

    /// @notice thrown when supply or borrow exchange price is zero at calc token data (token not configured yet)
    uint256 internal constant LiquidityCalcs__ExchangePriceZero = 70001;

    /// @notice thrown when rate data is set to a version that is not implemented
    uint256 internal constant LiquidityCalcs__UnsupportedRateVersion = 70002;

    /// @notice thrown when the calculated borrow rate turns negative. This should never happen.
    uint256 internal constant LiquidityCalcs__BorrowRateNegative = 70003;

    /***********************************|
    |           SafeTransfer            | 
    |__________________________________*/

    /// @notice thrown when safe transfer from for an ERC20 fails
    uint256 internal constant SafeTransfer__TransferFromFailed = 71001;

    /// @notice thrown when safe transfer for an ERC20 fails
    uint256 internal constant SafeTransfer__TransferFailed = 71002;
}
fluidProtocolTypes.sol 52 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

interface IFluidProtocol {
    function TYPE() external view returns (uint256);
}

/// @notice implements helper methods to filter Fluid protocols by a certain type
library FluidProtocolTypes {
    uint256 internal constant VAULT_T1_TYPE = 10000; // VaultT1 borrow protocol type vaults
    uint256 internal constant VAULT_T2_SMART_COL_TYPE = 20000; // DEX protocol type vault
    uint256 internal constant VAULT_T3_SMART_DEBT_TYPE = 30000; // DEX protocol type vault
    uint256 internal constant VAULT_T4_SMART_COL_SMART_DEBT_TYPE = 40000; // DEX protocol type vault

    /// @dev filters input `addresses_` by protocol `type_`. Input addresses must be actual Fluid protocols, otherwise
    ///      they would be wrongly assumed to be VaultT1 even if they are not Fluid VaultT1 smart contracts.
    ///      `type_` must be a listed constant type of this library.
    ///      Example usage is to filter all vault addresses at the Vault factory by a certain type, e.g. to not include
    ///      DEX protocol type vaults.
    function filterBy(address[] memory addresses_, uint256 type_) internal view returns (address[] memory filtered_) {
        uint256 curType_;
        uint256 filteredProtocols_ = addresses_.length;
        for (uint256 i; i < addresses_.length; ) {
            try IFluidProtocol(addresses_[i]).TYPE() returns (uint256 protocolType_) {
                curType_ = protocolType_;
            } catch {
                curType_ = VAULT_T1_TYPE;
            }

            if (curType_ != type_) {
                addresses_[i] = address(0);
                --filteredProtocols_;
            }

            unchecked {
                ++i;
            }
        }

        filtered_ = new address[](filteredProtocols_);
        uint256 index_;
        unchecked {
            for (uint256 i; i < addresses_.length; ) {
                if (addresses_[i] != address(0)) {
                    filtered_[index_] = addresses_[i];
                    ++index_;
                }
                ++i;
            }
        }
    }
}
liquidityCalcs.sol 686 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
import { LiquiditySlotsLink } from "./liquiditySlotsLink.sol";
import { BigMathMinified } from "./bigMathMinified.sol";

/// @notice implements calculation methods used for Fluid liquidity such as updated exchange prices,
/// borrow rate, withdrawal / borrow limits, revenue amount.
library LiquidityCalcs {
    error FluidLiquidityCalcsError(uint256 errorId_);

    /// @notice emitted if the calculated borrow rate surpassed max borrow rate (16 bits) and was capped at maximum value 65535
    event BorrowRateMaxCap();

    /// @dev constants as from Liquidity variables.sol
    uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;

    /// @dev Ignoring leap years
    uint256 internal constant SECONDS_PER_YEAR = 365 days;
    // constants used for BigMath conversion from and to storage
    uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
    uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;

    uint256 internal constant FOUR_DECIMALS = 1e4;
    uint256 internal constant TWELVE_DECIMALS = 1e12;
    uint256 internal constant X14 = 0x3fff;
    uint256 internal constant X15 = 0x7fff;
    uint256 internal constant X16 = 0xffff;
    uint256 internal constant X18 = 0x3ffff;
    uint256 internal constant X24 = 0xffffff;
    uint256 internal constant X33 = 0x1ffffffff;
    uint256 internal constant X64 = 0xffffffffffffffff;

    ///////////////////////////////////////////////////////////////////////////
    //////////                  CALC EXCHANGE PRICES                  /////////
    ///////////////////////////////////////////////////////////////////////////

    /// @dev calculates interest (exchange prices) for a token given its' exchangePricesAndConfig from storage.
    /// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
    /// @return supplyExchangePrice_ updated supplyExchangePrice
    /// @return borrowExchangePrice_ updated borrowExchangePrice
    function calcExchangePrices(
        uint256 exchangePricesAndConfig_
    ) internal view returns (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) {
        // Extracting exchange prices
        supplyExchangePrice_ =
            (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) &
            X64;
        borrowExchangePrice_ =
            (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) &
            X64;

        if (supplyExchangePrice_ == 0 || borrowExchangePrice_ == 0) {
            revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__ExchangePriceZero);
        }

        uint256 temp_ = exchangePricesAndConfig_ & X16; // temp_ = borrowRate

        unchecked {
            // last timestamp can not be > current timestamp
            uint256 secondsSinceLastUpdate_ = block.timestamp -
                ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) & X33);

            uint256 borrowRatio_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO) &
                X15;
            if (secondsSinceLastUpdate_ == 0 || temp_ == 0 || borrowRatio_ == 1) {
                // if no time passed, borrow rate is 0, or no raw borrowings: no exchange price update needed
                // (if borrowRatio_ == 1 means there is only borrowInterestFree, as first bit is 1 and rest is 0)
                return (supplyExchangePrice_, borrowExchangePrice_);
            }

            // calculate new borrow exchange price.
            // formula borrowExchangePriceIncrease: previous price * borrow rate * secondsSinceLastUpdate_.
            // nominator is max uint112 (uint64 * uint16 * uint32). Divisor can not be 0.
            borrowExchangePrice_ +=
                (borrowExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
                (SECONDS_PER_YEAR * FOUR_DECIMALS);

            // FOR SUPPLY EXCHANGE PRICE:
            // all yield paid by borrowers (in mode with interest) goes to suppliers in mode with interest.
            // formula: previous price * supply rate * secondsSinceLastUpdate_.
            // where supply rate = (borrow rate  - revenueFee%) * ratioSupplyYield. And
            // ratioSupplyYield = utilization * supplyRatio * borrowRatio
            //
            // Example:
            // supplyRawInterest is 80, supplyInterestFree is 20. totalSupply is 100. BorrowedRawInterest is 50.
            // BorrowInterestFree is 10. TotalBorrow is 60. borrow rate 40%, revenueFee 10%.
            // yield is 10 (so half a year must have passed).
            // supplyRawInterest must become worth 89. totalSupply must become 109. BorrowedRawInterest must become 60.
            // borrowInterestFree must still be 10. supplyInterestFree still 20. totalBorrow 70.
            // supplyExchangePrice would have to go from 1 to 1,125 (+ 0.125). borrowExchangePrice from 1 to 1,2 (+0.2).
            // utilization is 60%. supplyRatio = 20 / 80 = 25% (only 80% of lenders receiving yield).
            // borrowRatio = 10 / 50 = 20% (only 83,333% of borrowers paying yield):
            // x of borrowers paying yield = 100% - (20 / (100 + 20)) = 100% - 16.6666666% = 83,333%.
            // ratioSupplyYield = 60% * 83,33333% * (100% + 20%) = 62,5%
            // supplyRate = (40% * (100% - 10%)) * = 36% * 62,5% = 22.5%
            // increase in supplyExchangePrice, assuming 100 as previous price.
            // 100 * 22,5% * 1/2 (half a year) = 0,1125.
            // cross-check supplyRawInterest worth = 80 * 1.1125 = 89. totalSupply worth = 89 + 20.

            // -------------- 1. calculate ratioSupplyYield --------------------------------
            // step1: utilization * supplyRatio (or actually part of lenders receiving yield)

            // temp_ => supplyRatio (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
            // if first bit 0 then ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
            // else ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
            temp_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) & X15;

            if (temp_ == 1) {
                // if no raw supply: no exchange price update needed
                // (if supplyRatio_ == 1 means there is only supplyInterestFree, as first bit is 1 and rest is 0)
                return (supplyExchangePrice_, borrowExchangePrice_);
            }

            // ratioSupplyYield precision is 1e27 as 100% for increased precision when supplyInterestFree > supplyWithInterest
            if (temp_ & 1 == 1) {
                // ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
                temp_ = temp_ >> 1;

                // Note: case where temp_ == 0 (only supplyInterestFree, no yield) already covered by early return
                // in the if statement a little above.

                // based on above example but supplyRawInterest is 20, supplyInterestFree is 80. no fee.
                // supplyRawInterest must become worth 30. totalSupply must become 110.
                // supplyExchangePrice would have to go from 1 to 1,5. borrowExchangePrice from 1 to 1,2.
                // so ratioSupplyYield must come out as 2.5 (250%).
                // supplyRatio would be (20 * 10_000 / 80) = 2500. but must be inverted.
                temp_ = (1e27 * FOUR_DECIMALS) / temp_; // e.g. 1e31 / 2500 = 4e27. (* 1e27 for precision)
                // e.g. 5_000 * (1e27 + 4e27) / 1e27 = 25_000 (=250%).
                temp_ =
                    // utilization * (100% + 100% / supplyRatio)
                    (((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) *
                        (1e27 + temp_)) / // extract utilization (max 16_383 so there is no way this can overflow).
                    (FOUR_DECIMALS);
                // max possible value of temp_ here is 16383 * (1e27 + 1e31) / 1e4 = ~1.64e31
            } else {
                // ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
                temp_ = temp_ >> 1;
                // if temp_ == 0 then only supplyWithInterest => full yield. temp_ is already 0

                // e.g. 5_000 * 10_000 + (20 * 10_000 / 80) / 10_000 = 5000 * 12500 / 10000 = 6250 (=62.5%).
                temp_ =
                    // 1e27 * utilization * (100% + supplyRatio) / 100%
                    (1e27 *
                        ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) * // extract utilization (max 16_383 so there is no way this can overflow).
                        (FOUR_DECIMALS + temp_)) /
                    (FOUR_DECIMALS * FOUR_DECIMALS);
                // max possible temp_ value: 1e27 * 16383 * 2e4 / 1e8 = 3.2766e27
            }
            // from here temp_ => ratioSupplyYield (utilization * supplyRatio part) scaled by 1e27. max possible value ~1.64e31

            // step2 of ratioSupplyYield: add borrowRatio (only x% of borrowers paying yield)
            if (borrowRatio_ & 1 == 1) {
                // ratio is borrowWithInterest / borrowInterestFree (borrowInterestFree is bigger)
                borrowRatio_ = borrowRatio_ >> 1;
                // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.

                // Note: case where borrowRatio_ == 0 (only borrowInterestFree, no yield) already covered
                // at the beginning of the method by early return if `borrowRatio_ == 1`.

                // based on above example but borrowRawInterest is 10, borrowInterestFree is 50. no fee. borrowRatio = 20%.
                // so only 16.66% of borrowers are paying yield. so the 100% - part of the formula is not needed.
                // x of borrowers paying yield = (borrowRatio / (100 + borrowRatio)) = 16.6666666%
                // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                borrowRatio_ = (borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_);
                // max value here for borrowRatio_ is (1e31 / (1e4 + 1e4))= 5e26 (= 50% of borrowers paying yield).
            } else {
                // ratio is borrowInterestFree / borrowWithInterest (borrowWithInterest is bigger)
                borrowRatio_ = borrowRatio_ >> 1;

                // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                // x of borrowers paying yield = 100% - (borrowRatio / (100 + borrowRatio)) = 100% - 16.6666666% = 83,333%.
                borrowRatio_ = (1e27 - ((borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_)));
                // borrowRatio can never be > 100%. so max subtraction can be 100% - 100% / 200%.
                // or if borrowRatio_ is 0 -> 100% - 0. or if borrowRatio_ is 1 -> 100% - 1 / 101.
                // max value here for borrowRatio_ is 1e27 - 0 = 1e27 (= 100% of borrowers paying yield).
            }

            // temp_ => ratioSupplyYield. scaled down from 1e25 = 1% each to normal percent precision 1e2 = 1%.
            // max nominator value is ~1.64e31 * 1e27 = 1.64e58. max result = 1.64e8
            temp_ = (FOUR_DECIMALS * temp_ * borrowRatio_) / 1e54;

            // 2. calculate supply rate
            // temp_ => supply rate (borrow rate  - revenueFee%) * ratioSupplyYield.
            // division part is done in next step to increase precision. (divided by 2x FOUR_DECIMALS, fee + borrowRate)
            // Note that all calculation divisions for supplyExchangePrice are rounded down.
            // Note supply rate can be bigger than the borrowRate, e.g. if there are only few lenders with interest
            // but more suppliers not earning interest.
            temp_ = ((exchangePricesAndConfig_ & X16) * // borrow rate
                temp_ * // ratioSupplyYield
                (FOUR_DECIMALS - ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_FEE) & X14))); // revenueFee
            // fee can not be > 100%. max possible = 65535 * ~1.64e8 * 1e4 =~1.074774e17.

            // 3. calculate increase in supply exchange price
            supplyExchangePrice_ += ((supplyExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
                (SECONDS_PER_YEAR * FOUR_DECIMALS * FOUR_DECIMALS * FOUR_DECIMALS));
            // max possible nominator = max uint 64 * 1.074774e17 * max uint32 = ~8.52e45. Denominator can not be 0.
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    //////////                     CALC REVENUE                       /////////
    ///////////////////////////////////////////////////////////////////////////

    /// @dev gets the `revenueAmount_` for a token given its' totalAmounts and exchangePricesAndConfig from storage
    /// and the current balance of the Fluid liquidity contract for the token.
    /// @param totalAmounts_ total amounts packed uint256 read from storage
    /// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
    /// @param liquidityTokenBalance_   current balance of Liquidity contract (IERC20(token_).balanceOf(address(this)))
    /// @return revenueAmount_ collectable revenue amount
    function calcRevenue(
        uint256 totalAmounts_,
        uint256 exchangePricesAndConfig_,
        uint256 liquidityTokenBalance_
    ) internal view returns (uint256 revenueAmount_) {
        // @dev no need to super-optimize this method as it is only used by admin

        // calculate the new exchange prices based on earned interest
        (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) = calcExchangePrices(exchangePricesAndConfig_);

        // total supply = interest free + with interest converted from raw
        uint256 totalSupply_ = getTotalSupply(totalAmounts_, supplyExchangePrice_);

        if (totalSupply_ > 0) {
            // available revenue: balanceOf(token) + totalBorrowings - totalLendings.
            revenueAmount_ = liquidityTokenBalance_ + getTotalBorrow(totalAmounts_, borrowExchangePrice_);
            // ensure there is no possible case because of rounding etc. where this would revert,
            // explicitly check if >
            revenueAmount_ = revenueAmount_ > totalSupply_ ? revenueAmount_ - totalSupply_ : 0;
            // Note: if utilization > 100% (totalSupply < totalBorrow), then all the amount above 100% utilization
            // can only be revenue.
        } else {
            // if supply is 0, then rest of balance can be withdrawn as revenue so that no amounts get stuck
            revenueAmount_ = liquidityTokenBalance_;
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    //////////                      CALC LIMITS                       /////////
    ///////////////////////////////////////////////////////////////////////////

    /// @dev calculates withdrawal limit before an operate execution:
    /// amount of user supply that must stay supplied (not amount that can be withdrawn).
    /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
    /// @param userSupplyData_ user supply data packed uint256 from storage
    /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and converted from BigMath
    /// @return currentWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction.
    ///         returned value is in raw for with interest mode, normal amount for interest free mode!
    function calcWithdrawalLimitBeforeOperate(
        uint256 userSupplyData_,
        uint256 userSupply_
    ) internal view returns (uint256 currentWithdrawalLimit_) {
        // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet).
        // first tx where timestamp is 0 will enter `if (lastWithdrawalLimit_ == 0)` because lastWithdrawalLimit_ is not set yet.
        // returning max withdrawal allowed, which is not exactly right but doesn't matter because the first interaction must be
        // a deposit anyway. Important is that it would not revert.

        // Note the first time a deposit brings the user supply amount to above the base withdrawal limit, the active limit
        // is the fully expanded limit immediately.

        // extract last set withdrawal limit
        uint256 lastWithdrawalLimit_ = (userSupplyData_ >>
            LiquiditySlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) & X64;
        lastWithdrawalLimit_ =
            (lastWithdrawalLimit_ >> DEFAULT_EXPONENT_SIZE) <<
            (lastWithdrawalLimit_ & DEFAULT_EXPONENT_MASK);
        if (lastWithdrawalLimit_ == 0) {
            // withdrawal limit is not activated. Max withdrawal allowed
            return 0;
        }

        uint256 maxWithdrawableLimit_;
        uint256 temp_;
        unchecked {
            // extract max withdrawable percent of user supply and
            // calculate maximum withdrawable amount expandPercentage of user supply at full expansion duration elapsed
            // e.g.: if 10% expandPercentage, meaning 10% is withdrawable after full expandDuration has elapsed.

            // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
            maxWithdrawableLimit_ =
                (((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14) * userSupply_) /
                FOUR_DECIMALS;

            // time elapsed since last withdrawal limit was set (in seconds)
            // @dev last process timestamp is guaranteed to exist for withdrawal, as a supply must have happened before.
            // last timestamp can not be > current timestamp
            temp_ =
                block.timestamp -
                ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) & X33);
        }
        // calculate withdrawable amount of expandPercent that is elapsed of expandDuration.
        // e.g. if 60% of expandDuration has elapsed, then user should be able to withdraw 6% of user supply, down to 94%.
        // Note: no explicit check for this needed, it is covered by setting minWithdrawalLimit_ if needed.
        temp_ =
            (maxWithdrawableLimit_ * temp_) /
            // extract expand duration: After this, decrement won't happen (user can withdraw 100% of withdraw limit)
            ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_DURATION) & X24); // expand duration can never be 0
        // calculate expanded withdrawal limit: last withdrawal limit - withdrawable amount.
        // Note: withdrawable amount here can grow bigger than userSupply if timeElapsed is a lot bigger than expandDuration,
        // which would cause the subtraction `lastWithdrawalLimit_ - withdrawableAmount_` to revert. In that case, set 0
        // which will cause minimum (fully expanded) withdrawal limit to be set in lines below.
        unchecked {
            // underflow explicitly checked & handled
            currentWithdrawalLimit_ = lastWithdrawalLimit_ > temp_ ? lastWithdrawalLimit_ - temp_ : 0;
            // calculate minimum withdrawal limit: minimum amount of user supply that must stay supplied at full expansion.
            // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
            temp_ = userSupply_ - maxWithdrawableLimit_;
        }
        // if withdrawal limit is decreased below minimum then set minimum
        // (e.g. when more than expandDuration time has elapsed)
        if (temp_ > currentWithdrawalLimit_) {
            currentWithdrawalLimit_ = temp_;
        }
    }

    /// @dev calculates withdrawal limit after an operate execution:
    /// amount of user supply that must stay supplied (not amount that can be withdrawn).
    /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
    /// @param userSupplyData_ user supply data packed uint256 from storage
    /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and added / subtracted with the executed operate amount
    /// @param newWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction, result from `calcWithdrawalLimitBeforeOperate`
    /// @return withdrawalLimit_ updated withdrawal limit that should be written to storage. returned value is in
    ///                          raw for with interest mode, normal amount for interest free mode!
    function calcWithdrawalLimitAfterOperate(
        uint256 userSupplyData_,
        uint256 userSupply_,
        uint256 newWithdrawalLimit_
    ) internal pure returns (uint256) {
        // temp_ => base withdrawal limit. below this, maximum withdrawals are allowed
        uint256 temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) & X18;
        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);

        // if user supply is below base limit then max withdrawals are allowed
        if (userSupply_ < temp_) {
            return 0;
        }
        // temp_ => withdrawal limit expandPercent (is in 1e2 decimals)
        temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14;
        unchecked {
            // temp_ => minimum withdrawal limit: userSupply - max withdrawable limit (userSupply * expandPercent))
            // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
            // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
            temp_ = userSupply_ - ((userSupply_ * temp_) / FOUR_DECIMALS);
        }
        // if new (before operation) withdrawal limit is less than minimum limit then set minimum limit.
        // e.g. can happen on new deposits. withdrawal limit is instantly fully expanded in a scenario where
        // increased deposit amount outpaces withrawals.
        if (temp_ > newWithdrawalLimit_) {
            return temp_;
        }
        return newWithdrawalLimit_;
    }

    /// @dev calculates borrow limit before an operate execution:
    /// total amount user borrow can reach (not borrowable amount in current operation).
    /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
    /// @param userBorrowData_ user borrow data packed uint256 from storage
    /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_`
    /// @return currentBorrowLimit_ current borrow limit updated for expansion since last interaction. returned value is in
    ///                             raw for with interest mode, normal amount for interest free mode!
    function calcBorrowLimitBeforeOperate(
        uint256 userBorrowData_,
        uint256 userBorrow_
    ) internal view returns (uint256 currentBorrowLimit_) {
        // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet) -> base limit.
        // first tx where timestamp is 0 will enter `if (maxExpandedBorrowLimit_ < baseBorrowLimit_)` because `userBorrow_` and thus
        // `maxExpansionLimit_` and thus `maxExpandedBorrowLimit_` is 0 and `baseBorrowLimit_` can not be 0.

        // temp_ = extract borrow expand percent (is in 1e2 decimals)
        uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14;

        uint256 maxExpansionLimit_;
        uint256 maxExpandedBorrowLimit_;
        unchecked {
            // calculate max expansion limit: Max amount limit can expand to since last interaction
            // userBorrow_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
            maxExpansionLimit_ = ((userBorrow_ * temp_) / FOUR_DECIMALS);

            // calculate max borrow limit: Max point limit can increase to since last interaction
            maxExpandedBorrowLimit_ = userBorrow_ + maxExpansionLimit_;
        }

        // currentBorrowLimit_ = extract base borrow limit
        currentBorrowLimit_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
        currentBorrowLimit_ =
            (currentBorrowLimit_ >> DEFAULT_EXPONENT_SIZE) <<
            (currentBorrowLimit_ & DEFAULT_EXPONENT_MASK);

        if (maxExpandedBorrowLimit_ < currentBorrowLimit_) {
            return currentBorrowLimit_;
        }
        // time elapsed since last borrow limit was set (in seconds)
        unchecked {
            // temp_ = timeElapsed_ (last timestamp can not be > current timestamp)
            temp_ =
                block.timestamp -
                ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP) & X33); // extract last update timestamp
        }

        // currentBorrowLimit_ = expandedBorrowableAmount + extract last set borrow limit
        currentBorrowLimit_ =
            // calculate borrow limit expansion since last interaction for `expandPercent` that is elapsed of `expandDuration`.
            // divisor is extract expand duration (after this, full expansion to expandPercentage happened).
            ((maxExpansionLimit_ * temp_) /
                ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24)) + // expand duration can never be 0
            //  extract last set borrow limit
            BigMathMinified.fromBigNumber(
                (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) & X64,
                DEFAULT_EXPONENT_SIZE,
                DEFAULT_EXPONENT_MASK
            );

        // if timeElapsed is bigger than expandDuration, new borrow limit would be > max expansion,
        // so set to `maxExpandedBorrowLimit_` in that case.
        // also covers the case where last process timestamp = 0 (timeElapsed would simply be very big)
        if (currentBorrowLimit_ > maxExpandedBorrowLimit_) {
            currentBorrowLimit_ = maxExpandedBorrowLimit_;
        }
        // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
        temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);

        if (currentBorrowLimit_ > temp_) {
            currentBorrowLimit_ = temp_;
        }
    }

    /// @dev calculates borrow limit after an operate execution:
    /// total amount user borrow can reach (not borrowable amount in current operation).
    /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
    /// @param userBorrowData_ user borrow data packed uint256 from storage
    /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_` and added / subtracted with the executed operate amount
    /// @param newBorrowLimit_ current borrow limit updated for expansion since last interaction, result from `calcBorrowLimitBeforeOperate`
    /// @return borrowLimit_ updated borrow limit that should be written to storage.
    ///                      returned value is in raw for with interest mode, normal amount for interest free mode!
    function calcBorrowLimitAfterOperate(
        uint256 userBorrowData_,
        uint256 userBorrow_,
        uint256 newBorrowLimit_
    ) internal pure returns (uint256 borrowLimit_) {
        // temp_ = extract borrow expand percent
        uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14; // (is in 1e2 decimals)

        unchecked {
            // borrowLimit_ = calculate maximum borrow limit at full expansion.
            // userBorrow_ needs to be at least 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
            borrowLimit_ = userBorrow_ + ((userBorrow_ * temp_) / FOUR_DECIMALS);
        }

        // temp_ = extract base borrow limit
        temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);

        if (borrowLimit_ < temp_) {
            // below base limit, borrow limit is always base limit
            return temp_;
        }
        // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
        temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
        temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);

        // make sure fully expanded borrow limit is not above hard max borrow limit
        if (borrowLimit_ > temp_) {
            borrowLimit_ = temp_;
        }
        // if new borrow limit (from before operate) is > max borrow limit, set max borrow limit.
        // (e.g. on a repay shrinking instantly to fully expanded borrow limit from new borrow amount. shrinking is instant)
        if (newBorrowLimit_ > borrowLimit_) {
            return borrowLimit_;
        }
        return newBorrowLimit_;
    }

    ///////////////////////////////////////////////////////////////////////////
    //////////                      CALC RATES                        /////////
    ///////////////////////////////////////////////////////////////////////////

    /// @dev Calculates new borrow rate from utilization for a token
    /// @param rateData_ rate data packed uint256 from storage for the token
    /// @param utilization_ totalBorrow / totalSupply. 1e4 = 100% utilization
    /// @return rate_ rate for that particular token in 1e2 precision (e.g. 5% rate = 500)
    function calcBorrowRateFromUtilization(uint256 rateData_, uint256 utilization_) internal returns (uint256 rate_) {
        // extract rate version: 4 bits (0xF) starting from bit 0
        uint256 rateVersion_ = (rateData_ & 0xF);

        if (rateVersion_ == 1) {
            rate_ = calcRateV1(rateData_, utilization_);
        } else if (rateVersion_ == 2) {
            rate_ = calcRateV2(rateData_, utilization_);
        } else {
            revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__UnsupportedRateVersion);
        }

        if (rate_ > X16) {
            // hard cap for borrow rate at maximum value 16 bits (65535) to make sure it does not overflow storage space.
            // this is unlikely to ever happen if configs stay within expected levels.
            rate_ = X16;
            // emit event to more easily become aware
            emit BorrowRateMaxCap();
        }
    }

    /// @dev calculates the borrow rate based on utilization for rate data version 1 (with one kink) in 1e2 precision
    /// @param rateData_ rate data packed uint256 from storage for the token
    /// @param utilization_  in 1e2 (100% = 1e4)
    /// @return rate_ rate in 1e2 precision
    function calcRateV1(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
        /// For rate v1 (one kink) ------------------------------------------------------
        /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  52- 67 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Last 188 bits =>  68-255 => blank, might come in use in future

        // y = mx + c.
        // y is borrow rate
        // x is utilization
        // m = slope (m can also be negative for declining rates)
        // c is constant (c can be negative)

        uint256 y1_;
        uint256 y2_;
        uint256 x1_;
        uint256 x2_;

        // extract kink1: 16 bits (0xFFFF) starting from bit 20
        // kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
        uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_UTILIZATION_AT_KINK) & X16;
        if (utilization_ < kink1_) {
            // if utilization is less than kink
            y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO) & X16;
            y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
            x1_ = 0; // 0%
            x2_ = kink1_;
        } else {
            // else utilization is greater than kink
            y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
            y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) & X16;
            x1_ = kink1_;
            x2_ = FOUR_DECIMALS; // 100%
        }

        int256 constant_;
        int256 slope_;
        unchecked {
            // calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
            // utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
            // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
            slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));

            // calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
            // maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
            // maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
            // maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
            // subtraction most extreme case would be  0 - max value slope_ * x1_ => can not underflow int256
            constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));

            // calculating new borrow rate
            // - slope_ max value is 65535 * 1e12,
            // - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
            // - constant max value is 65535 * 1e12
            // so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
            // divisor TWELVE_DECIMALS can not be 0
            slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
            if (slope_ < 0) {
                revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
            }
            rate_ = uint256(slope_) / TWELVE_DECIMALS;
        }
    }

    /// @dev calculates the borrow rate based on utilization for rate data version 2 (with two kinks) in 1e4 precision
    /// @param rateData_ rate data packed uint256 from storage for the token
    /// @param utilization_  in 1e2 (100% = 1e4)
    /// @return rate_ rate in 1e4 precision
    function calcRateV2(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
        /// For rate v2 (two kinks) -----------------------------------------------------
        /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  52- 67 => Utilization at kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  68- 83 => Rate at utilization kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Next 16  bits =>  84- 99 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
        /// Last 156 bits => 100-255 => blank, might come in use in future

        // y = mx + c.
        // y is borrow rate
        // x is utilization
        // m = slope (m can also be negative for declining rates)
        // c is constant (c can be negative)

        uint256 y1_;
        uint256 y2_;
        uint256 x1_;
        uint256 x2_;

        // extract kink1: 16 bits (0xFFFF) starting from bit 20
        // kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
        uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1) & X16;
        if (utilization_ < kink1_) {
            // if utilization is less than kink1
            y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO) & X16;
            y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
            x1_ = 0; // 0%
            x2_ = kink1_;
        } else {
            // extract kink2: 16 bits (0xFFFF) starting from bit 52
            uint256 kink2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2) & X16;
            if (utilization_ < kink2_) {
                // if utilization is less than kink2
                y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
                y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
                x1_ = kink1_;
                x2_ = kink2_;
            } else {
                // else utilization is greater than kink2
                y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
                y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) & X16;
                x1_ = kink2_;
                x2_ = FOUR_DECIMALS;
            }
        }

        int256 constant_;
        int256 slope_;
        unchecked {
            // calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
            // utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
            // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
            slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));

            // calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
            // maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
            // maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
            // maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
            // subtraction most extreme case would be  0 - max value slope_ * x1_ => can not underflow int256
            constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));

            // calculating new borrow rate
            // - slope_ max value is 65535 * 1e12,
            // - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
            // - constant max value is 65535 * 1e12
            // so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
            // divisor TWELVE_DECIMALS can not be 0
            slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
            if (slope_ < 0) {
                revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
            }
            rate_ = uint256(slope_) / TWELVE_DECIMALS;
        }
    }

    /// @dev reads the total supply out of Liquidity packed storage `totalAmounts_` for `supplyExchangePrice_`
    function getTotalSupply(
        uint256 totalAmounts_,
        uint256 supplyExchangePrice_
    ) internal pure returns (uint256 totalSupply_) {
        // totalSupply_ => supplyInterestFree
        totalSupply_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
        totalSupply_ = (totalSupply_ >> DEFAULT_EXPONENT_SIZE) << (totalSupply_ & DEFAULT_EXPONENT_MASK);

        uint256 totalSupplyRaw_ = totalAmounts_ & X64; // no shifting as supplyRaw is first 64 bits
        totalSupplyRaw_ = (totalSupplyRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalSupplyRaw_ & DEFAULT_EXPONENT_MASK);

        // totalSupply = supplyInterestFree + supplyRawInterest normalized from raw
        totalSupply_ += ((totalSupplyRaw_ * supplyExchangePrice_) / EXCHANGE_PRICES_PRECISION);
    }

    /// @dev reads the total borrow out of Liquidity packed storage `totalAmounts_` for `borrowExchangePrice_`
    function getTotalBorrow(
        uint256 totalAmounts_,
        uint256 borrowExchangePrice_
    ) internal pure returns (uint256 totalBorrow_) {
        // totalBorrow_ => borrowInterestFree
        // no & mask needed for borrow interest free as it occupies the last bits in the storage slot
        totalBorrow_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
        totalBorrow_ = (totalBorrow_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrow_ & DEFAULT_EXPONENT_MASK);

        uint256 totalBorrowRaw_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
        totalBorrowRaw_ = (totalBorrowRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrowRaw_ & DEFAULT_EXPONENT_MASK);

        // totalBorrow = borrowInterestFree + borrowRawInterest normalized from raw
        totalBorrow_ += ((totalBorrowRaw_ * borrowExchangePrice_) / EXCHANGE_PRICES_PRECISION);
    }
}
liquiditySlotsLink.sol 107 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

/// @notice library that helps in reading / working with storage slot data of Fluid Liquidity.
/// @dev as all data for Fluid Liquidity is internal, any data must be fetched directly through manual
/// slot reading through this library or, if gas usage is less important, through the FluidLiquidityResolver.
library LiquiditySlotsLink {
    /// @dev storage slot for status at Liquidity
    uint256 internal constant LIQUIDITY_STATUS_SLOT = 1;
    /// @dev storage slot for auths mapping at Liquidity
    uint256 internal constant LIQUIDITY_AUTHS_MAPPING_SLOT = 2;
    /// @dev storage slot for guardians mapping at Liquidity
    uint256 internal constant LIQUIDITY_GUARDIANS_MAPPING_SLOT = 3;
    /// @dev storage slot for user class mapping at Liquidity
    uint256 internal constant LIQUIDITY_USER_CLASS_MAPPING_SLOT = 4;
    /// @dev storage slot for exchangePricesAndConfig mapping at Liquidity
    uint256 internal constant LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT = 5;
    /// @dev storage slot for rateData mapping at Liquidity
    uint256 internal constant LIQUIDITY_RATE_DATA_MAPPING_SLOT = 6;
    /// @dev storage slot for totalAmounts mapping at Liquidity
    uint256 internal constant LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT = 7;
    /// @dev storage slot for user supply double mapping at Liquidity
    uint256 internal constant LIQUIDITY_USER_SUPPLY_DOUBLE_MAPPING_SLOT = 8;
    /// @dev storage slot for user borrow double mapping at Liquidity
    uint256 internal constant LIQUIDITY_USER_BORROW_DOUBLE_MAPPING_SLOT = 9;
    /// @dev storage slot for listed tokens array at Liquidity
    uint256 internal constant LIQUIDITY_LISTED_TOKENS_ARRAY_SLOT = 10;
    /// @dev storage slot for listed tokens array at Liquidity
    uint256 internal constant LIQUIDITY_CONFIGS2_MAPPING_SLOT = 11;

    // --------------------------------
    // @dev stacked uint256 storage slots bits position data for each:

    // ExchangePricesAndConfig
    uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATE = 0;
    uint256 internal constant BITS_EXCHANGE_PRICES_FEE = 16;
    uint256 internal constant BITS_EXCHANGE_PRICES_UTILIZATION = 30;
    uint256 internal constant BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD = 44;
    uint256 internal constant BITS_EXCHANGE_PRICES_LAST_TIMESTAMP = 58;
    uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE = 91;
    uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE = 155;
    uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_RATIO = 219;
    uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATIO = 234;
    uint256 internal constant BITS_EXCHANGE_PRICES_USES_CONFIGS2 = 249;

    // RateData:
    uint256 internal constant BITS_RATE_DATA_VERSION = 0;
    // RateData: V1
    uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO = 4;
    uint256 internal constant BITS_RATE_DATA_V1_UTILIZATION_AT_KINK = 20;
    uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK = 36;
    uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX = 52;
    // RateData: V2
    uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO = 4;
    uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1 = 20;
    uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1 = 36;
    uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2 = 52;
    uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2 = 68;
    uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX = 84;

    // TotalAmounts
    uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_WITH_INTEREST = 0;
    uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE = 64;
    uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST = 128;
    uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE = 192;

    // UserSupplyData
    uint256 internal constant BITS_USER_SUPPLY_MODE = 0;
    uint256 internal constant BITS_USER_SUPPLY_AMOUNT = 1;
    uint256 internal constant BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT = 65;
    uint256 internal constant BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP = 129;
    uint256 internal constant BITS_USER_SUPPLY_EXPAND_PERCENT = 162;
    uint256 internal constant BITS_USER_SUPPLY_EXPAND_DURATION = 176;
    uint256 internal constant BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT = 200;
    uint256 internal constant BITS_USER_SUPPLY_IS_PAUSED = 255;

    // UserBorrowData
    uint256 internal constant BITS_USER_BORROW_MODE = 0;
    uint256 internal constant BITS_USER_BORROW_AMOUNT = 1;
    uint256 internal constant BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT = 65;
    uint256 internal constant BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP = 129;
    uint256 internal constant BITS_USER_BORROW_EXPAND_PERCENT = 162;
    uint256 internal constant BITS_USER_BORROW_EXPAND_DURATION = 176;
    uint256 internal constant BITS_USER_BORROW_BASE_BORROW_LIMIT = 200;
    uint256 internal constant BITS_USER_BORROW_MAX_BORROW_LIMIT = 218;
    uint256 internal constant BITS_USER_BORROW_IS_PAUSED = 255;

    // Configs2
    uint256 internal constant BITS_CONFIGS2_MAX_UTILIZATION = 0;

    // --------------------------------

    /// @notice Calculating the slot ID for Liquidity contract for single mapping at `slot_` for `key_`
    function calculateMappingStorageSlot(uint256 slot_, address key_) internal pure returns (bytes32) {
        return keccak256(abi.encode(key_, slot_));
    }

    /// @notice Calculating the slot ID for Liquidity contract for double mapping at `slot_` for `key1_` and `key2_`
    function calculateDoubleMappingStorageSlot(
        uint256 slot_,
        address key1_,
        address key2_
    ) internal pure returns (bytes32) {
        bytes32 intermediateSlot_ = keccak256(abi.encode(key1_, slot_));
        return keccak256(abi.encode(key2_, intermediateSlot_));
    }
}
safeTransfer.sol 97 lines
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.21;

import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";

/// @notice provides minimalistic methods for safe transfers, e.g. ERC20 safeTransferFrom
library SafeTransfer {
    uint256 internal constant MAX_NATIVE_TRANSFER_GAS = 20000; // pass max. 20k gas for native transfers

    error FluidSafeTransferError(uint256 errorId_);

    /// @dev Transfer `amount_` of `token_` from `from_` to `to_`, spending the approval given by `from_` to the
    /// calling contract. If `token_` returns no value, non-reverting calls are assumed to be successful.
    /// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
    /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L31-L63
    function safeTransferFrom(address token_, address from_, address to_, uint256 amount_) internal {
        bool success_;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from_" argument.
            mstore(add(freeMemoryPointer, 36), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
            mstore(add(freeMemoryPointer, 68), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.

            success_ := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token_, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        if (!success_) {
            revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFromFailed);
        }
    }

    /// @dev Transfer `amount_` of `token_` to `to_`.
    /// If `token_` returns no value, non-reverting calls are assumed to be successful.
    /// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
    /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L65-L95
    function safeTransfer(address token_, address to_, uint256 amount_) internal {
        bool success_;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
            mstore(add(freeMemoryPointer, 36), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.

            success_ := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token_, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        if (!success_) {
            revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
        }
    }

    /// @dev Transfer `amount_` of ` native token to `to_`.
    /// Minimally modified from Solmate SafeTransferLib (Custom Error):
    /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L15-L25
    function safeTransferNative(address to_, uint256 amount_) internal {
        bool success_;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not. Pass limited gas
            success_ := call(MAX_NATIVE_TRANSFER_GAS, to_, amount_, 0, 0, 0, 0)
        }

        if (!success_) {
            revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
        }
    }
}
storageRead.sol 11 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

/// @notice implements a method to read uint256 data from storage at a bytes32 storage slot key.
contract StorageRead {
    function readFromStorage(bytes32 slot_) public view returns (uint256 result_) {
        assembly {
            result_ := sload(slot_) // read value from the storage slot
        }
    }
}
tickMath.sol 270 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

/// @title library that calculates number "tick" and "ratioX96" from this: ratioX96 = (1.0015^tick) * 2^96
/// @notice this library is used in Fluid Vault protocol for optimiziation.
/// @dev "tick" supports between -32767 and 32767. "ratioX96" supports between 37075072 and 169307877264527972847801929085841449095838922544595
library TickMath {
    /// The minimum tick that can be passed in getRatioAtTick. 1.0015**-32767
    int24 internal constant MIN_TICK = -32767;
    /// The maximum tick that can be passed in getRatioAtTick. 1.0015**32767
    int24 internal constant MAX_TICK = 32767;

    uint256 internal constant FACTOR00 = 0x100000000000000000000000000000000;
    uint256 internal constant FACTOR01 = 0xff9dd7de423466c20352b1246ce4856f; // 2^128/1.0015**1 = 339772707859149738855091969477551883631
    uint256 internal constant FACTOR02 = 0xff3bd55f4488ad277531fa1c725a66d0; // 2^128/1.0015**2 = 339263812140938331358054887146831636176
    uint256 internal constant FACTOR03 = 0xfe78410fd6498b73cb96a6917f853259; // 2^128/1.0015**4 = 338248306163758188337119769319392490073
    uint256 internal constant FACTOR04 = 0xfcf2d9987c9be178ad5bfeffaa123273; // 2^128/1.0015**8 = 336226404141693512316971918999264834163
    uint256 internal constant FACTOR05 = 0xf9ef02c4529258b057769680fc6601b3; // 2^128/1.0015**16 = 332218786018727629051611634067491389875
    uint256 internal constant FACTOR06 = 0xf402d288133a85a17784a411f7aba082; // 2^128/1.0015**32 = 324346285652234375371948336458280706178
    uint256 internal constant FACTOR07 = 0xe895615b5beb6386553757b0352bda90; // 2^128/1.0015**64 = 309156521885964218294057947947195947664
    uint256 internal constant FACTOR08 = 0xd34f17a00ffa00a8309940a15930391a; // 2^128/1.0015**128 = 280877777739312896540849703637713172762 
    uint256 internal constant FACTOR09 = 0xae6b7961714e20548d88ea5123f9a0ff; // 2^128/1.0015**256 = 231843708922198649176471782639349113087
    uint256 internal constant FACTOR10 = 0x76d6461f27082d74e0feed3b388c0ca1; // 2^128/1.0015**512 = 157961477267171621126394973980180876449
    uint256 internal constant FACTOR11 = 0x372a3bfe0745d8b6b19d985d9a8b85bb; // 2^128/1.0015**1024 = 73326833024599564193373530205717235131
    uint256 internal constant FACTOR12 = 0x0be32cbee48979763cf7247dd7bb539d; // 2^128/1.0015**2048 = 15801066890623697521348224657638773661
    uint256 internal constant FACTOR13 = 0x8d4f70c9ff4924dac37612d1e2921e;   // 2^128/1.0015**4096 = 733725103481409245883800626999235102
    uint256 internal constant FACTOR14 = 0x4e009ae5519380809a02ca7aec77;     // 2^128/1.0015**8192 = 1582075887005588088019997442108535
    uint256 internal constant FACTOR15 = 0x17c45e641b6e95dee056ff10;         // 2^128/1.0015**16384 = 7355550435635883087458926352

    /// The minimum value that can be returned from getRatioAtTick. Equivalent to getRatioAtTick(MIN_TICK). ~ Equivalent to `(1 << 96) * (1.0015**-32767)`
    uint256 internal constant MIN_RATIOX96 = 37075072;
    /// The maximum value that can be returned from getRatioAtTick. Equivalent to getRatioAtTick(MAX_TICK).
    /// ~ Equivalent to `(1 << 96) * (1.0015**32767)`, rounding etc. leading to minor difference
    uint256 internal constant MAX_RATIOX96 = 169307877264527972847801929085841449095838922544595;

    uint256 internal constant ZERO_TICK_SCALED_RATIO = 0x1000000000000000000000000; // 1 << 96 // 79228162514264337593543950336
    uint256 internal constant _1E26 = 1e26;

    /// @notice ratioX96 = (1.0015^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return ratioX96 ratio = (debt amount/collateral amount)
    function getRatioAtTick(int tick) internal pure returns (uint256 ratioX96) {
        assembly {
            let absTick_ := sub(xor(tick, sar(255, tick)), sar(255, tick))

            if gt(absTick_, MAX_TICK) {
                revert(0, 0)
            }
            let factor_ := FACTOR00
            if and(absTick_, 0x1) {
                factor_ := FACTOR01
            }
            if and(absTick_, 0x2) {
                factor_ := shr(128, mul(factor_, FACTOR02))
            }
            if and(absTick_, 0x4) {
                factor_ := shr(128, mul(factor_, FACTOR03))
            }
            if and(absTick_, 0x8) {
                factor_ := shr(128, mul(factor_, FACTOR04))
            }
            if and(absTick_, 0x10) {
                factor_ := shr(128, mul(factor_, FACTOR05))
            }
            if and(absTick_, 0x20) {
                factor_ := shr(128, mul(factor_, FACTOR06))
            }
            if and(absTick_, 0x40) {
                factor_ := shr(128, mul(factor_, FACTOR07))
            }
            if and(absTick_, 0x80) {
                factor_ := shr(128, mul(factor_, FACTOR08))
            }
            if and(absTick_, 0x100) {
                factor_ := shr(128, mul(factor_, FACTOR09))
            }
            if and(absTick_, 0x200) {
                factor_ := shr(128, mul(factor_, FACTOR10))
            }
            if and(absTick_, 0x400) {
                factor_ := shr(128, mul(factor_, FACTOR11))
            }
            if and(absTick_, 0x800) {
                factor_ := shr(128, mul(factor_, FACTOR12))
            }
            if and(absTick_, 0x1000) {
                factor_ := shr(128, mul(factor_, FACTOR13))
            }
            if and(absTick_, 0x2000) {
                factor_ := shr(128, mul(factor_, FACTOR14))
            }
            if and(absTick_, 0x4000) {
                factor_ := shr(128, mul(factor_, FACTOR15))
            }

            let precision_ := 0
            if iszero(and(tick, 0x8000000000000000000000000000000000000000000000000000000000000000)) {
                factor_ := div(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, factor_)
                // we round up in the division so getTickAtRatio of the output price is always consistent
                if mod(factor_, 0x100000000) {
                    precision_ := 1
                }
            }
            ratioX96 := add(shr(32, factor_), precision_)
        }
    }

    /// @notice ratioX96 = (1.0015^tick) * 2^96
    /// @dev Throws if ratioX96 > max ratio || ratioX96 < min ratio
    /// @param ratioX96 The input ratio; ratio = (debt amount/collateral amount)
    /// @return tick The output tick for the above formula. Returns in round down form. if tick is 123.23 then 123, if tick is -123.23 then returns -124
    /// @return perfectRatioX96 perfect ratio for the above tick
    function getTickAtRatio(uint256 ratioX96) internal pure returns (int tick, uint perfectRatioX96) {
        assembly {
            if or(gt(ratioX96, MAX_RATIOX96), lt(ratioX96, MIN_RATIOX96)) {
                revert(0, 0)
            }

            let cond := lt(ratioX96, ZERO_TICK_SCALED_RATIO)
            let factor_

            if iszero(cond) {
                // if ratioX96 >= ZERO_TICK_SCALED_RATIO
                factor_ := div(mul(ratioX96, _1E26), ZERO_TICK_SCALED_RATIO)
            }
            if cond {
                // ratioX96 < ZERO_TICK_SCALED_RATIO
                factor_ := div(mul(ZERO_TICK_SCALED_RATIO, _1E26), ratioX96)
            }

            // put in https://www.wolframalpha.com/ whole equation: (1.0015^tick) * 2^96 * 10^26 / 79228162514264337593543950336

            // for tick = 16384
            // ratioX96 = (1.0015^16384) * 2^96 = 3665252098134783297721995888537077351735
            // 3665252098134783297721995888537077351735 * 10^26 / 79228162514264337593543950336 =
            // 4626198540796508716348404308345255985.06131964639489434655721
            if iszero(lt(factor_, 4626198540796508716348404308345255985)) {
                tick := or(tick, 0x4000)
                factor_ := div(mul(factor_, _1E26), 4626198540796508716348404308345255985)
            }
            // for tick = 8192
            // ratioX96 = (1.0015^8192) * 2^96 = 17040868196391020479062776466509865
            // 17040868196391020479062776466509865 * 10^26 / 79228162514264337593543950336 =
            // 21508599537851153911767490449162.3037648642153898377655505172
            if iszero(lt(factor_, 21508599537851153911767490449162)) {
                tick := or(tick, 0x2000)
                factor_ := div(mul(factor_, _1E26), 21508599537851153911767490449162)
            }
            // for tick = 4096
            // ratioX96 = (1.0015^4096) * 2^96 = 36743933851015821532611831851150
            // 36743933851015821532611831851150 * 10^26 / 79228162514264337593543950336 =
            // 46377364670549310883002866648.9777607649742626173648716941385
            if iszero(lt(factor_, 46377364670549310883002866649)) {
                tick := or(tick, 0x1000)
                factor_ := div(mul(factor_, _1E26), 46377364670549310883002866649)
            }
            // for tick = 2048
            // ratioX96 = (1.0015^2048) * 2^96 = 1706210527034005899209104452335
            // 1706210527034005899209104452335 * 10^26 / 79228162514264337593543950336 =
            // 2153540449365864845468344760.06357108484096046743300420319322
            if iszero(lt(factor_, 2153540449365864845468344760)) {
                tick := or(tick, 0x800)
                factor_ := div(mul(factor_, _1E26), 2153540449365864845468344760)
            }
            // for tick = 1024
            // ratioX96 = (1.0015^1024) * 2^96 = 367668226692760093024536487236
            // 367668226692760093024536487236 * 10^26 / 79228162514264337593543950336 =
            // 464062544207767844008185024.950588990554136265212906454481127
            if iszero(lt(factor_, 464062544207767844008185025)) {
                tick := or(tick, 0x400)
                factor_ := div(mul(factor_, _1E26), 464062544207767844008185025)
            }
            // for tick = 512
            // ratioX96 = (1.0015^512) * 2^96 = 170674186729409605620119663668
            // 170674186729409605620119663668 * 10^26 / 79228162514264337593543950336 =
            // 215421109505955298802281577.031879604792139232258508172947569
            if iszero(lt(factor_, 215421109505955298802281577)) {
                tick := or(tick, 0x200)
                factor_ := div(mul(factor_, _1E26), 215421109505955298802281577)
            }
            // for tick = 256
            // ratioX96 = (1.0015^256) * 2^96 = 116285004205991934861656513301
            // 116285004205991934861656513301 * 10^26 / 79228162514264337593543950336 =
            // 146772309890508740607270614.667650899656438875541505058062410
            if iszero(lt(factor_, 146772309890508740607270615)) {
                tick := or(tick, 0x100)
                factor_ := div(mul(factor_, _1E26), 146772309890508740607270615)
            }
            // for tick = 128
            // ratioX96 = (1.0015^128) * 2^96 = 95984619659632141743747099590
            // 95984619659632141743747099590 * 10^26 / 79228162514264337593543950336 =
            // 121149622323187099817270416.157248837742741760456796835775887
            if iszero(lt(factor_, 121149622323187099817270416)) {
                tick := or(tick, 0x80)
                factor_ := div(mul(factor_, _1E26), 121149622323187099817270416)
            }
            // for tick = 64
            // ratioX96 = (1.0015^64) * 2^96 = 87204845308406958006717891124
            // 87204845308406958006717891124 * 10^26 / 79228162514264337593543950336 =
            // 110067989135437147685980801.568068573422377364214113968609839
            if iszero(lt(factor_, 110067989135437147685980801)) {
                tick := or(tick, 0x40)
                factor_ := div(mul(factor_, _1E26), 110067989135437147685980801)
            }
            // for tick = 32
            // ratioX96 = (1.0015^32) * 2^96 = 83120873769022354029916374475
            // 83120873769022354029916374475 * 10^26 / 79228162514264337593543950336 =
            // 104913292358707887270979599.831816586773651266562785765558183
            if iszero(lt(factor_, 104913292358707887270979600)) {
                tick := or(tick, 0x20)
                factor_ := div(mul(factor_, _1E26), 104913292358707887270979600)
            }
            // for tick = 16
            // ratioX96 = (1.0015^16) * 2^96 = 81151180492336368327184716176
            // 81151180492336368327184716176 * 10^26 / 79228162514264337593543950336 =
            // 102427189924701091191840927.762844039579442328381455567932128
            if iszero(lt(factor_, 102427189924701091191840928)) {
                tick := or(tick, 0x10)
                factor_ := div(mul(factor_, _1E26), 102427189924701091191840928)
            }
            // for tick = 8
            // ratioX96 = (1.0015^8) * 2^96 = 80183906840906820640659903620
            // 80183906840906820640659903620 * 10^26 / 79228162514264337593543950336 =
            // 101206318935480056907421312.890625
            if iszero(lt(factor_, 101206318935480056907421313)) {
                tick := or(tick, 0x8)
                factor_ := div(mul(factor_, _1E26), 101206318935480056907421313)
            }
            // for tick = 4
            // ratioX96 = (1.0015^4) * 2^96 = 79704602139525152702959747603
            // 79704602139525152702959747603 * 10^26 / 79228162514264337593543950336 =
            // 100601351350506250000000000
            if iszero(lt(factor_, 100601351350506250000000000)) {
                tick := or(tick, 0x4)
                factor_ := div(mul(factor_, _1E26), 100601351350506250000000000)
            }
            // for tick = 2
            // ratioX96 = (1.0015^2) * 2^96 = 79466025265172787701084167660
            // 79466025265172787701084167660 * 10^26 / 79228162514264337593543950336 =
            // 100300225000000000000000000
            if iszero(lt(factor_, 100300225000000000000000000)) {
                tick := or(tick, 0x2)
                factor_ := div(mul(factor_, _1E26), 100300225000000000000000000)
            }
            // for tick = 1
            // ratioX96 = (1.0015^1) * 2^96 = 79347004758035734099934266261
            // 79347004758035734099934266261 * 10^26 / 79228162514264337593543950336 =
            // 100150000000000000000000000
            if iszero(lt(factor_, 100150000000000000000000000)) {
                tick := or(tick, 0x1)
                factor_ := div(mul(factor_, _1E26), 100150000000000000000000000)
            }
            if iszero(cond) {
                // if ratioX96 >= ZERO_TICK_SCALED_RATIO
                perfectRatioX96 := div(mul(ratioX96, _1E26), factor_)
            }
            if cond {
                // ratioX96 < ZERO_TICK_SCALED_RATIO
                tick := not(tick)
                perfectRatioX96 := div(mul(ratioX96, factor_), 100150000000000000000000000)
            }
            // perfect ratio should always be <= ratioX96
            // not sure if it can ever be bigger but better to have extra checks
            if gt(perfectRatioX96, ratioX96) {
                revert(0, 0)
            }
        }
    }
}
structs.sol 145 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

abstract contract Structs {
    struct AddressBool {
        address addr;
        bool value;
    }

    struct AddressUint256 {
        address addr;
        uint256 value;
    }

    /// @notice struct to set borrow rate data for version 1
    struct RateDataV1Params {
        ///
        /// @param token for rate data
        address token;
        ///
        /// @param kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
        /// utilization below kink usually means slow increase in rate, once utilization is above kink borrow rate increases fast
        uint256 kink;
        ///
        /// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
        /// i.e. constant minimum borrow rate
        /// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
        uint256 rateAtUtilizationZero;
        ///
        /// @param rateAtUtilizationKink borrow rate when utilization is at kink. in 1e2: 100% = 10_000; 1% = 100
        /// e.g. when rate should be 7% at kink then rateAtUtilizationKink would be 700
        uint256 rateAtUtilizationKink;
        ///
        /// @param rateAtUtilizationMax borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
        /// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
        uint256 rateAtUtilizationMax;
    }

    /// @notice struct to set borrow rate data for version 2
    struct RateDataV2Params {
        ///
        /// @param token for rate data
        address token;
        ///
        /// @param kink1 first kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
        /// utilization below kink 1 usually means slow increase in rate, once utilization is above kink 1 borrow rate increases faster
        uint256 kink1;
        ///
        /// @param kink2 second kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
        /// utilization below kink 2 usually means slow / medium increase in rate, once utilization is above kink 2 borrow rate increases fast
        uint256 kink2;
        ///
        /// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
        /// i.e. constant minimum borrow rate
        /// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
        uint256 rateAtUtilizationZero;
        ///
        /// @param rateAtUtilizationKink1 desired borrow rate when utilization is at first kink. in 1e2: 100% = 10_000; 1% = 100
        /// e.g. when rate should be 7% at first kink then rateAtUtilizationKink would be 700
        uint256 rateAtUtilizationKink1;
        ///
        /// @param rateAtUtilizationKink2 desired borrow rate when utilization is at second kink. in 1e2: 100% = 10_000; 1% = 100
        /// e.g. when rate should be 7% at second kink then rateAtUtilizationKink would be 1_200
        uint256 rateAtUtilizationKink2;
        ///
        /// @param rateAtUtilizationMax desired borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
        /// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
        uint256 rateAtUtilizationMax;
    }

    /// @notice struct to set token config
    struct TokenConfig {
        ///
        /// @param token address
        address token;
        ///
        /// @param fee charges on borrower's interest. in 1e2: 100% = 10_000; 1% = 100
        uint256 fee;
        ///
        /// @param threshold on when to update the storage slot. in 1e2: 100% = 10_000; 1% = 100
        uint256 threshold;
        ///
        /// @param maxUtilization maximum allowed utilization. in 1e2: 100% = 10_000; 1% = 100
        ///                       set to 100% to disable and have default limit of 100% (avoiding SLOAD).
        uint256 maxUtilization;
    }

    /// @notice struct to set user supply & withdrawal config
    struct UserSupplyConfig {
        ///
        /// @param user address
        address user;
        ///
        /// @param token address
        address token;
        ///
        /// @param mode: 0 = without interest. 1 = with interest
        uint8 mode;
        ///
        /// @param expandPercent withdrawal limit expand percent. in 1e2: 100% = 10_000; 1% = 100
        /// Also used to calculate rate at which withdrawal limit should decrease (instant).
        uint256 expandPercent;
        ///
        /// @param expandDuration withdrawal limit expand duration in seconds.
        /// used to calculate rate together with expandPercent
        uint256 expandDuration;
        ///
        /// @param baseWithdrawalLimit base limit, below this, user can withdraw the entire amount.
        /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
        /// with interest -> raw, without interest -> normal
        uint256 baseWithdrawalLimit;
    }

    /// @notice struct to set user borrow & payback config
    struct UserBorrowConfig {
        ///
        /// @param user address
        address user;
        ///
        /// @param token address
        address token;
        ///
        /// @param mode: 0 = without interest. 1 = with interest
        uint8 mode;
        ///
        /// @param expandPercent debt limit expand percent. in 1e2: 100% = 10_000; 1% = 100
        /// Also used to calculate rate at which debt limit should decrease (instant).
        uint256 expandPercent;
        ///
        /// @param expandDuration debt limit expand duration in seconds.
        /// used to calculate rate together with expandPercent
        uint256 expandDuration;
        ///
        /// @param baseDebtCeiling base borrow limit. until here, borrow limit remains as baseDebtCeiling
        /// (user can borrow until this point at once without stepped expansion). Above this, automated limit comes in place.
        /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
        /// with interest -> raw, without interest -> normal
        uint256 baseDebtCeiling;
        ///
        /// @param maxDebtCeiling max borrow ceiling, maximum amount the user can borrow.
        /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
        /// with interest -> raw, without interest -> normal
        uint256 maxDebtCeiling;
    }
}
iLiquidity.sol 129 lines
//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { IProxy } from "../../infiniteProxy/interfaces/iProxy.sol";
import { Structs as AdminModuleStructs } from "../adminModule/structs.sol";

interface IFluidLiquidityAdmin {
    /// @notice adds/removes auths. Auths generally could be contracts which can have restricted actions defined on contract.
    ///         auths can be helpful in reducing governance overhead where it's not needed.
    /// @param authsStatus_ array of structs setting allowed status for an address.
    ///                     status true => add auth, false => remove auth
    function updateAuths(AdminModuleStructs.AddressBool[] calldata authsStatus_) external;

    /// @notice adds/removes guardians. Only callable by Governance.
    /// @param guardiansStatus_ array of structs setting allowed status for an address.
    ///                         status true => add guardian, false => remove guardian
    function updateGuardians(AdminModuleStructs.AddressBool[] calldata guardiansStatus_) external;

    /// @notice changes the revenue collector address (contract that is sent revenue). Only callable by Governance.
    /// @param revenueCollector_  new revenue collector address
    function updateRevenueCollector(address revenueCollector_) external;

    /// @notice changes current status, e.g. for pausing or unpausing all user operations. Only callable by Auths.
    /// @param newStatus_ new status
    ///        status = 2 -> pause, status = 1 -> resume.
    function changeStatus(uint256 newStatus_) external;

    /// @notice                  update tokens rate data version 1. Only callable by Auths.
    /// @param tokensRateData_   array of RateDataV1Params with rate data to set for each token
    function updateRateDataV1s(AdminModuleStructs.RateDataV1Params[] calldata tokensRateData_) external;

    /// @notice                  update tokens rate data version 2. Only callable by Auths.
    /// @param tokensRateData_   array of RateDataV2Params with rate data to set for each token
    function updateRateDataV2s(AdminModuleStructs.RateDataV2Params[] calldata tokensRateData_) external;

    /// @notice updates token configs: fee charge on borrowers interest & storage update utilization threshold.
    ///         Only callable by Auths.
    /// @param tokenConfigs_ contains token address, fee & utilization threshold
    function updateTokenConfigs(AdminModuleStructs.TokenConfig[] calldata tokenConfigs_) external;

    /// @notice updates user classes: 0 is for new protocols, 1 is for established protocols.
    ///         Only callable by Auths.
    /// @param userClasses_ struct array of uint256 value to assign for each user address
    function updateUserClasses(AdminModuleStructs.AddressUint256[] calldata userClasses_) external;

    /// @notice sets user supply configs per token basis. Eg: with interest or interest-free and automated limits.
    ///         Only callable by Auths.
    /// @param userSupplyConfigs_ struct array containing user supply config, see `UserSupplyConfig` struct for more info
    function updateUserSupplyConfigs(AdminModuleStructs.UserSupplyConfig[] memory userSupplyConfigs_) external;

    /// @notice sets a new withdrawal limit as the current limit for a certain user
    /// @param user_ user address for which to update the withdrawal limit
    /// @param token_ token address for which to update the withdrawal limit
    /// @param newLimit_ new limit until which user supply can decrease to.
    ///                  Important: input in raw. Must account for exchange price in input param calculation.
    ///                  Note any limit that is < max expansion or > current user supply will set max expansion limit or
    ///                  current user supply as limit respectively.
    ///                  - set 0 to make maximum possible withdrawable: instant full expansion, and if that goes
    ///                  below base limit then fully down to 0.
    ///                  - set type(uint256).max to make current withdrawable 0 (sets current user supply as limit).
    function updateUserWithdrawalLimit(address user_, address token_, uint256 newLimit_) external;

    /// @notice setting user borrow configs per token basis. Eg: with interest or interest-free and automated limits.
    ///         Only callable by Auths.
    /// @param userBorrowConfigs_ struct array containing user borrow config, see `UserBorrowConfig` struct for more info
    function updateUserBorrowConfigs(AdminModuleStructs.UserBorrowConfig[] memory userBorrowConfigs_) external;

    /// @notice pause operations for a particular user in class 0 (class 1 users can't be paused by guardians).
    /// Only callable by Guardians.
    /// @param user_          address of user to pause operations for
    /// @param supplyTokens_  token addresses to pause withdrawals for
    /// @param borrowTokens_  token addresses to pause borrowings for
    function pauseUser(address user_, address[] calldata supplyTokens_, address[] calldata borrowTokens_) external;

    /// @notice unpause operations for a particular user in class 0 (class 1 users can't be paused by guardians).
    /// Only callable by Guardians.
    /// @param user_          address of user to unpause operations for
    /// @param supplyTokens_  token addresses to unpause withdrawals for
    /// @param borrowTokens_  token addresses to unpause borrowings for
    function unpauseUser(address user_, address[] calldata supplyTokens_, address[] calldata borrowTokens_) external;

    /// @notice         collects revenue for tokens to configured revenueCollector address.
    /// @param tokens_  array of tokens to collect revenue for
    /// @dev            Note that this can revert if token balance is < revenueAmount (utilization > 100%)
    function collectRevenue(address[] calldata tokens_) external;

    /// @notice gets the current updated exchange prices for n tokens and updates all prices, rates related data in storage.
    /// @param tokens_ tokens to update exchange prices for
    /// @return supplyExchangePrices_ new supply rates of overall system for each token
    /// @return borrowExchangePrices_ new borrow rates of overall system for each token
    function updateExchangePrices(
        address[] calldata tokens_
    ) external returns (uint256[] memory supplyExchangePrices_, uint256[] memory borrowExchangePrices_);
}

interface IFluidLiquidityLogic is IFluidLiquidityAdmin {
    /// @notice Single function which handles supply, withdraw, borrow & payback
    /// @param token_ address of token (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native)
    /// @param supplyAmount_ if +ve then supply, if -ve then withdraw, if 0 then nothing
    /// @param borrowAmount_ if +ve then borrow, if -ve then payback, if 0 then nothing
    /// @param withdrawTo_ if withdrawal then to which address
    /// @param borrowTo_ if borrow then to which address
    /// @param callbackData_ callback data passed to `liquidityCallback` method of protocol
    /// @return memVar3_ updated supplyExchangePrice
    /// @return memVar4_ updated borrowExchangePrice
    /// @dev to trigger skipping in / out transfers (gas optimization):
    /// -  ` callbackData_` MUST be encoded so that "from" address is the last 20 bytes in the last 32 bytes slot,
    ///     also for native token operations where liquidityCallback is not triggered!
    ///     from address must come at last position if there is more data. I.e. encode like:
    ///     abi.encode(otherVar1, otherVar2, FROM_ADDRESS). Note dynamic types used with abi.encode come at the end
    ///     so if dynamic types are needed, you must use abi.encodePacked to ensure the from address is at the end.
    /// -   this "from" address must match withdrawTo_ or borrowTo_ and must be == `msg.sender`
    /// -   `callbackData_` must in addition to the from address as described above include bytes32 SKIP_TRANSFERS
    ///     in the slot before (bytes 32 to 63)
    /// -   `msg.value` must be 0.
    /// -   Amounts must be either:
    ///     -  supply(+) == borrow(+), withdraw(-) == payback(-).
    ///     -  Liquidity must be on the winning side (deposit < borrow OR payback < withdraw).
    function operate(
        address token_,
        int256 supplyAmount_,
        int256 borrowAmount_,
        address withdrawTo_,
        address borrowTo_,
        bytes calldata callbackData_
    ) external payable returns (uint256 memVar3_, uint256 memVar4_);
}

interface IFluidLiquidity is IProxy, IFluidLiquidityLogic {}
error.sol 6 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

contract Error {
    error FluidOracleError(uint256 errorId_);
}
errorTypes.sol 151 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

library ErrorTypes {
    /***********************************|
    |           FluidOracleL2           | 
    |__________________________________*/

    /// @notice thrown when sequencer on a L2 has an outage and grace period has not yet passed.
    uint256 internal constant FluidOracleL2__SequencerOutage = 60000;

    /***********************************|
    |     UniV3CheckCLRSOracle          | 
    |__________________________________*/

    /// @notice thrown when the delta between main price source and check rate source is exceeding the allowed delta
    uint256 internal constant UniV3CheckCLRSOracle__InvalidPrice = 60001;

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant UniV3CheckCLRSOracle__InvalidParams = 60002;

    /// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
    uint256 internal constant UniV3CheckCLRSOracle__ExchangeRateZero = 60003;

    /***********************************|
    |           FluidOracle             | 
    |__________________________________*/

    /// @notice thrown when an invalid info name is passed into a fluid oracle (e.g. not set or too long)
    uint256 internal constant FluidOracle__InvalidInfoName = 60010;

    /***********************************|
    |            sUSDe Oracle           | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant SUSDeOracle__InvalidParams = 60102;

    /***********************************|
    |           Pendle Oracle           | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant PendleOracle__InvalidParams = 60201;

    /// @notice thrown when the Pendle market Oracle has not been initialized yet
    uint256 internal constant PendleOracle__MarketNotInitialized = 60202;

    /// @notice thrown when the Pendle market does not have 18 decimals
    uint256 internal constant PendleOracle__MarketInvalidDecimals = 60203;

    /// @notice thrown when the Pendle market returns an unexpected price
    uint256 internal constant PendleOracle__InvalidPrice = 60204;

    /***********************************|
    |    CLRS2UniV3CheckCLRSOracleL2    | 
    |__________________________________*/

    /// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
    uint256 internal constant CLRS2UniV3CheckCLRSOracleL2__ExchangeRateZero = 60301;

    /***********************************|
    |    Ratio2xFallbackCLRSOracleL2    | 
    |__________________________________*/

    /// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
    uint256 internal constant Ratio2xFallbackCLRSOracleL2__ExchangeRateZero = 60311;

    /***********************************|
    |            WeETHsOracle           | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant WeETHsOracle__InvalidParams = 60321;

    /***********************************|
    |          Chainlink Oracle         | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant ChainlinkOracle__InvalidParams = 61001;

    /***********************************|
    |          UniswapV3 Oracle         | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant UniV3Oracle__InvalidParams = 62001;

    /// @notice thrown when constructor is called with invalid ordered seconds agos values
    uint256 internal constant UniV3Oracle__InvalidSecondsAgos = 62002;

    /// @notice thrown when constructor is called with invalid delta values > 100%
    uint256 internal constant UniV3Oracle__InvalidDeltas = 62003;

    /***********************************|
    |            WstETh Oracle          | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant WstETHOracle__InvalidParams = 63001;

    /***********************************|
    |           Redstone Oracle         | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant RedstoneOracle__InvalidParams = 64001;

    /***********************************|
    |          Fallback Oracle          | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant FallbackOracle__InvalidParams = 65001;

    /***********************************|
    |       FallbackCLRSOracle          | 
    |__________________________________*/

    /// @notice thrown when the exchange rate is zero, even for the fallback oracle source (if enabled)
    uint256 internal constant FallbackCLRSOracle__ExchangeRateZero = 66001;

    /***********************************|
    |         WstETHCLRSOracle          | 
    |__________________________________*/

    /// @notice thrown when the exchange rate is zero, even for the fallback oracle source (if enabled)
    uint256 internal constant WstETHCLRSOracle__ExchangeRateZero = 67001;

    /***********************************|
    |        CLFallbackUniV3Oracle      | 
    |__________________________________*/

    /// @notice thrown when the exchange rate is zero, even for the uniV3 rate
    uint256 internal constant CLFallbackUniV3Oracle__ExchangeRateZero = 68001;

    /***********************************|
    |  WstETHCLRS2UniV3CheckCLRSOracle  | 
    |__________________________________*/

    /// @notice thrown when the exchange rate is zero, even for the uniV3 rate
    uint256 internal constant WstETHCLRS2UniV3CheckCLRSOracle__ExchangeRateZero = 69001;

    /***********************************|
    |             WeETh Oracle          | 
    |__________________________________*/

    /// @notice thrown when an invalid parameter is passed to a method
    uint256 internal constant WeETHOracle__InvalidParams = 70001;
}
fluidOracle.sol 51 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { IFluidOracle } from "./interfaces/iFluidOracle.sol";
import { ErrorTypes } from "./errorTypes.sol";
import { Error as OracleError } from "./error.sol";

/// @title   FluidOracle
/// @notice  Base contract that any Fluid Oracle must implement
abstract contract FluidOracle is IFluidOracle, OracleError {
    /// @dev short helper string to easily identify the oracle. E.g. token symbols
    //
    // using a bytes32 because string can not be immutable.
    bytes32 private immutable _infoName;

    constructor(string memory infoName_) {
        if (bytes(infoName_).length > 32 || bytes(infoName_).length == 0) {
            revert FluidOracleError(ErrorTypes.FluidOracle__InvalidInfoName);
        }

        // convert string to bytes32
        bytes32 infoNameBytes32_;
        assembly {
            infoNameBytes32_ := mload(add(infoName_, 32))
        }
        _infoName = infoNameBytes32_;
    }

    /// @inheritdoc IFluidOracle
    function infoName() external view returns (string memory) {
        // convert bytes32 to string
        uint256 length_;
        while (length_ < 32 && _infoName[length_] != 0) {
            length_++;
        }
        bytes memory infoNameBytes_ = new bytes(length_);
        for (uint256 i; i < length_; i++) {
            infoNameBytes_[i] = _infoName[i];
        }
        return string(infoNameBytes_);
    }

    /// @inheritdoc IFluidOracle
    function getExchangeRate() external view virtual returns (uint256 exchangeRate_);

    /// @inheritdoc IFluidOracle
    function getExchangeRateOperate() external view virtual returns (uint256 exchangeRate_);

    /// @inheritdoc IFluidOracle
    function getExchangeRateLiquidate() external view virtual returns (uint256 exchangeRate_);
}
iFluidOracle.sol 17 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IFluidOracle {
    /// @dev Deprecated. Use `getExchangeRateOperate()` and `getExchangeRateLiquidate()` instead. Only implemented for
    ///      backwards compatibility.
    function getExchangeRate() external view returns (uint256 exchangeRate_);

    /// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for operates
    function getExchangeRateOperate() external view returns (uint256 exchangeRate_);

    /// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for liquidations
    function getExchangeRateLiquidate() external view returns (uint256 exchangeRate_);

    /// @notice helper string to easily identify the oracle. E.g. token symbols
    function infoName() external view returns (string memory);
}
iDexT1.sol 294 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IFluidDexT1 {
    error FluidDexError(uint256 errorId);

    /// @notice used to simulate swap to find the output amount
    error FluidDexSwapResult(uint256 amountOut);

    error FluidDexPerfectLiquidityOutput(uint256 token0Amt, uint token1Amt);

    error FluidDexSingleTokenOutput(uint256 tokenAmt);

    error FluidDexLiquidityOutput(uint256 shares);

    error FluidDexPricesAndExchangeRates(PricesAndExchangePrice pex_);

    /// @notice returns the dex id
    function DEX_ID() external view returns (uint256);

    /// @notice reads uint256 data `result_` from storage at a bytes32 storage `slot_` key.
    function readFromStorage(bytes32 slot_) external view returns (uint256 result_);

    struct Implementations {
        address shift;
        address admin;
        address colOperations;
        address debtOperations;
        address perfectOperationsAndOracle;
    }

    struct ConstantViews {
        uint256 dexId;
        address liquidity;
        address factory;
        Implementations implementations;
        address deployerContract;
        address token0;
        address token1;
        bytes32 supplyToken0Slot;
        bytes32 borrowToken0Slot;
        bytes32 supplyToken1Slot;
        bytes32 borrowToken1Slot;
        bytes32 exchangePriceToken0Slot;
        bytes32 exchangePriceToken1Slot;
        uint256 oracleMapping;
    }

    struct ConstantViews2 {
        uint token0NumeratorPrecision;
        uint token0DenominatorPrecision;
        uint token1NumeratorPrecision;
        uint token1DenominatorPrecision;
    }

    struct PricesAndExchangePrice {
        uint lastStoredPrice; // last stored price in 1e27 decimals
        uint centerPrice; // last stored price in 1e27 decimals
        uint upperRange; // price at upper range in 1e27 decimals
        uint lowerRange; // price at lower range in 1e27 decimals
        uint geometricMean; // geometric mean of upper range & lower range in 1e27 decimals
        uint supplyToken0ExchangePrice;
        uint borrowToken0ExchangePrice;
        uint supplyToken1ExchangePrice;
        uint borrowToken1ExchangePrice;
    }

    struct CollateralReserves {
        uint token0RealReserves;
        uint token1RealReserves;
        uint token0ImaginaryReserves;
        uint token1ImaginaryReserves;
    }

    struct DebtReserves {
        uint token0Debt;
        uint token1Debt;
        uint token0RealReserves;
        uint token1RealReserves;
        uint token0ImaginaryReserves;
        uint token1ImaginaryReserves;
    }

    function getCollateralReserves(
        uint geometricMean_,
        uint upperRange_,
        uint lowerRange_,
        uint token0SupplyExchangePrice_,
        uint token1SupplyExchangePrice_
    ) external view returns (CollateralReserves memory c_);

    function getDebtReserves(
        uint geometricMean_,
        uint upperRange_,
        uint lowerRange_,
        uint token0BorrowExchangePrice_,
        uint token1BorrowExchangePrice_
    ) external view returns (DebtReserves memory d_);

    // reverts with FluidDexPricesAndExchangeRates(pex_);
    function getPricesAndExchangePrices() external;

    function constantsView() external view returns (ConstantViews memory constantsView_);

    function constantsView2() external view returns (ConstantViews2 memory constantsView2_);

    struct Oracle {
        uint twap1by0; // TWAP price
        uint lowestPrice1by0; // lowest price point
        uint highestPrice1by0; // highest price point
        uint twap0by1; // TWAP price
        uint lowestPrice0by1; // lowest price point
        uint highestPrice0by1; // highest price point
    }

    /// @dev This function allows users to swap a specific amount of input tokens for output tokens
    /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
    /// @param amountIn_ The exact amount of input tokens to swap
    /// @param amountOutMin_ The minimum amount of output tokens the user is willing to accept
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountOut_
    /// @return amountOut_ The amount of output tokens received from the swap
    function swapIn(
        bool swap0to1_,
        uint256 amountIn_,
        uint256 amountOutMin_,
        address to_
    ) external payable returns (uint256 amountOut_);

    /// @dev Swap tokens with perfect amount out
    /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
    /// @param amountOut_ The exact amount of tokens to receive after swap
    /// @param amountInMax_ Maximum amount of tokens to swap in
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountIn_
    /// @return amountIn_ The amount of input tokens used for the swap
    function swapOut(
        bool swap0to1_,
        uint256 amountOut_,
        uint256 amountInMax_,
        address to_
    ) external payable returns (uint256 amountIn_);

    /// @dev Deposit tokens in equal proportion to the current pool ratio
    /// @param shares_ The number of shares to mint
    /// @param maxToken0Deposit_ Maximum amount of token0 to deposit
    /// @param maxToken1Deposit_ Maximum amount of token1 to deposit
    /// @param estimate_ If true, function will revert with estimated deposit amounts without executing the deposit
    /// @return token0Amt_ Amount of token0 deposited
    /// @return token1Amt_ Amount of token1 deposited
    function depositPerfect(
        uint shares_,
        uint maxToken0Deposit_,
        uint maxToken1Deposit_,
        bool estimate_
    ) external payable returns (uint token0Amt_, uint token1Amt_);

    /// @dev This function allows users to withdraw a perfect amount of collateral liquidity
    /// @param shares_ The number of shares to withdraw
    /// @param minToken0Withdraw_ The minimum amount of token0 the user is willing to accept
    /// @param minToken1Withdraw_ The minimum amount of token1 the user is willing to accept
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with token0Amt_ & token1Amt_
    /// @return token0Amt_ The amount of token0 withdrawn
    /// @return token1Amt_ The amount of token1 withdrawn
    function withdrawPerfect(
        uint shares_,
        uint minToken0Withdraw_,
        uint minToken1Withdraw_,
        address to_
    ) external returns (uint token0Amt_, uint token1Amt_);

    /// @dev This function allows users to borrow tokens in equal proportion to the current debt pool ratio
    /// @param shares_ The number of shares to borrow
    /// @param minToken0Borrow_ Minimum amount of token0 to borrow
    /// @param minToken1Borrow_ Minimum amount of token1 to borrow
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with token0Amt_ & token1Amt_
    /// @return token0Amt_ Amount of token0 borrowed
    /// @return token1Amt_ Amount of token1 borrowed
    function borrowPerfect(
        uint shares_,
        uint minToken0Borrow_,
        uint minToken1Borrow_,
        address to_
    ) external returns (uint token0Amt_, uint token1Amt_);

    /// @dev This function allows users to pay back borrowed tokens in equal proportion to the current debt pool ratio
    /// @param shares_ The number of shares to pay back
    /// @param maxToken0Payback_ Maximum amount of token0 to pay back
    /// @param maxToken1Payback_ Maximum amount of token1 to pay back
    /// @param estimate_ If true, function will revert with estimated payback amounts without executing the payback
    /// @return token0Amt_ Amount of token0 paid back
    /// @return token1Amt_ Amount of token1 paid back
    function paybackPerfect(
        uint shares_,
        uint maxToken0Payback_,
        uint maxToken1Payback_,
        bool estimate_
    ) external payable returns (uint token0Amt_, uint token1Amt_);

    /// @dev This function allows users to deposit tokens in any proportion into the col pool
    /// @param token0Amt_ The amount of token0 to deposit
    /// @param token1Amt_ The amount of token1 to deposit
    /// @param minSharesAmt_ The minimum amount of shares the user expects to receive
    /// @param estimate_ If true, function will revert with estimated shares without executing the deposit
    /// @return shares_ The amount of shares minted for the deposit
    function deposit(
        uint token0Amt_,
        uint token1Amt_,
        uint minSharesAmt_,
        bool estimate_
    ) external payable returns (uint shares_);

    /// @dev This function allows users to withdraw tokens in any proportion from the col pool
    /// @param token0Amt_ The amount of token0 to withdraw
    /// @param token1Amt_ The amount of token1 to withdraw
    /// @param maxSharesAmt_ The maximum number of shares the user is willing to burn
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with shares_
    /// @return shares_ The number of shares burned for the withdrawal
    function withdraw(
        uint token0Amt_,
        uint token1Amt_,
        uint maxSharesAmt_,
        address to_
    ) external returns (uint shares_);

    /// @dev This function allows users to borrow tokens in any proportion from the debt pool
    /// @param token0Amt_ The amount of token0 to borrow
    /// @param token1Amt_ The amount of token1 to borrow
    /// @param maxSharesAmt_ The maximum amount of shares the user is willing to receive
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with shares_
    /// @return shares_ The amount of borrow shares minted to represent the borrowed amount
    function borrow(
        uint token0Amt_,
        uint token1Amt_,
        uint maxSharesAmt_,
        address to_
    ) external returns (uint shares_);

    /// @dev This function allows users to payback tokens in any proportion to the debt pool
    /// @param token0Amt_ The amount of token0 to payback
    /// @param token1Amt_ The amount of token1 to payback
    /// @param minSharesAmt_ The minimum amount of shares the user expects to burn
    /// @param estimate_ If true, function will revert with estimated shares without executing the payback
    /// @return shares_ The amount of borrow shares burned for the payback
    function payback(
        uint token0Amt_,
        uint token1Amt_,
        uint minSharesAmt_,
        bool estimate_
    ) external payable returns (uint shares_);

    /// @dev This function allows users to withdraw their collateral with perfect shares in one token
    /// @param shares_ The number of shares to burn for withdrawal
    /// @param minToken0_ The minimum amount of token0 the user expects to receive (set to 0 if withdrawing in token1)
    /// @param minToken1_ The minimum amount of token1 the user expects to receive (set to 0 if withdrawing in token0)
    /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with withdrawAmt_
    /// @return withdrawAmt_ The amount of tokens withdrawn in the chosen token
    function withdrawPerfectInOneToken(
        uint shares_,
        uint minToken0_,
        uint minToken1_,
        address to_
    ) external returns (
        uint withdrawAmt_
    );

    /// @dev This function allows users to payback their debt with perfect shares in one token
    /// @param shares_ The number of shares to burn for payback
    /// @param maxToken0_ The maximum amount of token0 the user is willing to pay (set to 0 if paying back in token1)
    /// @param maxToken1_ The maximum amount of token1 the user is willing to pay (set to 0 if paying back in token0)
    /// @param estimate_ If true, the function will revert with the estimated payback amount without executing the payback
    /// @return paybackAmt_ The amount of tokens paid back in the chosen token
    function paybackPerfectInOneToken(
        uint shares_,
        uint maxToken0_,
        uint maxToken1_,
        bool estimate_
    ) external payable returns (
        uint paybackAmt_
    );

    /// @dev the oracle assumes last set price of pool till the next swap happens.
    /// There's a possibility that during that time some interest is generated hence the last stored price is not the 100% correct price for the whole duration
    /// but the difference due to interest will be super low so this difference is ignored
    /// For example 2 swaps happened 10min (600 seconds) apart and 1 token has 10% higher interest than other.
    /// then that token will accrue about 10% * 600 / secondsInAYear = ~0.0002%
    /// @param secondsAgos_ array of seconds ago for which TWAP is needed. If user sends [10, 30, 60] then twaps_ will return [10-0, 30-10, 60-30]
    /// @return twaps_ twap price, lowest price (aka minima) & highest price (aka maxima) between secondsAgo checkpoints
    /// @return currentPrice_ price of pool after the most recent swap
    function oraclePrice(
        uint[] memory secondsAgos_
    ) external view returns (
        Oracle[] memory twaps_,
        uint currentPrice_
    );
}
error.sol 9 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

abstract contract Error {
    error FluidVaultError(uint256 errorId_);

    /// @notice used to simulate liquidation to find the maximum liquidatable amounts
    error FluidLiquidateResult(uint256 colLiquidated, uint256 debtLiquidated);
}
errorTypes.sol 212 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

library ErrorTypes {
    /***********************************|
    |           Vault Factory           | 
    |__________________________________*/

    uint256 internal constant VaultFactory__InvalidOperation = 30001;
    uint256 internal constant VaultFactory__Unauthorized = 30002;
    uint256 internal constant VaultFactory__SameTokenNotAllowed = 30003;
    uint256 internal constant VaultFactory__InvalidParams = 30004;
    uint256 internal constant VaultFactory__InvalidVault = 30005;
    uint256 internal constant VaultFactory__InvalidVaultAddress = 30006;
    uint256 internal constant VaultFactory__OnlyDelegateCallAllowed = 30007;

    /***********************************|
    |            Vault                  | 
    |__________________________________*/

    /// @notice thrown at reentrancy
    uint256 internal constant Vault__AlreadyEntered = 31001;

    /// @notice thrown when user sends deposit & borrow amount as 0
    uint256 internal constant Vault__InvalidOperateAmount = 31002;

    /// @notice thrown when msg.value is not in sync with native token deposit or payback
    uint256 internal constant Vault__InvalidMsgValueOperate = 31003;

    /// @notice thrown when msg.sender is not the owner of the vault
    uint256 internal constant Vault__NotAnOwner = 31004;

    /// @notice thrown when user's position does not exist. Sending the wrong index from the frontend
    uint256 internal constant Vault__TickIsEmpty = 31005;

    /// @notice thrown when the user's position is above CF and the user tries to make it more risky by trying to withdraw or borrow
    uint256 internal constant Vault__PositionAboveCF = 31006;

    /// @notice thrown when the top tick is not initialized. Happens if the vault is totally new or all the user's left
    uint256 internal constant Vault__TopTickDoesNotExist = 31007;

    /// @notice thrown when msg.value in liquidate is not in sync payback
    uint256 internal constant Vault__InvalidMsgValueLiquidate = 31008;

    /// @notice thrown when slippage is more on liquidation than what the liquidator sent
    uint256 internal constant Vault__ExcessSlippageLiquidation = 31009;

    /// @notice thrown when msg.sender is not the rebalancer/reserve contract
    uint256 internal constant Vault__NotRebalancer = 31010;

    /// @notice thrown when NFT of one vault interacts with the NFT of other vault
    uint256 internal constant Vault__NftNotOfThisVault = 31011;

    /// @notice thrown when the token is not initialized on the liquidity contract
    uint256 internal constant Vault__TokenNotInitialized = 31012;

    /// @notice thrown when admin updates fallback if a non-auth calls vault
    uint256 internal constant Vault__NotAnAuth = 31013;

    /// @notice thrown in operate when user tries to witdhraw more collateral than deposited
    uint256 internal constant Vault__ExcessCollateralWithdrawal = 31014;

    /// @notice thrown in operate when user tries to payback more debt than borrowed
    uint256 internal constant Vault__ExcessDebtPayback = 31015;

    /// @notice thrown when user try to withdrawal more than operate's withdrawal limit
    uint256 internal constant Vault__WithdrawMoreThanOperateLimit = 31016;

    /// @notice thrown when caller of liquidityCallback is not Liquidity
    uint256 internal constant Vault__InvalidLiquidityCallbackAddress = 31017;

    /// @notice thrown when reentrancy is not already on
    uint256 internal constant Vault__NotEntered = 31018;

    /// @notice thrown when someone directly calls operate or secondary implementation contract
    uint256 internal constant Vault__OnlyDelegateCallAllowed = 31019;

    /// @notice thrown when the safeTransferFrom for a token amount failed
    uint256 internal constant Vault__TransferFromFailed = 31020;

    /// @notice thrown when exchange price overflows while updating on storage
    uint256 internal constant Vault__ExchangePriceOverFlow = 31021;

    /// @notice thrown when debt to liquidate amt is sent wrong
    uint256 internal constant Vault__InvalidLiquidationAmt = 31022;

    /// @notice thrown when user debt or collateral goes above 2**128 or below -2**128
    uint256 internal constant Vault__UserCollateralDebtExceed = 31023;

    /// @notice thrown if on liquidation branch debt becomes lower than 100
    uint256 internal constant Vault__BranchDebtTooLow = 31024;

    /// @notice thrown when tick's debt is less than 10000
    uint256 internal constant Vault__TickDebtTooLow = 31025;

    /// @notice thrown when the received new liquidity exchange price is of unexpected value (< than the old one)
    uint256 internal constant Vault__LiquidityExchangePriceUnexpected = 31026;

    /// @notice thrown when user's debt is less than 10000
    uint256 internal constant Vault__UserDebtTooLow = 31027;

    /// @notice thrown when on only payback and only deposit the ratio of position increases
    uint256 internal constant Vault__InvalidPaybackOrDeposit = 31028;

    /// @notice thrown when liquidation just happens of a single partial or when there's nothing to liquidate
    uint256 internal constant Vault__InvalidLiquidation = 31029;

    /// @notice thrown when msg.value is sent wrong in rebalance
    uint256 internal constant Vault__InvalidMsgValueInRebalance = 31030;

    /// @notice thrown when nothing rebalanced
    uint256 internal constant Vault__NothingToRebalance = 31031;

    /// @notice thrown on unforseen liquidation scenarios. Might never come in use.
    uint256 internal constant Vault__LiquidationReverts = 31032;

    /// @notice thrown when oracle price is > 1e54
    uint256 internal constant Vault__InvalidOraclePrice = 31033;

    /// @notice thrown when constants are not set properly via contructor
    uint256 internal constant Vault__ImproperConstantsSetup = 31034;

    /// @notice thrown when externally calling fetchLatestPosition function
    uint256 internal constant Vault__FetchLatestPositionFailed = 31035;

    /// @notice thrown when dex callback is not from dex
    uint256 internal constant Vault__InvalidDexCallbackAddress = 31036;

    /// @notice thrown when dex callback is already set
    uint256 internal constant Vault__DexFromAddressAlreadySet = 31037;

    /// @notice thrown when an invalid min / max amounts config is passed to rebalance()
    uint256 internal constant Vault__InvalidMinMaxInRebalance = 31038;

    /***********************************|
    |              ERC721               | 
    |__________________________________*/

    uint256 internal constant ERC721__InvalidParams = 32001;
    uint256 internal constant ERC721__Unauthorized = 32002;
    uint256 internal constant ERC721__InvalidOperation = 32003;
    uint256 internal constant ERC721__UnsafeRecipient = 32004;
    uint256 internal constant ERC721__OutOfBoundsIndex = 32005;

    /***********************************|
    |            Vault Admin            | 
    |__________________________________*/

    /// @notice thrown when admin tries to setup invalid value which are crossing limits
    uint256 internal constant VaultAdmin__ValueAboveLimit = 33001;

    /// @notice when someone directly calls admin implementation contract
    uint256 internal constant VaultAdmin__OnlyDelegateCallAllowed = 33002;

    /// @notice thrown when auth sends NFT ID as 0 while collecting dust debt
    uint256 internal constant VaultAdmin__NftIdShouldBeNonZero = 33003;

    /// @notice thrown when trying to collect dust debt of NFT which is not of this vault
    uint256 internal constant VaultAdmin__NftNotOfThisVault = 33004;

    /// @notice thrown when dust debt of NFT is 0, meaning nothing to collect
    uint256 internal constant VaultAdmin__DustDebtIsZero = 33005;

    /// @notice thrown when final debt after liquidation is not 0, meaning position 100% liquidated
    uint256 internal constant VaultAdmin__FinalDebtShouldBeZero = 33006;

    /// @notice thrown when NFT is not liquidated state
    uint256 internal constant VaultAdmin__NftNotLiquidated = 33007;

    /// @notice thrown when total absorbed dust debt is 0
    uint256 internal constant VaultAdmin__AbsorbedDustDebtIsZero = 33008;

    /// @notice thrown when address is set as 0
    uint256 internal constant VaultAdmin__AddressZeroNotAllowed = 33009;

    /***********************************|
    |            Vault Rewards          | 
    |__________________________________*/

    uint256 internal constant VaultRewards__Unauthorized = 34001;
    uint256 internal constant VaultRewards__AddressZero = 34002;
    uint256 internal constant VaultRewards__InvalidParams = 34003;
    uint256 internal constant VaultRewards__NewMagnifierSameAsOldMagnifier = 34004;
    uint256 internal constant VaultRewards__NotTheInitiator = 34005;
    uint256 internal constant VaultRewards__NotTheGovernance = 34006;
    uint256 internal constant VaultRewards__AlreadyStarted = 34007;
    uint256 internal constant VaultRewards__RewardsNotStartedOrEnded = 34008;
    uint256 internal constant VaultRewards__InvalidStartTime = 34009;
    uint256 internal constant VaultRewards__AlreadyEnded = 34010;

    /***********************************|
    |          Vault DEX Types          | 
    |__________________________________*/

    uint256 internal constant VaultDex__InvalidOperateAmount = 35001;
    uint256 internal constant VaultDex__DebtSharesPaidMoreThanAvailableLiquidation = 35002;

    /***********************************|
    |        Vault Borrow Rewards       | 
    |__________________________________*/

    uint256 internal constant VaultBorrowRewards__Unauthorized = 36001;
    uint256 internal constant VaultBorrowRewards__AddressZero = 36002;
    uint256 internal constant VaultBorrowRewards__InvalidParams = 36003;
    uint256 internal constant VaultBorrowRewards__NewMagnifierSameAsOldMagnifier = 36004;
    uint256 internal constant VaultBorrowRewards__NotTheInitiator = 36005;
    uint256 internal constant VaultBorrowRewards__NotTheGovernance = 36006;
    uint256 internal constant VaultBorrowRewards__AlreadyStarted = 36007;
    uint256 internal constant VaultBorrowRewards__RewardsNotStartedOrEnded = 36008;
    uint256 internal constant VaultBorrowRewards__InvalidStartTime = 36009;
    uint256 internal constant VaultBorrowRewards__AlreadyEnded = 36010;
}
iLiquidityDexCommon.sol 9 lines
//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { IFluidLiquidityLogic } from "../../../liquidity/interfaces/iLiquidity.sol";
import { IFluidDexT1 } from "../../dex/interfaces/iDexT1.sol";

interface ILiquidityDexCommon is IFluidLiquidityLogic, IFluidDexT1 {
    /// @notice only importing IFluidLiquidityLogic as readFromStorage is also defined in iDexT1 as well so to avoid clashing
}
iVaultFactory.sol 27 lines
//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";

interface IFluidVaultFactory is IERC721Enumerable {
    /// @notice Minting an NFT Vault for the user
    function mint(uint256 vaultId_, address user_) external returns (uint256 tokenId_);

    /// @notice returns owner of Vault which is also an NFT
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /// @notice Global auth is auth for all vaults
    function isGlobalAuth(address auth_) external view returns (bool);

    /// @notice Vault auth is auth for a specific vault
    function isVaultAuth(address vault_, address auth_) external view returns (bool);

    /// @notice Total vaults deployed.
    function totalVaults() external view returns (uint256);

    /// @notice Compute vaultAddress
    function getVaultAddress(uint256 vaultId) external view returns (address);

    /// @notice read uint256 `result_` for a storage `slot_` key
    function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
}
main.sol 255 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { ErrorTypes } from "../../errorTypes.sol";
import { FluidVault } from "../../vaultTypesCommon/coreModule/main.sol";

/// @notice Fluid "VaultT3" (Vault Type 3). Fluid vault protocol main contract. T3 -> Normal collateral | Smart debt
///         Fluid Vault protocol is a borrow / lending protocol, allowing users to create collateral / borrow positions.
///         All funds are deposited into / borrowed from Fluid Liquidity layer.
///         Positions are represented through NFTs minted by the VaultFactory.
///         Deployed by "VaultFactory" and linked together with Vault AdminModule `ADMIN_IMPLEMENTATION` and
///         FluidVaultSecondary (main2.sol) `SECONDARY_IMPLEMENTATION`.
///         AdminModule & FluidVaultSecondary methods are delegateCalled, if the msg.sender has the required authorization.
///         This contract links to an Oracle, which is used to assess collateral / debt value. Oracles implement the
///         "FluidOracle" base contract and return the price in 1e27 precision.
/// @dev    For view methods / accessing data, use the "VaultResolver" periphery contract.
//
// vaults can only be deployed for tokens that are listed at Liquidity (constructor reverts otherwise
// if either the exchange price for the supply token or the borrow token is still not set at Liquidity).
abstract contract Internals is FluidVault {
    function _debtLiquidateBefore(
        uint token0DebtAmt_,
        uint token1DebtAmt_,
        uint debtSharesMin_
    ) internal returns (uint shares_) {
        // paying back and burning shares
        if (token0DebtAmt_ == 0 && token1DebtAmt_ == 0) {
            // when burning shares, debt amount should always be > 0 (aka payback)
            revert FluidVaultError(ErrorTypes.VaultDex__InvalidOperateAmount);
        }

        shares_ = BORROW.payback{
            value: (BORROW_TOKEN0 == NATIVE_TOKEN)
                ? token0DebtAmt_
                : (BORROW_TOKEN1 == NATIVE_TOKEN)
                    ? token1DebtAmt_
                    : 0
        }(token0DebtAmt_, token1DebtAmt_, debtSharesMin_, false);
    }

    function _debtLiquidatePerfectPayback(
        uint perfectDebtShares_,
        uint token0DebtAmtPerUnitShares_,
        uint token1DebtAmtPerUnitShares_
    ) internal returns (uint token0DebtPaid_, uint token1DebtPaid_) {
        uint debtToken0Min_ = (token0DebtAmtPerUnitShares_ * perfectDebtShares_) / 1e18;
        uint debtToken1Min_ = (token1DebtAmtPerUnitShares_ * perfectDebtShares_) / 1e18;

        if (debtToken0Min_ > 0 && debtToken1Min_ > 0) {
            (token0DebtPaid_, token1DebtPaid_) = BORROW.paybackPerfect{
                value: (BORROW_TOKEN0 == NATIVE_TOKEN)
                    ? debtToken0Min_
                    : (BORROW_TOKEN1 == NATIVE_TOKEN)
                        ? debtToken1Min_
                        : 0
            }(perfectDebtShares_, debtToken0Min_, debtToken1Min_, false);
        } else if (debtToken0Min_ > 0 && debtToken1Min_ == 0) {
            // payback only in token0, token1DebtPaid_ remains 0
            (token0DebtPaid_) = BORROW.paybackPerfectInOneToken{
                value: (BORROW_TOKEN0 == NATIVE_TOKEN) ? debtToken0Min_ : 0
            }(perfectDebtShares_, debtToken0Min_, debtToken1Min_, false);
        } else if (debtToken0Min_ == 0 && debtToken1Min_ > 0) {
            // payback only in token1, token0DebtPaid_ remains 0
            (token1DebtPaid_) = BORROW.paybackPerfectInOneToken{
                value: (BORROW_TOKEN1 == NATIVE_TOKEN) ? debtToken1Min_ : 0
            }(perfectDebtShares_, debtToken0Min_, debtToken1Min_, false);
        } else {
            // both sent as 0
            revert FluidVaultError(ErrorTypes.VaultDex__InvalidOperateAmount);
        }
    }

    constructor(ConstantViews memory constants_) FluidVault(constants_) {}
}

contract FluidVaultT3 is Internals {
    /// @notice Performs operations on a vault position
    /// @dev This function allows users to modify their vault position by adjusting collateral and debt
    /// @param nftId_ The ID of the NFT representing the vault position
    /// @param newCol_ The change in collateral amount (positive for deposit, negative for withdrawal)
    /// @param newDebtToken0_ The change in debt amount for token0 (positive for borrowing, negative for repayment)
    /// @param newDebtToken1_ The change in debt amount for token1 (positive for borrowing, negative for repayment)
    /// @param debtSharesMinMax_ Min or max debt shares to burn or mint (positive for borrowing, negative for repayment)
    /// @param to_ The address to receive withdrawn collateral or borrowed tokens (if address(0), defaults to msg.sender)
    /// @return nftId_ The ID of the NFT representing the updated vault position
    /// @return supplyAmt_ Final supply amount (negative if withdrawal occurred)
    /// @return borrowAmt_ Final borrow amount (negative if repayment occurred)
    /// @custom:security Re-entrancy protection is implemented
    /// @custom:security ETH balance is validated before and after operation
    function operate(
        uint nftId_,
        int newCol_,
        int newDebtToken0_,
        int newDebtToken1_,
        int debtSharesMinMax_,
        address to_
    )
        external
        payable
        _dexFromAddress
        returns (
            uint256, // nftId_
            int256, // final supply amount. if - then withdraw
            int256 // final borrow amount. if - then payback
        )
    {
        return abi.decode(_spell(OPERATE_IMPLEMENTATION, msg.data), (uint, int, int));
    }

    /// @notice Performs operations on a vault position with perfect collateral shares
    /// @dev This function allows users to modify their vault position by adjusting collateral and debt
    /// @param nftId_ The ID of the NFT representing the vault position
    /// @param newCol_ The change in collateral amount (positive for deposit, negative for withdrawal)
    /// @param perfectDebtShares_ The change in debt shares (positive for borrowing, negative for repayment)
    /// @param debtToken0MinMax_ Min or max debt amount for token0 to payback or borrow (positive for borrowing, negative for repayment)
    /// @param debtToken1MinMax_ Min or max debt amount for token1 to payback or borrow (positive for borrowing, negative for repayment)
    /// @param to_ The address to receive withdrawn collateral or borrowed tokens (if address(0), defaults to msg.sender)
    /// @return nftId_ The ID of the NFT representing the updated vault position
    /// @return r_ int256 array of return values:
    ///              0 - col amount, will only change if user sends type(int).min
    ///              1 - final debt shares amount (can only change on max payback)
    ///              2 - token0 borrow or payback amount
    ///              3 - token1 borrow or payback amount
    function operatePerfect(
        uint nftId_,
        int newCol_,
        int perfectDebtShares_,
        int debtToken0MinMax_,
        int debtToken1MinMax_,
        address to_
    )
        external
        payable
        _dexFromAddress
        returns (
            uint256, // nftId_
            int256[] memory r_
        )
    {
        return abi.decode(_spell(OPERATE_IMPLEMENTATION, msg.data), (uint, int256[]));
    }

    /// @notice Liquidates a vault position
    /// @dev This function allows users to liquidate a vault position by adjusting collateral and debt
    /// @param token0DebtAmt_ The amount of debt in token0 to payback
    /// @param token1DebtAmt_ The amount of debt in token1 to payback
    /// @param debtSharesMin_ The minimum number of debt shares to liquidate
    /// @param colPerUnitDebt_ The collateral amount per unit of debt shares
    /// @param to_ The address to receive withdrawn collateral (if address(0), defaults to msg.sender)
    /// @param absorb_ Whether to liquidate absorbed liquidity as well
    /// @return actualDebtShares_ The actual number of debt shares liquidated
    /// @return actualCol_ The actual amount of collateral withdrawn
    function liquidate(
        uint256 token0DebtAmt_,
        uint256 token1DebtAmt_,
        uint256 debtSharesMin_,
        uint256 colPerUnitDebt_, // col per unit is w.r.t debt shares and not token0/1 debt amount
        address to_,
        bool absorb_
    ) external payable _dexFromAddress returns (uint256 actualDebtShares_, uint256 actualCol_) {
        uint vaultVariables_ = vaultVariables;
        // ############# turning re-entrancy bit on #############
        if (vaultVariables_ & 1 == 0) {
            // Updating on storage
            vaultVariables = vaultVariables_ | 1;
        } else {
            revert FluidVaultError(ErrorTypes.Vault__AlreadyEntered);
        }

        uint initialEth_ = address(this).balance - msg.value;

        to_ = to_ == address(0) ? msg.sender : to_;

        uint sharesPaid_ = _debtLiquidateBefore(token0DebtAmt_, token1DebtAmt_, debtSharesMin_);
        (actualDebtShares_, actualCol_, vaultVariables_) = abi.decode(
            _liquidate(sharesPaid_, colPerUnitDebt_, to_, absorb_, vaultVariables_),
            (uint, uint, uint)
        );

        if (actualDebtShares_ < sharesPaid_) {
            // shares paid should never be more than actual liquidation available
            revert FluidVaultError(ErrorTypes.VaultDex__DebtSharesPaidMoreThanAvailableLiquidation);
        }

        // disabling re-entrancy and updating on storage
        vaultVariables = vaultVariables_;

        _validateEth(initialEth_);
    }

    struct LiquidatePerfect {
        uint256 vaultVariables;
        uint256 initialEth;
    }

    /// @notice Liquidates a vault position with perfect collateral shares
    /// @dev This function allows users to liquidate a vault position by adjusting collateral and debt
    /// @param debtShares_ The amount of debt shares to liquidate
    /// @param token0DebtAmtPerUnitShares_ The amount of debt in token0 per unit of debt shares (if sent 0 then entire payback is in token1)
    /// @param token1DebtAmtPerUnitShares_ The amount of debt in token1 per unit of debt shares (if sent 0 then entire payback is in token0)
    /// @param colPerUnitDebt_ The collateral amount per unit of debt shares
    /// @param to_ The address to receive withdrawn collateral (if address(0), defaults to msg.sender)
    /// @param absorb_ Whether to liquidate absorbed liquidity as well
    /// @return actualDebtShares_ The actual number of debt shares liquidated
    /// @return token0Debt_ The amount of debt in token0 that was paid back
    /// @return token1Debt_ The amount of debt in token1 that was paid back
    /// @return actualCol_ The actual amount of collateral withdrawn
    function liquidatePerfect(
        uint256 debtShares_,
        uint256 token0DebtAmtPerUnitShares_,
        uint256 token1DebtAmtPerUnitShares_,
        uint256 colPerUnitDebt_, // col per unit is w.r.t debt shares and not token0/1 debt amount
        address to_,
        bool absorb_
    )
        external
        payable
        _dexFromAddress
        returns (uint256 actualDebtShares_, uint256 token0Debt_, uint256 token1Debt_, uint256 actualCol_)
    {
        LiquidatePerfect memory lp_;
        lp_.vaultVariables = vaultVariables;
        // ############# turning re-entrancy bit on #############
        if (lp_.vaultVariables & 1 == 0) {
            // Updating on storage
            vaultVariables = lp_.vaultVariables | 1;
        } else {
            revert FluidVaultError(ErrorTypes.Vault__AlreadyEntered);
        }

        lp_.initialEth = address(this).balance - msg.value;

        to_ = to_ == address(0) ? msg.sender : to_;

        (actualDebtShares_, actualCol_, lp_.vaultVariables) = abi.decode(
            _liquidate(debtShares_, colPerUnitDebt_, to_, absorb_, lp_.vaultVariables),
            (uint, uint, uint)
        );

        (token0Debt_, token1Debt_) = _debtLiquidatePerfectPayback(
            actualDebtShares_,
            token0DebtAmtPerUnitShares_,
            token1DebtAmtPerUnitShares_
        );

        // disabling re-entrancy and updating on storage
        vaultVariables = lp_.vaultVariables;

        _validateEth(lp_.initialEth);
    }

    constructor(ConstantViews memory constants_) Internals(constants_) {
        // Note that vaults are deployed by VaultFactory so we somewhat trust the values being passed in
    }
}
tokenTransfers.sol 19 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { SafeTransfer } from "../../../../libraries/safeTransfer.sol";
import { ErrorTypes } from "../../errorTypes.sol";
import { Error } from "../../error.sol";

abstract contract TokenTransfers is Error {
    function _validateEth(uint initialEth_) internal {
        uint finalEth_ = payable(address(this)).balance;
        if (finalEth_ > initialEth_) {
            unchecked {
                SafeTransfer.safeTransferNative(msg.sender, finalEth_ - initialEth_); // sending back excess ETH
            }
        } else if (finalEth_ < initialEth_) {
            revert FluidVaultError(ErrorTypes.Vault__InvalidMsgValueOperate);
        }
    }
}
variables.sol 126 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

abstract contract Variables {
    /***********************************|
    |         Storage Variables         |
    |__________________________________*/

    /// note: in all variables. For tick >= 0 are represented with bit as 1, tick < 0 are represented with bit as 0
    /// note: read all the variables through storageRead.sol

    /// note: vaultVariables contains vault variables which need regular updates through transactions
    /// First 1 bit => 0 => re-entrancy. If 0 then allow transaction to go, else throw.
    /// Next 1 bit => 1 => Is the current active branch liquidated? If true then check the branch's minima tick before creating a new position
    /// If the new tick is greater than minima tick then initialize a new branch, make that as current branch & do proper linking
    /// Next 1 bit => 2 => sign of topmost tick (0 -> negative; 1 -> positive)
    /// Next 19 bits => 3-21 => absolute value of topmost tick
    /// Next 30 bits => 22-51 => current branch ID
    /// Next 30 bits => 52-81 => total branch ID
    /// Next 64 bits => 82-145 => Total supply
    /// Next 64 bits => 146-209 => Total borrow
    /// Next 32 bits => 210-241 => Total positions
    uint256 internal vaultVariables;

    /// note: vaultVariables2 contains variables which do not update on every transaction. So mainly admin/auth set amount
    /// First 16 bits => 0-15 => supply rate magnifier; 10000 = 1x (Here 16 bits should be more than enough)
    /// Next 16 bits => 16-31 => borrow rate magnifier; 10000 = 1x (Here 16 bits should be more than enough)
    /// Next 10 bits => 32-41 => collateral factor. 800 = 0.8 = 80% (max precision of 0.1%)
    /// Next 10 bits => 42-51 => liquidation Threshold. 900 = 0.9 = 90% (max precision of 0.1%)
    /// Next 10 bits => 52-61 => liquidation Max Limit. 950 = 0.95 = 95% (max precision of 0.1%) (above this 100% liquidation can happen)
    /// Next 10 bits => 62-71 => withdraw gap. 100 = 0.1 = 10%. (max precision of 0.1%) (max 7 bits can also suffice for the requirement here of 0.1% to 10%). Needed to save some limits on withdrawals so liquidate can work seamlessly.
    /// Next 10 bits => 72-81 => liquidation penalty. 100 = 0.01 = 1%. (max precision of 0.01%) (max liquidation penantly can be 10.23%). Applies when tick is in between liquidation Threshold & liquidation Max Limit.
    /// Next 10 bits => 82-91 => borrow fee. 100 = 0.01 = 1%. (max precision of 0.01%) (max borrow fee can be 10.23%). Fees on borrow.
    /// Next 30 bits => 92-121 => bits to calculate address of oracle
    /// Next 33 bits => 122-154 => last update timestamp
    uint256 internal vaultVariables2;

    /// note: stores absorbed liquidity
    /// First 128 bits raw debt amount
    /// last 128 bits raw col amount
    uint256 internal absorbedLiquidity;

    /// position index => position data uint
    /// if the entire variable is 0 (meaning not initialized) at the start that means no position at all
    /// First 1 bit => 0 => position type (0 => borrow position; 1 => supply position)
    /// Next 1 bit => 1 => sign of user's tick (0 => negative; 1 => positive)
    /// Next 19 bits => 2-20 => absolute value of user's tick
    /// Next 24 bits => 21-44 => user's tick's id
    /// Below we are storing user's collateral & not debt, because the position can also be only collateral with no tick but it can never be only debt
    /// Next 64 bits => 45-108 => user's supply amount. Debt will be calculated through supply & ratio.
    /// Next 64 bits => 109-172 => user's dust debt amount. User's net debt = total debt - dust amount. Total debt is calculated through supply & ratio
    /// User won't pay any extra interest on dust debt & hence we will not show it as a debt on UI. For user's there's no dust.
    mapping(uint256 => uint256) internal positionData;

    /// Tick has debt only keeps data of non liquidated positions. liquidated tick's data stays in branch itself
    /// tick parent => uint (represents bool for 256 children)
    /// parent of (i)th tick:-
    /// if (i>=0) (i / 256);
    /// else ((i + 1) / 256) - 1
    /// first bit of the variable is the smallest tick & last bit is the biggest tick of that slot
    mapping(int256 => uint256) internal tickHasDebt;

    /// mapping tickId => tickData
    /// Tick related data. Total debt & other things
    /// First bit => 0 => If 1 then liquidated else not liquidated
    /// Next 24 bits => 1-24 => Total IDs. ID should start from 1.
    /// If not liquidated:
    /// Next 64 bits => 25-88 => raw debt
    /// If liquidated
    /// The below 3 things are of last ID. This is to be updated when user creates a new position
    /// Next 1 bit => 25 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated (100% liquidated)
    /// Next 30 bits => 26-55 => branch ID where this tick got liquidated
    /// Next 50 bits => 56-105 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
    mapping(int256 => uint256) internal tickData;

    /// tick id => previous tick id liquidation data. ID starts from 1
    /// One tick ID contains 3 IDs of 80 bits in it, holding liquidation data of previously active but liquidated ticks
    /// 81 bits data below
    /// #### First 85 bits ####
    /// 1st bit => 0 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
    /// Next 30 bits => 1-30 => branch ID where this tick got liquidated
    /// Next 50 bits => 31-80 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
    /// #### Second 85 bits ####
    /// 85th bit => 85 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
    /// Next 30 bits => 86-115 => branch ID where this tick got liquidated
    /// Next 50 bits => 116-165 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
    /// #### Third 85 bits ####
    /// 170th bit => 170 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
    /// Next 30 bits => 171-200 => branch ID where this tick got liquidated
    /// Next 50 bits => 201-250 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
    mapping(int256 => mapping(uint256 => uint256)) internal tickId;

    /// mapping branchId => branchData
    /// First 2 bits => 0-1 => if 0 then not liquidated, if 1 then liquidated, if 2 then merged, if 3 then closed
    /// merged means the branch is merged into it's base branch
    /// closed means all the users are 100% liquidated
    /// Next 1 bit => 2 => minima tick sign of this branch. Will only be there if any liquidation happened.
    /// Next 19 bits => 3-21 => minima tick of this branch. Will only be there if any liquidation happened.
    /// Next 30 bits => 22-51 => Partials of minima tick of branch this is connected to. 0 if master branch.
    /// Next 64 bits => 52-115 Debt liquidity at this branch. Similar to last's top tick data. Remaining debt will move here from tickData after first liquidation
    /// If not merged
    /// Next 50 bits => 116-165 => Debt factor or of this branch. (35 bits coefficient | 15 bits expansion)
    /// If merged
    /// Next 50 bits => 116-165 => Connection/adjustment debt factor of this branch with the next branch.
    /// If closed
    /// Next 50 bits => 116-165 => Debt factor as 0. As all the user's positions are now fully gone
    /// following values are present always again (merged / not merged / closed)
    /// Next 30 bits => 166-195 => Branch's ID with which this branch is connected. If 0 then that means this is the master branch
    /// Next 1 bit => 196 => sign of minima tick of branch this is connected to. 0 if master branch.
    /// Next 19 bits => 197-215 => minima tick of branch this is connected to. 0 if master branch.
    mapping(uint256 => uint256) internal branchData;

    /// Exchange prices are in 1e12
    /// First 64 bits => 0-63 => Liquidity's collateral token supply exchange price
    /// First 64 bits => 64-127 => Liquidity's debt token borrow exchange price
    /// First 64 bits => 128-191 => Vault's collateral token supply exchange price
    /// First 64 bits => 192-255 => Vault's debt token borrow exchange price
    uint256 internal rates;

    /// address of rebalancer
    address internal rebalancer;

    uint256 internal absorbedDustDebt;

    address internal dexFromAddress;
}
constantVariables.sol 155 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { IFluidVaultFactory } from "../../interfaces/iVaultFactory.sol";
import { IFluidLiquidity } from "../../../../liquidity/interfaces/iLiquidity.sol";
import { StorageRead } from "../../../../libraries/storageRead.sol";
import { ILiquidityDexCommon } from "../../interfaces/iLiquidityDexCommon.sol";
import { Structs } from "./structs.sol";
import { Error } from "../../error.sol";
import { ErrorTypes } from "../../errorTypes.sol";
import { FluidProtocolTypes } from "../../../../libraries/fluidProtocolTypes.sol";

interface TokenInterface {
    function decimals() external view returns (uint8);
}

abstract contract ConstantVariables is StorageRead, Structs, Error {
    /***********************************|
    |        Constant Variables         |
    |__________________________________*/

    address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address internal constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
    /// @dev collateral token address
    address internal immutable SUPPLY_TOKEN;
    /// @dev borrow token address
    address internal immutable BORROW_TOKEN;

    /// @dev contract via which we deploy oracle contract
    address internal immutable DEPLOYER_CONTRACT;

    ILiquidityDexCommon internal immutable SUPPLY;
    ILiquidityDexCommon internal immutable BORROW;

    /// @dev if smart collateral then token0 is dex token0 address else it's normal collateral token0 address
    address internal immutable SUPPLY_TOKEN0;
    /// @dev if smart collateral then token1 is dex token1 address else it's address(0)
    address internal immutable SUPPLY_TOKEN1;

    /// @dev if smart debt then token0 is dex token0 address else it's normal borrow token0 address
    address internal immutable BORROW_TOKEN0;
    /// @dev if smart debt then token1 is dex token1 address else it's address(0)
    address internal immutable BORROW_TOKEN1;

    /// @dev Vault OperateModule implemenation address
    address internal immutable OPERATE_IMPLEMENTATION;

    /// @dev Vault AdminModule implemenation address
    address internal immutable ADMIN_IMPLEMENTATION;

    /// @dev Vault Secondary implemenation (main2.sol) address
    address internal immutable SECONDARY_IMPLEMENTATION;

    /// @dev liquidity proxy contract address
    IFluidLiquidity public immutable LIQUIDITY;

    /// @dev vault factory contract address
    IFluidVaultFactory public immutable VAULT_FACTORY;

    uint public immutable VAULT_ID;

    uint public immutable TYPE;

    uint internal constant X8 = 0xff;
    uint internal constant X10 = 0x3ff;
    uint internal constant X15 = 0x7fff;
    uint internal constant X16 = 0xffff;
    uint internal constant X19 = 0x7ffff;
    uint internal constant X20 = 0xfffff;
    uint internal constant X24 = 0xffffff;
    uint internal constant X25 = 0x1ffffff;
    uint internal constant X30 = 0x3fffffff;
    uint internal constant X33 = 0x1ffffffff;
    uint internal constant X35 = 0x7ffffffff;
    uint internal constant X50 = 0x3ffffffffffff;
    uint internal constant X64 = 0xffffffffffffffff;
    uint internal constant X96 = 0xffffffffffffffffffffffff;
    uint internal constant X128 = 0xffffffffffffffffffffffffffffffff;

    uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;

    /// @dev slot ids in Liquidity contract. Helps in low gas fetch from liquidity contract by skipping delegate call
    bytes32 internal immutable SUPPLY_EXCHANGE_PRICE_SLOT; // Can be of DEX or liquidity layer
    bytes32 internal immutable BORROW_EXCHANGE_PRICE_SLOT; // Can be of DEX or liquidity layer
    bytes32 internal immutable USER_SUPPLY_SLOT; // Can be of DEX or liquidity layer
    bytes32 internal immutable USER_BORROW_SLOT; // Can be of DEX or liquidity layer

    constructor(ConstantViews memory constants_) {
        TYPE = constants_.vaultType;

        if (
            TYPE != FluidProtocolTypes.VAULT_T1_TYPE &&
            TYPE != FluidProtocolTypes.VAULT_T2_SMART_COL_TYPE &&
            TYPE != FluidProtocolTypes.VAULT_T3_SMART_DEBT_TYPE &&
            TYPE != FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE
        ) {
            revert FluidVaultError(ErrorTypes.Vault__ImproperConstantsSetup);
        }

        LIQUIDITY = IFluidLiquidity(constants_.liquidity);
        VAULT_FACTORY = IFluidVaultFactory(constants_.factory);
        DEPLOYER_CONTRACT = constants_.deployer;
        SUPPLY = ILiquidityDexCommon(constants_.supply);
        BORROW = ILiquidityDexCommon(constants_.borrow);
        VAULT_ID = constants_.vaultId;

        OPERATE_IMPLEMENTATION = constants_.operateImplementation == address(0)
            ? address(this)
            : constants_.operateImplementation;

        // if smart collateral then adding dex address (even though it's not a token) else adding token address
        if (
            TYPE == FluidProtocolTypes.VAULT_T2_SMART_COL_TYPE ||
            TYPE == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE
        ) {
            SUPPLY_TOKEN = constants_.supply;
        } else {
            SUPPLY_TOKEN = constants_.supplyToken.token0;
            if (constants_.supply != constants_.liquidity) {
                revert FluidVaultError(ErrorTypes.Vault__ImproperConstantsSetup);
            }
        }

        // if smart debt then adding dex address (even though it's not a token) else adding token address
        if (
            TYPE == FluidProtocolTypes.VAULT_T3_SMART_DEBT_TYPE ||
            TYPE == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE
        ) {
            BORROW_TOKEN = constants_.borrow;
        } else {
            BORROW_TOKEN = constants_.borrowToken.token0;
            if (constants_.borrow != constants_.liquidity) {
                revert FluidVaultError(ErrorTypes.Vault__ImproperConstantsSetup);
            }
        }

        SUPPLY_TOKEN0 = constants_.supplyToken.token0;
        BORROW_TOKEN0 = constants_.borrowToken.token0;
        SUPPLY_TOKEN1 = constants_.supplyToken.token1;
        BORROW_TOKEN1 = constants_.borrowToken.token1;

        // below slots are calculated in the deploymentLogics / VaultFactory
        // if supply is directly on liquidity layer then liquidity layer storage slot else if supply is via DEX then bytes32(0)
        SUPPLY_EXCHANGE_PRICE_SLOT = constants_.supplyExchangePriceSlot;
        // if borrow is directly on liquidity layer then liquidity layer storage slot else if borrow is via DEX then bytes32(0)
        BORROW_EXCHANGE_PRICE_SLOT = constants_.borrowExchangePriceSlot;
        // if supply is directly on liquidity layer then liquidity layer storage slot else if supply is via DEX then dex storage slot
        USER_SUPPLY_SLOT = constants_.userSupplySlot;
        // if borrow is directly on liquidity layer then liquidity layer storage slot else if borrow is via DEX then dex storage slot
        USER_BORROW_SLOT = constants_.userBorrowSlot;

        ADMIN_IMPLEMENTATION = constants_.adminImplementation;
        SECONDARY_IMPLEMENTATION = constants_.secondaryImplementation;
    }
}
events.sol 25 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

abstract contract Events {
    /// @notice emitted when an operate() method is executed that changes collateral (`colAmt_`) / debt (debtAmt_`)
    /// amount for a `user_` position with `nftId_`. Receiver of any funds is the address `to_`.
    event LogOperate(address user_, uint256 nftId_, int256 colAmt_, int256 debtAmt_, address to_);

    /// @notice emitted when the exchange prices are updated in storage.
    event LogUpdateExchangePrice(uint256 supplyExPrice_, uint256 borrowExPrice_);

    /// @notice emitted when a liquidation has been executed.
    event LogLiquidate(address liquidator_, uint256 colAmt_, uint256 debtAmt_, address to_);

    /// @notice emitted when `absorb()` was executed to absorb bad debt.
    event LogAbsorb(uint colAbsorbedRaw_, uint debtAbsorbedRaw_);

    /// @notice emitted when a `rebalance()` has been executed, balancing out total supply / borrow between Vault
    /// and Fluid Liquidity pools.
    /// if `colAmt_` is positive then loss, meaning transfer from rebalancer address to vault and deposit.
    /// if `colAmt_` is negative then profit, meaning withdrawn from vault and sent to rebalancer address.
    /// if `debtAmt_` is positive then profit, meaning borrow from vault and sent to rebalancer address.
    /// if `debtAmt_` is negative then loss, meaning transfer from rebalancer address to vault and payback.
    event LogRebalance(int colAmt_, int debtAmt_);
}
helpers.sol 293 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { Variables } from "../common/variables.sol";
import { TokenTransfers } from "../common/tokenTransfers.sol";
import { ConstantVariables } from "./constantVariables.sol";
import { Events } from "./events.sol";
import { TickMath } from "../../../../libraries/tickMath.sol";
import { BigMathMinified } from "../../../../libraries/bigMathMinified.sol";
import { BigMathVault } from "../../../../libraries/bigMathVault.sol";
import { LiquidityCalcs } from "../../../../libraries/liquidityCalcs.sol";
import { ErrorTypes } from "../../errorTypes.sol";
import { FluidProtocolTypes } from "../../../../libraries/fluidProtocolTypes.sol";

/// @dev Fluid vault protocol helper methods. Mostly used for `operate()` and `liquidate()` methods of CoreModule.
abstract contract Helpers is Variables, ConstantVariables, Events, TokenTransfers {
    using BigMathMinified for uint256;
    using BigMathVault for uint256;

    modifier _dexFromAddress() {
        if (dexFromAddress != DEAD_ADDRESS) revert FluidVaultError(ErrorTypes.Vault__DexFromAddressAlreadySet);
        dexFromAddress = msg.sender;
        _;
        dexFromAddress = DEAD_ADDRESS;
    }

    /// @notice Calculates new vault exchange prices. Does not update values in storage.
    /// @param vaultVariables2_ exactly same as vaultVariables2 from storage
    /// @return liqSupplyExPrice_ latest liquidity's supply token supply exchange price
    /// @return liqBorrowExPrice_ latest liquidity's borrow token borrow exchange price
    /// @return vaultSupplyExPrice_ latest vault's supply token exchange price
    /// @return vaultBorrowExPrice_ latest vault's borrow token exchange price
    function updateExchangePrices(
        uint256 vaultVariables2_
    )
        public
        view
        returns (
            uint256 liqSupplyExPrice_,
            uint256 liqBorrowExPrice_,
            uint256 vaultSupplyExPrice_,
            uint256 vaultBorrowExPrice_
        )
    {
        // Fetching last stored rates
        uint rates_ = rates;

        // in case of smart collateral oldLiqSupplyExPrice_ will be 0
        uint256 oldLiqSupplyExPrice_ = (rates_ & X64);
        // in case of smart debt oldLiqBorrowExPrice_ will be 0
        uint256 oldLiqBorrowExPrice_ = ((rates_ >> 64) & X64);

        uint timeStampDiff_ = block.timestamp - ((vaultVariables2_ >> 122) & X33);

        if (
            TYPE == FluidProtocolTypes.VAULT_T2_SMART_COL_TYPE ||
            TYPE == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE
        ) {
            liqSupplyExPrice_ = EXCHANGE_PRICES_PRECISION;
            // in case of smart collateral supply magnifier bits stores, supply interest rate positive or negative
            // negative meaning charging users, positive means incentivizing users
            vaultSupplyExPrice_ = ((rates_ >> 128) & X64);
            // if 1 then positive else negative
            if ((vaultVariables2_ & 1) == 1) {
                vaultSupplyExPrice_ =
                    vaultSupplyExPrice_ +
                    (vaultSupplyExPrice_ * timeStampDiff_ * ((vaultVariables2_ >> 1) & X15)) /
                    (10000 * LiquidityCalcs.SECONDS_PER_YEAR);
            } else {
                vaultSupplyExPrice_ =
                    vaultSupplyExPrice_ -
                    (vaultSupplyExPrice_ * timeStampDiff_ * ((vaultVariables2_ >> 1) & X15)) /
                    (10000 * LiquidityCalcs.SECONDS_PER_YEAR);
            }
        } else {
            (liqSupplyExPrice_, ) = LiquidityCalcs.calcExchangePrices(
                LIQUIDITY.readFromStorage(SUPPLY_EXCHANGE_PRICE_SLOT)
            );
            if (liqSupplyExPrice_ < oldLiqSupplyExPrice_) {
                // new liquidity exchange price is < than the old one. liquidity exchange price should only ever increase.
                // If not, something went wrong and avoid proceeding with unknown outcome.
                revert FluidVaultError(ErrorTypes.Vault__LiquidityExchangePriceUnexpected);
            }

            // liquidity Exchange Prices always increases in next block. Hence substraction with old will never be negative
            // uint64 * 1e18 is the max the number that could be
            unchecked {
                // Calculating increase in supply exchange price w.r.t last stored liquidity's exchange price
                // vaultSupplyExPrice_ => supplyIncreaseInPercent_
                vaultSupplyExPrice_ =
                    ((((liqSupplyExPrice_ * 1e18) / oldLiqSupplyExPrice_) - 1e18) * (vaultVariables2_ & X16)) /
                    10000; // supply rate magnifier

                // It's extremely hard the exchange prices to overflow even in 100 years but if it does it's not an
                // issue here as we are not updating on storage
                // (rates_ >> 128) & X64) -> last stored vault's supply token exchange price
                vaultSupplyExPrice_ = (((rates_ >> 128) & X64) * (1e18 + vaultSupplyExPrice_)) / 1e18;                
            }
        }

        if (
            TYPE == FluidProtocolTypes.VAULT_T3_SMART_DEBT_TYPE ||
            TYPE == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE
        ) {
            liqBorrowExPrice_ = EXCHANGE_PRICES_PRECISION;
            // in case of smart debt borrow magnifier bits stores, borrow interest rate positive or negative
            // negative meaning incentivizing users, positive means charging users
            vaultBorrowExPrice_ = ((rates_ >> 192) & X64);
            // if 1 then positive else negative
            if (((vaultVariables2_ >> 16) & 1) == 1) {
                vaultBorrowExPrice_ =
                    vaultBorrowExPrice_ +
                    (vaultBorrowExPrice_ * timeStampDiff_ * (((vaultVariables2_ >> 17) & X15))) /
                    (10000 * LiquidityCalcs.SECONDS_PER_YEAR);
            } else {
                vaultBorrowExPrice_ =
                    vaultBorrowExPrice_ -
                    (vaultBorrowExPrice_ * timeStampDiff_ * (((vaultVariables2_ >> 17) & X15))) /
                    (10000 * LiquidityCalcs.SECONDS_PER_YEAR);
            }
        } else {
            (, liqBorrowExPrice_) = LiquidityCalcs.calcExchangePrices(
                LIQUIDITY.readFromStorage(BORROW_EXCHANGE_PRICE_SLOT)
            );
            if (liqBorrowExPrice_ < oldLiqBorrowExPrice_) {
                // new liquidity exchange price is < than the old one. liquidity exchange price should only ever increase.
                // If not, something went wrong and avoid proceeding with unknown outcome.
                revert FluidVaultError(ErrorTypes.Vault__LiquidityExchangePriceUnexpected);
            }
            // liquidity Exchange Prices always increases in next block. Hence substraction with old will never be negative
            // uint64 * 1e18 is the max the number that could be
            unchecked {
                // Calculating increase in borrow exchange price w.r.t last stored liquidity's exchange price
                // vaultBorrowExPrice_ => borrowIncreaseInPercent_
                vaultBorrowExPrice_ =
                    ((((liqBorrowExPrice_ * 1e18) / oldLiqBorrowExPrice_) - 1e18) * ((vaultVariables2_ >> 16) & X16)) /
                    10000; // borrow rate magnifier

                // It's extremely hard the exchange prices to overflow even in 100 years but if it does it's not an
                // issue here as we are not updating on storage
                // (rates_ >> 192) -> last stored vault's borrow token exchange price (no need to mask with & X64 as it is anyway max 64 bits)
                vaultBorrowExPrice_ = ((rates_ >> 192) * (1e18 + vaultBorrowExPrice_)) / 1e18;
            }
        }
    }

    /// @dev fetches new user's position after liquidation. The new liquidated position's debt is decreased by 0.01%
    /// to make sure that branch's liquidity never becomes 0 as if it would have gotten 0 then there will be multiple cases that we would need to tackle.
    /// @param positionTick_ position's tick when it was last updated through operate
    /// @param positionTickId_ position's tick Id. This stores the debt factor and branch to make the first connection
    /// @param positionRawDebt_ position's raw debt when it was last updated through operate
    /// @param tickData_ position's tick's tickData just for minor comparison to know if data is moved to tick Id or is still in tick data
    /// @return final tick position after all the liquidation
    /// @return final debt of position after all the liquidation
    /// @return positionRawCol_ final collateral of position after all the liquidation
    /// @return branchId_ final branch's ID where the position is at currently
    /// @return branchData_ final branch's data where the position is at currently
    function fetchLatestPosition(
        int256 positionTick_,
        uint256 positionTickId_,
        uint256 positionRawDebt_,
        uint256 tickData_
    )
        public
        view
        returns (
            int256, // positionTick_
            uint256, // positionRawDebt_
            uint256 positionRawCol_,
            uint256 branchId_,
            uint256 branchData_
        )
    {
        uint256 initialPositionRawDebt_ = positionRawDebt_;
        uint256 connectionFactor_;
        bool isFullyLiquidated_;

        // Checking if tick's total ID = user's tick ID
        if (((tickData_ >> 1) & X24) == positionTickId_) {
            // fetching from tick data itself
            isFullyLiquidated_ = ((tickData_ >> 25) & 1) == 1;
            branchId_ = (tickData_ >> 26) & X30;
            connectionFactor_ = (tickData_ >> 56) & X50;
        } else {
            {
                uint256 tickLiquidationData_;
                unchecked {
                    // Fetching tick's liquidation data. One variable contains data of 3 IDs. Tick Id mapping is starting from 1.
                    tickLiquidationData_ =
                        tickId[positionTick_][(positionTickId_ + 2) / 3] >>
                        (((positionTickId_ + 2) % 3) * 85);
                }

                isFullyLiquidated_ = (tickLiquidationData_ & 1) == 1;
                branchId_ = (tickLiquidationData_ >> 1) & X30;
                connectionFactor_ = (tickLiquidationData_ >> 31) & X50;
            }
        }

        // data of branch
        branchData_ = branchData[branchId_];

        if (isFullyLiquidated_) {
            positionTick_ = type(int).min;
            positionRawDebt_ = 0;
        } else {
            // Below information about connection debt factor
            // If branch is merged, Connection debt factor is used to multiply in order to get perfect liquidation of user
            // For example: Considering user was at the top.
            // In first branch, the user liquidated to debt factor 0.5 and then branch got merged (branching starting from 1)
            // In second branch, it got liquidated to 0.4 but when the above branch merged the debt factor on this branch was 0.6
            // Meaning on 1st branch, user got liquidated by 50% & on 2nd by 33.33%. So a total of 66.6%.
            // What we will set a connection factor will be 0.6/0.5 = 1.2
            // So now to get user's position, this is what we'll do:
            // finalDebt = (0.4 / (1 * 1.2)) * debtBeforeLiquidation
            // 0.4 is current active branch's minima debt factor
            // 1 is debt factor from where user started
            // 1.2 is connection factor which we found out through 0.6 / 0.5
            while ((branchData_ & 3) == 2) {
                // If true then the branch is merged

                // userTickDebtFactor * connectionDebtFactor *... connectionDebtFactor aka adjustmentDebtFactor
                connectionFactor_ = connectionFactor_.mulBigNumber(((branchData_ >> 116) & X50));
                if (connectionFactor_ == BigMathVault.MAX_MASK_DEBT_FACTOR) break; // user ~100% liquidated
                // Note we don't need updated branch data in case of 100% liquidated so saving gas for fetching it

                // Fetching new branch data
                branchId_ = (branchData_ >> 166) & X30; // Link to base branch of current branch
                branchData_ = branchData[branchId_];
            }
            // When the while loop breaks meaning the branch now has minima Debt Factor or is a closed branch;

            if (((branchData_ & 3) == 3) || (connectionFactor_ == BigMathVault.MAX_MASK_DEBT_FACTOR)) {
                // Branch got closed (or user liquidated ~100%). Hence make the user's position 0
                // Rare cases to get into this situation
                // Branch can get close often but once closed it's tricky that some user might come iterating through there
                // If a user comes then that user will be very mini user like some cents probably
                positionTick_ = type(int).min;
                positionRawDebt_ = 0;
            } else {
                // If branch is not merged, the main branch it's connected to then it'll have minima debt factor

                // position debt = debt * base branch minimaDebtFactor / connectionFactor
                positionRawDebt_ = positionRawDebt_.mulDivNormal(
                    (branchData_ >> 116) & X50, // minimaDebtFactor
                    connectionFactor_
                );

                unchecked {
                    // Reducing user's liquidity by 0.01% if user got liquidated.
                    // As this will make sure that the branch always have some debt even if all liquidated user left
                    // This saves a lot more logics & consideration on Operate function
                    // if we don't do this then we have to add logics related to closing the branch and factor connections accordingly.
                    if (positionRawDebt_ > (initialPositionRawDebt_ / 100)) {
                        positionRawDebt_ = (positionRawDebt_ * 9999) / 10000;
                    } else {
                        // if user debt reduced by more than 99% in liquidation then making user as fully liquidated
                        positionRawDebt_ = 0;
                    }
                }

                {
                    if (positionRawDebt_ > 0) {
                        // positionTick_ -> read minima tick of branch
                        unchecked {
                            positionTick_ = branchData_ & 4 == 4
                                ? int((branchData_ >> 3) & X19)
                                : -int((branchData_ >> 3) & X19);
                        }
                        // Calculating user's collateral
                        uint256 ratioAtTick_ = TickMath.getRatioAtTick(int24(positionTick_));
                        uint256 ratioOneLess_;
                        unchecked {
                            ratioOneLess_ = (ratioAtTick_ * 10000) / 10015;
                        }
                        // formula below for better readability:
                        // length = ratioAtTick_ - ratioOneLess_
                        // ratio = ratioOneLess_ + (length * positionPartials_) / X30
                        // positionRawCol_ = (positionRawDebt_ * (1 << 96)) / ratio_
                        positionRawCol_ =
                            (positionRawDebt_ * TickMath.ZERO_TICK_SCALED_RATIO) /
                            (ratioOneLess_ + ((ratioAtTick_ - ratioOneLess_) * ((branchData_ >> 22) & X30)) / X30);
                    } else {
                        positionTick_ = type(int).min;
                    }
                }
            }
        }
        return (positionTick_, positionRawDebt_, positionRawCol_, branchId_, branchData_);
    }

    constructor(ConstantViews memory constants_) ConstantVariables(constants_) {}
}
helpersLiquidate.sol 45 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { Helpers } from "./helpers.sol";
import { ErrorTypes } from "../../errorTypes.sol";

/// @dev Fluid vault protocol helper methods. Mostly used for `operate()` and `liquidate()` methods of CoreModule.
abstract contract HelpersLiquidate is Helpers {
    /// note admin module is also calling this function self call
    /// @dev updating exchange price on storage. Only need to update on storage when changing supply or borrow magnifier
    function updateExchangePricesOnStorage()
        public
        returns (
            uint256 liqSupplyExPrice_,
            uint256 liqBorrowExPrice_,
            uint256 vaultSupplyExPrice_,
            uint256 vaultBorrowExPrice_
        )
    {
        (liqSupplyExPrice_, liqBorrowExPrice_, vaultSupplyExPrice_, vaultBorrowExPrice_) = updateExchangePrices(
            vaultVariables2
        );

        if (
            liqSupplyExPrice_ > X64 || liqBorrowExPrice_ > X64 || vaultSupplyExPrice_ > X64 || vaultBorrowExPrice_ > X64
        ) {
            revert FluidVaultError(ErrorTypes.Vault__ExchangePriceOverFlow);
        }

        // Updating in storage
        rates =
            liqSupplyExPrice_ |
            (liqBorrowExPrice_ << 64) |
            (vaultSupplyExPrice_ << 128) |
            (vaultBorrowExPrice_ << 192);

        vaultVariables2 =
            (vaultVariables2 & 0xfffffffffffffffffffffffff800000003ffffffffffffffffffffffffffffff) |
            (block.timestamp << 122);

        emit LogUpdateExchangePrice(vaultSupplyExPrice_, vaultBorrowExPrice_);
    }

    constructor(ConstantViews memory constants_) Helpers(constants_) {}
}
main.sol 835 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

import { IFluidOracle } from "../../../../oracle/fluidOracle.sol";
import { TickMath } from "../../../../libraries/tickMath.sol";
import { BigMathMinified } from "../../../../libraries/bigMathMinified.sol";
import { BigMathVault } from "../../../../libraries/bigMathVault.sol";
import { SafeTransfer } from "../../../../libraries/safeTransfer.sol";
import { HelpersLiquidate } from "./helpersLiquidate.sol";
import { LiquiditySlotsLink } from "../../../../libraries/liquiditySlotsLink.sol";
import { FluidProtocolTypes } from "../../../../libraries/fluidProtocolTypes.sol";
import { ErrorTypes } from "../../errorTypes.sol";
import { AddressCalcs } from "../../../../libraries/addressCalcs.sol";

/// @notice Fluid vault protocol main contract base.
///         Fluid Vault protocol is a borrow / lending protocol, allowing users to create collateral / borrow positions.
///         All funds are deposited into / borrowed from Fluid Liquidity layer.
///         Positions are represented through NFTs minted by the VaultFactory.
///         Deployed by "VaultFactory" and linked together with Vault AdminModule `ADMIN_IMPLEMENTATION` and
///         FluidVaultSecondary (main2.sol) `SECONDARY_IMPLEMENTATION`.
///         AdminModule & FluidVaultSecondary methods are delegateCalled, if the msg.sender has the required authorization.
///         This contract links to an Oracle, which is used to assess collateral / debt value. Oracles implement the
///         "FluidOracle" base contract and return the price in 1e27 precision.
/// @dev    For view methods / accessing data, use the "VaultResolver" periphery contract.
//
// vaults can only be deployed for tokens that are listed at Liquidity (constructor reverts otherwise
// if either the exchange price for the supply token or the borrow token is still not set at Liquidity).
abstract contract FluidVault is HelpersLiquidate {
    using BigMathMinified for uint256;
    using BigMathVault for uint256;

    function simulateLiquidate(uint debtAmt_, bool absorb_) external {
        uint vaultVariables_ = vaultVariables;
        // ############# turning re-entrancy bit on #############
        if (vaultVariables_ & 1 == 0) {
            // Updating on storage
            vaultVariables = vaultVariables_ | 1;
        } else {
            revert FluidVaultError(ErrorTypes.Vault__AlreadyEntered);
        }

        debtAmt_ = debtAmt_ == 0 ? X128 : debtAmt_;

        _liquidate(X128, 0, DEAD_ADDRESS, absorb_, vaultVariables_);

        // this revert will never reach as the revert is inside the liquidate function due to to_ = DEAD_ADDRESS
        // but still added just to be extra safe
        revert();
    }

    /// @dev allows to liquidate all bad debt of all users at once. Liquidator can also liquidate partially any amount they want.
    /// @param debtAmt_ total debt to liquidate (aka debt token to swap into collateral token)
    /// @param colPerUnitDebt_ minimum collateral token per unit of debt in 1e18 decimals
    /// @param to_ address at which collateral token should go to.
    ///            If dead address (DEAD_ADDRESS) then reverts with custom error "FluidLiquidateResult"
    ///            returning the actual collateral and actual debt liquidated. Useful to find max liquidatable amounts via try / catch.
    /// @param absorb_ if true then liquidate from absorbed first
    /// @param vaultVariables_ the current state of the vaultVariables from storage
    /// @return bytes with 3 uints, r_[0] = actualDebtAmt, r_[1] = actualColAmt, r_[2] = vaultVariables_
    ///         actualDebtAmt if liquidator sends debtAmt_ more than debt remaining to liquidate then actualDebtAmt changes from debtAmt_ else remains same
    ///         actualColAmt total liquidated collateral which liquidator will get
    function _liquidate(
        uint256 debtAmt_,
        uint256 colPerUnitDebt_, // min collateral needed per unit of debt in 1e18
        address to_,
        bool absorb_,
        uint vaultVariables_
    ) internal returns (bytes memory) {
        LiquidateMemoryVars memory memoryVars_;

        memoryVars_.vaultVariables2 = vaultVariables2;

        if (((vaultVariables_ >> 2) & X20) == 0) {
            revert FluidVaultError(ErrorTypes.Vault__TopTickDoesNotExist);
        }

        // Below are exchange prices of vaults
        (, , memoryVars_.supplyExPrice, memoryVars_.borrowExPrice) = updateExchangePrices(memoryVars_.vaultVariables2);

        CurrentLiquidity memory currentData_;
        BranchData memory branch_;
        // Temporary holder variables, used many times for different small things
        uint temp_;
        uint temp2_;

        {
            // ############# Oracle related stuff #############
            // Col price w.r.t debt. For example: 1 ETH = 1000 DAI
            // temp_ -> debtPerCol
            temp_ = IFluidOracle(
                AddressCalcs.addressCalc(DEPLOYER_CONTRACT, ((memoryVars_.vaultVariables2 >> 92) & X30))
            ).getExchangeRateLiquidate(); // Price in 27 decimals

            // not reverting if oracle price is lower than 1e9 as it can pause potential liquidation in this edge case situations
            if (temp_ > 1e54 || temp_ == 0) {
                revert FluidVaultError(ErrorTypes.Vault__InvalidOraclePrice);
            }

            unchecked {
                // temp_ -> debtPerCol Converting in terms of raw amount
                temp_ = (temp_ * memoryVars_.supplyExPrice) / memoryVars_.borrowExPrice;

                // capping oracle pricing to 1e45
                // Reason mentioned at (search: #487RGF783GF)
                if (temp_ > 1e45) {
                    temp_ = 1e45;
                }
                // temp2_ -> Raw colPerDebt_ in 27 decimals
                temp2_ = 1e54 / temp_;

                // temp2_ can never be > 1e54
                // Oracle price should never be > 1e54
                // Liquidation penalty in 4 decimals (1e2 = 1%) (max: 10.23%) -> (vaultVariables2_ >> 72) & X10
                currentData_.colPerDebt = (temp2_ * (10000 + ((memoryVars_.vaultVariables2 >> 72) & X10))) / 10000;

                // get liquidiation tick (tick at liquidation threshold ratio)
                // Liquidation threshold in 3 decimals (900 = 90%) -> (vaultVariables2_ >> 42) & X10
                // Dividing by 1e27 to convert temp_ into normal number
                temp_ = ((temp_ * TickMath.ZERO_TICK_SCALED_RATIO) / 1e27);
                // temp2_ -> liquidationRatio_
                temp2_ = (temp_ * ((memoryVars_.vaultVariables2 >> 42) & X10)) / 1000;
            }
            (memoryVars_.liquidationTick, ) = TickMath.getTickAtRatio(temp2_);

            // get liquidiation max limit tick (tick at liquidation max limit ratio)
            // Max limit in 3 decimals (900 = 90%) -> (vaultVariables2_ >> 52) & X10
            // temp2_ -> maxRatio_
            unchecked {
                temp2_ = (temp_ * ((memoryVars_.vaultVariables2 >> 52) & X10)) / 1000;
            }
            (memoryVars_.maxTick, ) = TickMath.getTickAtRatio(temp2_);
        }

        // extracting top tick as top tick will be the current tick
        unchecked {
            currentData_.tick = (vaultVariables_ & 4) == 4
                ? int256((vaultVariables_ >> 3) & X19)
                : -int256((vaultVariables_ >> 3) & X19);
        }

        if (currentData_.tick > memoryVars_.maxTick) {
            // absorbing all the debt above maxTick if available
            vaultVariables_ = (
                abi.decode(
                    _spell(
                        SECONDARY_IMPLEMENTATION,
                        abi.encodeWithSignature("absorb(uint256,int256)", vaultVariables_, memoryVars_.maxTick)
                    ),
                    (uint256)
                )
            );

            // updating current tick to new topTick after absorb
            unchecked {
                currentData_.tick = (vaultVariables_ & 4) == 4
                    ? int256((vaultVariables_ >> 3) & X19)
                    : -int256((vaultVariables_ >> 3) & X19);
            }
            if (debtAmt_ == 0) {
                // updating vault variables on storage as the transaction was for only absorb
                // Vault variables is getting updated through liquidate function
                return abi.encode(0, 0, vaultVariables_);
            }
        }

        if (debtAmt_ < 10000 || debtAmt_ > X128) {
            revert FluidVaultError(ErrorTypes.Vault__InvalidLiquidationAmt);
        }

        // setting up status if top tick is liquidated or not
        currentData_.tickStatus = vaultVariables_ & 2 == 0 ? 1 : 2;
        // Tick info is mainly used as a place holder to store temporary tick related data
        // (it can be current or ref using same memory variable)
        TickData memory tickInfo_;
        tickInfo_.tick = currentData_.tick;

        {
            // ############# Setting current branch in memory #############

            // Updating branch related data
            branch_.id = (vaultVariables_ >> 22) & X30;
            branch_.data = branchData[branch_.id];
            branch_.debtFactor = (branch_.data >> 116) & X50;
            if (branch_.debtFactor == 0) {
                // Initializing branch debt factor. 35 | 15 bit number. Where full 35 bits and 15th bit is occupied.
                // Making the total number as (2**35 - 1) << 2**14.
                // note: initial debt factor can be any number.
                branch_.debtFactor = ((X35 << 15) | (1 << 14));
            }
            // fetching base branch's minima tick. if 0 that means it's a master branch
            temp_ = (branch_.data >> 196) & X20;
            if (temp_ > 0) {
                unchecked {
                    branch_.minimaTick = (temp_ & 1) == 1 ? int256((temp_ >> 1) & X19) : -int256((temp_ >> 1) & X19);
                }
            } else {
                branch_.minimaTick = type(int).min;
            }
        }

        // debtAmt_ should be less than 2**128 & EXCHANGE_PRICES_PRECISION is 1e12
        unchecked {
            currentData_.debtRemaining = (debtAmt_ * EXCHANGE_PRICES_PRECISION) / memoryVars_.borrowExPrice;
        }

        // extracting total debt
        temp2_ = (vaultVariables_ >> 146) & X64;
        temp2_ = ((temp2_ >> 8) << (temp2_ & X8));

        if ((temp2_ / 1e9) > currentData_.debtRemaining) {
            // if liquidation amount is less than 1e9 of total debt then revert
            // so if total debt is $1B then minimum liquidation limit = $1
            // so if total debt is $1T then minimum liquidation limit = $1000
            // partials precision is slightlty above 1e9 so this will make sure that on every liquidation atleast 1 partial gets liquidated
            // not sure if it can result in any issue but restricting amount further more to remove very low amount scenarios totally
            revert FluidVaultError(ErrorTypes.Vault__InvalidLiquidationAmt);
        }

        if (absorb_) {
            temp_ = absorbedLiquidity;
            // temp2_ -> absorbed col
            temp2_ = (temp_ >> 128) & X128;
            // temp_ -> absorbed debt
            temp_ = temp_ & X128;

            if (temp_ > currentData_.debtRemaining) {
                // Removing collateral in equal proportion as debt
                currentData_.totalColLiq = ((temp2_ * currentData_.debtRemaining) / temp_);
                temp2_ -= currentData_.totalColLiq;
                // Removing debt
                currentData_.totalDebtLiq = currentData_.debtRemaining;
                unchecked {
                    temp_ -= currentData_.debtRemaining;
                }
                currentData_.debtRemaining = 0;

                // updating on storage
                absorbedLiquidity = temp_ | (temp2_ << 128);
            } else {
                // updating on storage
                absorbedLiquidity = 0;
                unchecked {
                    currentData_.debtRemaining -= temp_;
                }
                currentData_.totalDebtLiq = temp_;
                currentData_.totalColLiq = temp2_;
            }
        }

        // current tick should be greater than liquidationTick and it cannot be greater than maxTick as absorb will run
        if (currentData_.tick > memoryVars_.liquidationTick) {
            if (currentData_.debtRemaining > 0) {
                // Stores liquidated debt & collateral in each loop
                uint debtLiquidated_;
                uint colLiquidated_;
                uint debtFactor_ = BigMathVault.TWO_POWER_64;

                TickHasDebt memory tickHasDebt_;
                unchecked {
                    tickHasDebt_.mapId = (currentData_.tick < 0)
                        ? (((currentData_.tick + 1) / 256) - 1)
                        : (currentData_.tick / 256);
                }

                tickInfo_.ratio = TickMath.getRatioAtTick(tickInfo_.tick);

                if (currentData_.tickStatus == 1) {
                    // top tick is not liquidated. Hence it's a perfect tick.
                    currentData_.ratio = tickInfo_.ratio;
                    // if current tick in liquidation is a perfect tick then it is also the next tick that has debt.
                    tickHasDebt_.nextTick = currentData_.tick;
                } else {
                    // top tick is liquidated. Hence it has partials.
                    // next tick that has debt liquidity will have to be fetched from tickHasDebt
                    unchecked {
                        tickInfo_.ratioOneLess = (tickInfo_.ratio * 10000) / 10015;
                        tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
                        tickInfo_.partials = (branch_.data >> 22) & X30;
                        currentData_.ratio = tickInfo_.ratioOneLess + ((tickInfo_.length * tickInfo_.partials) / X30);

                        if ((memoryVars_.liquidationTick + 1) == tickInfo_.tick && (tickInfo_.partials == 1)) {
                            if (to_ == DEAD_ADDRESS) {
                                // revert with liquidated amounts if to_ address is the dead address.
                                // this can be used in a resolver to find the max liquidatable amounts.
                                revert FluidLiquidateResult(0, 0);
                            }
                            revert FluidVaultError(ErrorTypes.Vault__InvalidLiquidation);
                        }
                    }
                }

                while (true) {
                    if (currentData_.tickStatus == 1) {
                        // not liquidated -> Getting the debt from tick data itself
                        temp2_ = tickData[currentData_.tick];
                        // temp_ => tick debt
                        temp_ = (temp2_ >> 25) & X64;
                        // Converting big number into normal number
                        temp_ = (temp_ >> 8) << (temp_ & X8);
                        // Updating tickData on storage with removing debt & adding connection to branch
                        tickData[currentData_.tick] =
                            1 | // set tick as liquidated
                            (temp2_ & 0x1fffffe) | // set same total tick ids
                            (branch_.id << 26) | // branch id where this tick got liquidated
                            (branch_.debtFactor << 56);
                    } else {
                        // already liquidated -> Get the debt from branch data in big number
                        // temp_ => tick debt
                        temp_ = (branch_.data >> 52) & X64;
                        // Converting big number into normal number
                        temp_ = (temp_ >> 8) << (temp_ & X8);
                        // Branch is getting updated over the end
                    }

                    // Adding new debt into active debt for liquidation
                    currentData_.debt += temp_;

                    // Adding new col into active col for liquidation
                    // Ratio is in 2**96 decimals hence multiplying debt with 2**96 to get proper collateral
                    currentData_.col += (temp_ * TickMath.ZERO_TICK_SCALED_RATIO) / currentData_.ratio;

                    if (
                        (tickHasDebt_.nextTick == currentData_.tick && currentData_.tickStatus == 1) ||
                        tickHasDebt_.tickHasDebt == 0
                    ) {
                        // Fetching next perfect tick with liquidity
                        // tickHasDebt_.tickHasDebt == 0 will only happen in the first while loop
                        // in the very first perfect tick liquidation it'll be 0
                        if (tickHasDebt_.tickHasDebt == 0) {
                            tickHasDebt_.tickHasDebt = tickHasDebt[tickHasDebt_.mapId];
                        }

                        // in 1st loop tickStatus can be 2. Meaning not a perfect current tick
                        if (currentData_.tickStatus == 1) {
                            unchecked {
                                tickHasDebt_.bitsToRemove = uint(-currentData_.tick + (tickHasDebt_.mapId * 256 + 256));
                            }
                            // Removing current top tick from tickHasDebt
                            tickHasDebt_.tickHasDebt =
                                (tickHasDebt_.tickHasDebt << tickHasDebt_.bitsToRemove) >>
                                tickHasDebt_.bitsToRemove;
                            // Updating in storage if tickHasDebt becomes 0.
                            if (tickHasDebt_.tickHasDebt == 0) {
                                tickHasDebt[tickHasDebt_.mapId] = 0;
                            }
                        }

                        // For last user remaining in vault there could be a lot of while loop.
                        // Chances of this to happen is extremely low (like ~0%)
                        while (true) {
                            if (tickHasDebt_.tickHasDebt > 0) {
                                unchecked {
                                    tickHasDebt_.nextTick =
                                        tickHasDebt_.mapId *
                                        256 +
                                        int(tickHasDebt_.tickHasDebt.mostSignificantBit()) -
                                        1;
                                }
                                break;
                            }

                            // tickHasDebt_.tickHasDebt == 0. Checking if minimum tick of this mapID is less than liquidationTick_
                            // if true that means now the next tick is not needed as liquidation gets over minimum at liquidationTick_
                            unchecked {
                                if ((tickHasDebt_.mapId * 256) < memoryVars_.liquidationTick) {
                                    tickHasDebt_.nextTick = type(int).min;
                                    break;
                                }

                                // Fetching next tick has debt by decreasing tickHasDebt_.mapId first
                                tickHasDebt_.tickHasDebt = tickHasDebt[--tickHasDebt_.mapId];
                            }
                        }
                    }

                    // Fetching refTick. refTick is the biggest tick of these 3:
                    // 1. Next tick with liquidity (from tickHasDebt)
                    // 2. Minima tick of current branch
                    // 3. Liquidation threshold tick
                    {
                        // Setting currentData_.refTick & currentData_.refTickStatus
                        if (
                            branch_.minimaTick > tickHasDebt_.nextTick &&
                            branch_.minimaTick > memoryVars_.liquidationTick
                        ) {
                            // next tick will be of base branch (merge)
                            currentData_.refTick = branch_.minimaTick;
                            currentData_.refTickStatus = 2;
                        } else if (tickHasDebt_.nextTick > memoryVars_.liquidationTick) {
                            // next tick will be next tick from perfect tick
                            currentData_.refTick = tickHasDebt_.nextTick;
                            currentData_.refTickStatus = 1;
                        } else {
                            // next tick is threshold tick
                            currentData_.refTick = memoryVars_.liquidationTick;
                            currentData_.refTickStatus = 3; // leads to end of liquidation loop
                        }
                    }

                    // using tickInfo variable again for ref tick as we don't have the need for it any more
                    tickInfo_.ratio = TickMath.getRatioAtTick(int24(currentData_.refTick));
                    if (currentData_.refTickStatus == 2) {
                        // merge current branch with base branch
                        unchecked {
                            tickInfo_.ratioOneLess = (tickInfo_.ratio * 10000) / 10015;
                            tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
                            // Fetching base branch data to get the base branch's partial
                            branch_.baseBranchData = branchData[((branch_.data >> 166) & X30)];
                            tickInfo_.partials = (branch_.baseBranchData >> 22) & X30;
                            tickInfo_.currentRatio =
                                tickInfo_.ratioOneLess +
                                ((tickInfo_.length * tickInfo_.partials) / X30);
                            currentData_.refRatio = tickInfo_.currentRatio;
                        }
                    } else {
                        // refTickStatus can only be 1 (next tick from perfect tick) or 3 (liquidation threshold tick)
                        tickInfo_.currentRatio = tickInfo_.ratio;
                        currentData_.refRatio = tickInfo_.ratio;
                        tickInfo_.partials = X30;
                    }

                    // Formula: (debt_ - x) / (col_ - (x * colPerDebt_)) = ratioEnd_
                    // x = ((ratioEnd_ * col) - debt_) / ((colPerDebt_ * ratioEnd_) - 1)
                    // x is debtToLiquidate_
                    // col_ = debt_ / ratioStart_ -> (currentData_.debt / currentData_.ratio)
                    // ratioEnd_ is currentData_.refRatio
                    //
                    // Calculation results of numerator & denominator is always negative
                    // which will cancel out to give positive output in the end so we can safely cast to uint.
                    // for nominator:
                    // ratioStart can only be >= ratioEnd so first part can only be reducing currentData_.debt leading to
                    // currentData_.debt reduced - currentData_.debt original * 1e27 -> can only be a negative number
                    // for denominator:
                    // currentData_.colPerDebt and currentData_.refRatio are inversely proportional to each other.
                    // the maximum value they can ever be is ~9.97e26 which is the 0.3% away from 100% because liquidation
                    // threshold + liquidation penalty can never be > 99.7%. This can also be verified by going back from
                    // min / max ratio values further up where we fetch oracle price etc.
                    // as optimization we can inverse nominator and denominator subtraction to directly get a positive number.

                    debtLiquidated_ =
                        // nominator
                        ((currentData_.debt - (currentData_.refRatio * currentData_.debt) / currentData_.ratio) *
                            1e27) /
                        // denominator
                        (1e27 - ((currentData_.colPerDebt * currentData_.refRatio) / TickMath.ZERO_TICK_SCALED_RATIO));

                    colLiquidated_ = (debtLiquidated_ * currentData_.colPerDebt) / 1e27;

                    if (currentData_.debt == debtLiquidated_) {
                        debtLiquidated_ -= 1;
                    }

                    if (debtLiquidated_ >= currentData_.debtRemaining || currentData_.refTickStatus == 3) {
                        // End of liquidation as full amount to liquidate or liquidation threshold tick has been reached;

                        // Updating tickHasDebt on storage.
                        tickHasDebt[tickHasDebt_.mapId] = tickHasDebt_.tickHasDebt;

                        if (debtLiquidated_ >= currentData_.debtRemaining) {
                            // Liquidation ended between currentTick & refTick.
                            // Not all of liquidatable debt is actually liquidated -> recalculate
                            debtLiquidated_ = currentData_.debtRemaining;
                            colLiquidated_ = (debtLiquidated_ * currentData_.colPerDebt) / 1e27;
                            // Liquidating to debt. temp_ => final ratio after liquidation
                            // liquidatable debt - debtLiquidated / liquidatable col - colLiquidated
                            temp_ =
                                ((currentData_.debt - debtLiquidated_) * TickMath.ZERO_TICK_SCALED_RATIO) /
                                (currentData_.col - colLiquidated_);
                            // Fetching tick of where liquidation ended
                            (tickInfo_.tick, tickInfo_.ratioOneLess) = TickMath.getTickAtRatio(temp_);
                            if ((tickInfo_.tick < currentData_.refTick) && (tickInfo_.partials == X30)) {
                                // this situation might never happen
                                // if this happens then there might be some very edge case precision of few weis which is returning 1 tick less
                                // if the above were to ever happen then tickInfo_.tick only be currentData_.refTick - 1
                                // in this case the partial will be very very near to full (X30)
                                // increasing tick by 2 and making partial as 1 which is basically very very near to currentData_.refTick
                                unchecked {
                                    tickInfo_.tick += 2;
                                }
                                tickInfo_.partials = 1;
                            } else {
                                unchecked {
                                    // Increasing tick by 1 as final ratio will probably be a partial
                                    ++tickInfo_.tick;

                                    // if ref tick is old liquidated tick then storing partials in temp2_
                                    // tickInfo_.partials contains partial of branch which is the current ref tick
                                    temp2_ = (currentData_.refTickStatus == 2 && tickInfo_.tick == currentData_.refTick)
                                        ? tickInfo_.partials
                                        : 0;

                                    tickInfo_.ratio = (tickInfo_.ratioOneLess * 10015) / 10000;
                                    tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
                                    tickInfo_.partials = ((temp_ - tickInfo_.ratioOneLess) * X30) / tickInfo_.length;

                                    // Taking edge cases where partial comes as 0 or X30 meaning perfect tick.
                                    // Hence, increasing or reducing it by 1 as liquidation tick cannot be perfect tick.
                                    tickInfo_.partials = tickInfo_.partials == 0
                                        ? 1
                                        : tickInfo_.partials >= X30
                                            ? X30 - 1
                                            : tickInfo_.partials;
                                }
                                if (temp2_ > 0 && temp2_ >= tickInfo_.partials) {
                                    // if refTick is liquidated tick and hence contains partials then checking that
                                    // current liquidation tick's partial should not be less than last liquidation refTick

                                    // not sure if this is even possible to happen but adding checks to avoid it fully
                                    // if it reverts here then next liquidation on next block should go through fine
                                    revert FluidVaultError(ErrorTypes.Vault__LiquidationReverts);
                                }
                            }
                        } else {
                            // End in liquidation threshold.
                            // finalRatio_ = currentData_.refRatio;
                            // Increasing liquidation threshold tick by 1 partial. With 1 partial it'll reach to the next tick.
                            // Ratio change will be negligible. Doing this as liquidation threshold tick can also be a perfect non-liquidated tick.
                            unchecked {
                                tickInfo_.tick = currentData_.refTick + 1;
                            }
                            // Making partial as 1 so it doesn't stay perfect tick
                            tickInfo_.partials = 1;
                            // length is not needed as only partials are written to storage
                        }

                        // debtFactor = debtFactor * (liquidatableDebt - debtLiquidated) / liquidatableDebt
                        // -> debtFactor * leftOverDebt / liquidatableDebt
                        debtFactor_ = (debtFactor_ * (currentData_.debt - debtLiquidated_)) / currentData_.debt;
                        currentData_.totalDebtLiq += debtLiquidated_;
                        currentData_.debt -= debtLiquidated_; // currentData_.debt => leftOverDebt after debtLiquidated_
                        currentData_.totalColLiq += colLiquidated_;
                        currentData_.col -= colLiquidated_; // currentData_.col => leftOverCol after colLiquidated_

                        // Updating branch's debt factor & write to storage as liquidation is over
                        branch_.debtFactor = branch_.debtFactor.mulDivBigNumber(debtFactor_);

                        if (currentData_.debt < 100) {
                            // this can happen when someone tries to create a dust tick
                            revert FluidVaultError(ErrorTypes.Vault__BranchDebtTooLow);
                        }

                        unchecked {
                            // Tick to insert
                            temp2_ = tickInfo_.tick < 0
                                ? (uint(-tickInfo_.tick) << 1)
                                : ((uint(tickInfo_.tick) << 1) | 1);
                        }

                        // Updating Branch data with debt factor, debt, partials, minima tick & assigning is liquidated
                        branchData[branch_.id] =
                            ((branch_.data >> 166) << 166) |
                            1 | // set as liquidated
                            (temp2_ << 2) | // minima tick of branch
                            (tickInfo_.partials << 22) |
                            (currentData_.debt.toBigNumber(56, 8, BigMathMinified.ROUND_UP) << 52) | // branch debt
                            (branch_.debtFactor << 116);

                        // Updating vault variables with current branch & tick
                        vaultVariables_ =
                            ((vaultVariables_ >> 52) << 52) |
                            2 | // set as liquidated
                            (temp2_ << 2) | // top tick
                            (branch_.id << 22);
                        break;
                    }

                    unchecked {
                        // debtLiquidated_ >= currentData_.debtRemaining leads to loop break in if statement above
                        // so this can be unchecked
                        currentData_.debtRemaining -= debtLiquidated_;
                    }

                    // debtFactor = debtFactor * (liquidatableDebt - debtLiquidated) / liquidatableDebt
                    // -> debtFactor * leftOverDebt / liquidatableDebt
                    debtFactor_ = (debtFactor_ * (currentData_.debt - debtLiquidated_)) / currentData_.debt;
                    currentData_.totalDebtLiq += debtLiquidated_;
                    currentData_.debt -= debtLiquidated_;
                    currentData_.totalColLiq += colLiquidated_;
                    currentData_.col -= colLiquidated_;

                    // updating branch's debt factor
                    branch_.debtFactor = branch_.debtFactor.mulDivBigNumber(debtFactor_);
                    // Setting debt factor as 1 << 64 again
                    debtFactor_ = BigMathVault.TWO_POWER_64;

                    if (currentData_.refTickStatus == 2) {
                        // ref tick is base branch's minima hence merging current branch to base branch
                        // and making base branch as current branch.

                        // read base branch related data
                        temp_ = (branch_.data >> 166) & X30; // temp_ -> base branch id
                        temp2_ = branch_.baseBranchData;
                        {
                            uint newBranchDebtFactor_ = (temp2_ >> 116) & X50;

                            // connectionFactor_ = baseBranchDebtFactor / currentBranchDebtFactor
                            uint connectionFactor_ = newBranchDebtFactor_.divBigNumber(branch_.debtFactor);
                            // Updating current branch in storage
                            branchData[branch_.id] =
                                ((branch_.data >> 166) << 166) | // deleting debt / partials / minima tick
                                2 | // setting as merged
                                (connectionFactor_ << 116); // set new connectionFactor

                            // Storing base branch in memory
                            // Updating branch ID to base branch ID
                            branch_.id = temp_;
                            // Updating branch data with base branch data
                            branch_.data = temp2_;
                            // Remove next branch connection from base branch
                            branch_.debtFactor = newBranchDebtFactor_;
                            // temp_ => minima tick of base branch
                            temp_ = (temp2_ >> 196) & X20;
                            if (temp_ > 0) {
                                unchecked {
                                    branch_.minimaTick = (temp_ & 1) == 1
                                        ? int256((temp_ >> 1) & X19)
                                        : -int256((temp_ >> 1) & X19);
                                }
                            } else {
                                branch_.minimaTick = type(int).min;
                            }
                        }
                    }

                    // Making refTick as currentTick
                    currentData_.tick = currentData_.refTick;
                    currentData_.tickStatus = currentData_.refTickStatus;
                    currentData_.ratio = currentData_.refRatio;
                }
            }
        }

        // calculating net token amounts using exchange price
        memoryVars_.actualDebtAmt = (currentData_.totalDebtLiq * memoryVars_.borrowExPrice) / EXCHANGE_PRICES_PRECISION;
        memoryVars_.actualColAmt = (currentData_.totalColLiq * memoryVars_.supplyExPrice) / EXCHANGE_PRICES_PRECISION;

        // Chances of this to happen are in few wei
        if (memoryVars_.actualDebtAmt > debtAmt_) {
            // calc new memoryVars_.actualColAmt via ratio.
            memoryVars_.actualColAmt = memoryVars_.actualColAmt * (debtAmt_ / memoryVars_.actualDebtAmt);
            memoryVars_.actualDebtAmt = debtAmt_;
        }

        if (memoryVars_.actualDebtAmt == 0) {
            revert FluidVaultError(ErrorTypes.Vault__InvalidLiquidation);
        }

        if (((memoryVars_.actualColAmt * 1e18) / memoryVars_.actualDebtAmt) < colPerUnitDebt_) {
            revert FluidVaultError(ErrorTypes.Vault__ExcessSlippageLiquidation);
        }

        if (to_ == DEAD_ADDRESS) {
            // revert with liquidated amounts if to_ address is the dead address.
            // this can be used in a resolver to find the max liquidatable amounts.
            revert FluidLiquidateResult(memoryVars_.actualColAmt, memoryVars_.actualDebtAmt);
        }

        if (
            !(TYPE == FluidProtocolTypes.VAULT_T3_SMART_DEBT_TYPE ||
                TYPE == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE)
        ) {
            // payback at Liquidity
            if (BORROW_TOKEN == NATIVE_TOKEN) {
                temp_ = memoryVars_.actualDebtAmt;
            } else {
                temp_ = 0;
            }

            // payback at liquidity
            LIQUIDITY.operate{ value: temp_ }(
                BORROW_TOKEN,
                0,
                -int(memoryVars_.actualDebtAmt),
                address(0),
                address(0),
                abi.encode(msg.sender)
            );
        }

        if (
            !(TYPE == FluidProtocolTypes.VAULT_T2_SMART_COL_TYPE ||
                TYPE == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE)
        ) {
            // withdraw at liquidity
            LIQUIDITY.operate(SUPPLY_TOKEN, -int(memoryVars_.actualColAmt), 0, to_, address(0), new bytes(0));
        }

        // Calculating new total collateral & total debt.
        // temp_ -> total supply
        temp_ = (vaultVariables_ >> 82) & X64;
        temp_ = ((temp_ >> 8) << (temp_ & X8)) - currentData_.totalColLiq;
        // temp2_ -> total borrow
        temp2_ = (vaultVariables_ >> 146) & X64;
        temp2_ = ((temp2_ >> 8) << (temp2_ & X8)) - currentData_.totalDebtLiq;
        // Updating vault variables on storage
        // Converting total supply & total borrow in 64 bits (56 | 8) bignumber
        vaultVariables_ =
            (vaultVariables_ & 0xfffffffffffc00000000000000000000000000000003ffffffffffffffffffff) |
            (temp_.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 82) | // total supply
            (temp2_.toBigNumber(56, 8, BigMathMinified.ROUND_UP) << 146); // total borrow

        emit LogLiquidate(msg.sender, memoryVars_.actualColAmt, memoryVars_.actualDebtAmt, to_);

        return abi.encode(memoryVars_.actualDebtAmt, memoryVars_.actualColAmt, vaultVariables_);
    }

    /// @dev Checks total supply of vault's in Liquidity Layer & Vault contract and rebalance it accordingly
    /// if vault supply is more than LiquidityLayer/DEX then deposit difference through reserve/rebalance contract
    /// if vault supply is less than LiquidityLayer/DEX then withdraw difference to reserve/rebalance contract
    /// if vault borrow is more than LiquidityLayer/DEX then borrow difference to reserve/rebalance contract
    /// if vault borrow is less than LiquidityLayer/DEX then payback difference through reserve/rebalance contract
    function rebalance(int, int, int, int) external payable _dexFromAddress returns (int supplyAmt_, int borrowAmt_) {
        (supplyAmt_, borrowAmt_) = abi.decode(_spell(SECONDARY_IMPLEMENTATION, msg.data), (int, int));
    }

    /// @dev liquidity callback for cheaper token transfers in case of deposit or payback.
    /// only callable by Liquidity during an operation.
    function liquidityCallback(address token_, uint amount_, bytes calldata data_) external {
        if (msg.sender != address(LIQUIDITY)) {
            revert FluidVaultError(ErrorTypes.Vault__InvalidLiquidityCallbackAddress);
        }
        if (vaultVariables & 1 == 0) revert FluidVaultError(ErrorTypes.Vault__NotEntered);

        SafeTransfer.safeTransferFrom(token_, abi.decode(data_, (address)), address(LIQUIDITY), amount_);
    }

    /// @dev dex callback for cheaper token transfers in case of deposit or payback.
    /// only callable by dex during an operation.
    function dexCallback(address token_, uint amount_) external {
        if (!(msg.sender == address(SUPPLY) || msg.sender == address(BORROW))) {
            revert FluidVaultError(ErrorTypes.Vault__InvalidDexCallbackAddress);
        }
        if (vaultVariables & 1 == 0) revert FluidVaultError(ErrorTypes.Vault__NotEntered);

        SafeTransfer.safeTransferFrom(token_, dexFromAddress, address(LIQUIDITY), amount_);
    }

    /// @notice returns all Vault constants
    function constantsView() external view returns (ConstantViews memory constantsView_) {
        constantsView_.liquidity = address(LIQUIDITY);
        constantsView_.factory = address(VAULT_FACTORY);
        constantsView_.operateImplementation = OPERATE_IMPLEMENTATION;
        constantsView_.adminImplementation = ADMIN_IMPLEMENTATION;
        constantsView_.secondaryImplementation = SECONDARY_IMPLEMENTATION;
        constantsView_.deployer = DEPLOYER_CONTRACT;
        constantsView_.supply = address(SUPPLY);
        constantsView_.borrow = address(BORROW);
        constantsView_.supplyToken.token0 = SUPPLY_TOKEN0;
        constantsView_.supplyToken.token1 = SUPPLY_TOKEN1;
        constantsView_.borrowToken.token0 = BORROW_TOKEN0;
        constantsView_.borrowToken.token1 = BORROW_TOKEN1;
        constantsView_.vaultId = VAULT_ID;
        constantsView_.vaultType = TYPE;
        constantsView_.supplyExchangePriceSlot = SUPPLY_EXCHANGE_PRICE_SLOT;
        constantsView_.borrowExchangePriceSlot = BORROW_EXCHANGE_PRICE_SLOT;
        constantsView_.userSupplySlot = USER_SUPPLY_SLOT;
        constantsView_.userBorrowSlot = USER_BORROW_SLOT;
    }

    constructor(ConstantViews memory constants_) HelpersLiquidate(constants_) {
        // Note that vaults are deployed by VaultFactory so we somewhat trust the values being passed in

        // Setting branch in vault.
        vaultVariables = (vaultVariables) | (1 << 22) | (1 << 52);

        dexFromAddress = DEAD_ADDRESS;

        // If smart collateral then liqSupplyExchangePrice_ will always be EXCHANGE_PRICES_PRECISION
        uint liqSupplyExchangePrice_ = (constants_.vaultType == FluidProtocolTypes.VAULT_T2_SMART_COL_TYPE ||
            constants_.vaultType == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE)
            ? EXCHANGE_PRICES_PRECISION
            : ((SUPPLY.readFromStorage(SUPPLY_EXCHANGE_PRICE_SLOT) >>
                LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) & X64);

        // If smart debt then liqBorrowExchangePrice_ will always be EXCHANGE_PRICES_PRECISION
        uint liqBorrowExchangePrice_ = (constants_.vaultType == FluidProtocolTypes.VAULT_T3_SMART_DEBT_TYPE ||
            constants_.vaultType == FluidProtocolTypes.VAULT_T4_SMART_COL_SMART_DEBT_TYPE)
            ? EXCHANGE_PRICES_PRECISION
            : ((BORROW.readFromStorage(BORROW_EXCHANGE_PRICE_SLOT) >>
                LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) & X64);

        if (
            liqSupplyExchangePrice_ < EXCHANGE_PRICES_PRECISION || liqBorrowExchangePrice_ < EXCHANGE_PRICES_PRECISION
        ) {
            revert FluidVaultError(ErrorTypes.Vault__TokenNotInitialized);
        }

        if (constants_.operateImplementation == address(0)) {
            revert FluidVaultError(ErrorTypes.Vault__ImproperConstantsSetup);
        }

        // Updating initial rates in storage
        rates =
            liqSupplyExchangePrice_ |
            (liqBorrowExchangePrice_ << 64) |
            (EXCHANGE_PRICES_PRECISION << 128) |
            (EXCHANGE_PRICES_PRECISION << 192);

        vaultVariables2 =
            (vaultVariables2 & 0xfffffffffffffffffffffffff800000003ffffffffffffffffffffffffffffff) |
            (block.timestamp << 122);
    }

    fallback() external {
        if (!(VAULT_FACTORY.isGlobalAuth(msg.sender) || VAULT_FACTORY.isVaultAuth(address(this), msg.sender))) {
            revert FluidVaultError(ErrorTypes.Vault__NotAnAuth);
        }

        // Delegate the current call to `implementation`.
        // This does not return to its internall call site, it will return directly to the external caller.
        // solhint-disable-next-line no-inline-assembly
        _spell(ADMIN_IMPLEMENTATION, msg.data);
    }

    receive() external payable {}

    function _spell(address target_, bytes memory data_) internal returns (bytes memory response_) {
        assembly {
            let succeeded := delegatecall(gas(), target_, add(data_, 0x20), mload(data_), 0, 0)
            let size := returndatasize()

            response_ := mload(0x40)
            mstore(0x40, add(response_, and(add(add(size, 0x20), 0x1f), not(0x1f))))
            mstore(response_, size)
            returndatacopy(add(response_, 0x20), 0, size)

            switch iszero(succeeded)
            case 1 {
                // throw if delegatecall failed
                returndatacopy(0x00, 0x00, size)
                revert(0x00, size)
            }
        }
    }
}
structs.sol 126 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;

abstract contract Structs {
    // structs are used to mitigate Stack too deep errors

    struct OperateMemoryVars {
        // ## User's position before update ##
        uint oldColRaw;
        uint oldNetDebtRaw; // total debt - dust debt
        int oldTick;
        // ## User's position after update ##
        uint colRaw;
        uint debtRaw;
        uint dustDebtRaw;
        int tick;
        uint tickId;
        // others
        uint256 vaultVariables2;
        uint256 branchId;
        int256 topTick;
        uint liquidityExPrice;
        uint supplyExPrice;
        uint borrowExPrice;
        uint branchData;
        // user's supply slot data in liquidity
        uint userSupplyLiquidityData;
    }

    struct BranchData {
        uint id;
        uint data;
        uint ratio;
        uint debtFactor;
        int minimaTick;
        uint baseBranchData;
    }

    struct TickData {
        int tick;
        uint data;
        uint ratio;
        uint ratioOneLess;
        uint length;
        uint currentRatio; // current tick is ratio with partials.
        uint partials;
    }

    // note: All the below token amounts are in raw form.
    struct CurrentLiquidity {
        uint256 debtRemaining; // Debt remaining to liquidate
        uint256 debt; // Current liquidatable debt before reaching next check point
        uint256 col; // Calculate using debt & ratioCurrent
        uint256 colPerDebt; // How much collateral to liquidate per unit of Debt
        uint256 totalDebtLiq; // Total debt liquidated till now
        uint256 totalColLiq; // Total collateral liquidated till now
        int tick; // Current tick to liquidate
        uint ratio; // Current ratio to liquidate
        uint tickStatus; // if 1 then it's a perfect tick, if 2 that means it's a liquidated tick
        int refTick; // ref tick to liquidate
        uint refRatio; // ratio at ref tick
        uint refTickStatus; // if 1 then it's a perfect tick, if 2 that means it's a liquidated tick, if 3 that means it's a liquidation threshold
    }

    struct TickHasDebt {
        int tick; // current tick
        int nextTick; // next tick with liquidity
        int mapId; // mapping ID of tickHasDebt
        uint bitsToRemove; // liquidity to remove till tick_ so we can search for next tick
        uint tickHasDebt; // getting tickHasDebt_ from tickHasDebt[mapId_]
        uint mostSigBit; // most significant bit in tickHasDebt_ to get the next tick
    }

    struct LiquidateMemoryVars {
        uint256 vaultVariables2;
        int liquidationTick;
        int maxTick;
        uint256 supplyExPrice;
        uint256 borrowExPrice;
        uint256 actualDebtAmt;
        uint256 actualColAmt;
    }

    struct AbsorbMemoryVariables {
        uint256 debtAbsorbed;
        uint256 colAbsorbed;
        int256 startingTick;
        uint256 mostSigBit;
    }

    struct Tokens {
        address token0;
        address token1;
    }

    struct ConstantViews {
        address liquidity;
        address factory;
        address operateImplementation;
        address adminImplementation;
        address secondaryImplementation;
        address deployer; // address which deploys oracle
        address supply; // either liquidity layer or DEX protocol
        address borrow; // either liquidity layer or DEX protocol
        Tokens supplyToken; // if smart collateral then address of token0 & token1 else just supply token address at token0 and token1 as empty
        Tokens borrowToken; // if smart debt then address of token0 & token1 else just borrow token address at token0 and token1 as empty
        uint256 vaultId;
        uint256 vaultType;
        bytes32 supplyExchangePriceSlot; // if smart collateral then slot is from DEX protocol else from liquidity layer
        bytes32 borrowExchangePriceSlot; // if smart debt then slot is from DEX protocol else from liquidity layer
        bytes32 userSupplySlot; // if smart collateral then slot is from DEX protocol else from liquidity layer
        bytes32 userBorrowSlot; // if smart debt then slot is from DEX protocol else from liquidity layer
    }

    struct RebalanceMemoryVariables {
        uint256 liqSupplyExPrice;
        uint256 liqBorrowExPrice;
        uint256 vaultSupplyExPrice;
        uint256 vaultBorrowExPrice;
        uint256 totalSupply;
        uint256 totalBorrow;
        uint256 totalSupplyVault;
        uint256 totalBorrowVault;
        uint256 initialEth;
    }
}

Read Contract

LIQUIDITY 0x2861c7d1 → address
TYPE 0xbb24fe8a → uint256
VAULT_FACTORY 0x103f2907 → address
VAULT_ID 0x540acabc → uint256
constantsView 0xb7791bf2 → tuple
fetchLatestPosition 0x22348cc7 → int256, uint256, uint256, uint256, uint256
readFromStorage 0xb5c736e4 → uint256
updateExchangePrices 0x09f0d8cb → uint256, uint256, uint256, uint256

Write Contract 9 functions

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

dexCallback 0x9410ae88
address token_
uint256 amount_
liquidate 0x7bae3361
uint256 token0DebtAmt_
uint256 token1DebtAmt_
uint256 debtSharesMin_
uint256 colPerUnitDebt_
address to_
bool absorb_
returns: uint256, uint256
liquidatePerfect 0xf4e7bfd1
uint256 debtShares_
uint256 token0DebtAmtPerUnitShares_
uint256 token1DebtAmtPerUnitShares_
uint256 colPerUnitDebt_
address to_
bool absorb_
returns: uint256, uint256, uint256, uint256
liquidityCallback 0xad207501
address token_
uint256 amount_
bytes data_
operate 0x10259f26
uint256 nftId_
int256 newCol_
int256 newDebtToken0_
int256 newDebtToken1_
int256 debtSharesMinMax_
address to_
returns: uint256, int256, int256
operatePerfect 0x0931bf2d
uint256 nftId_
int256 newCol_
int256 perfectDebtShares_
int256 debtToken0MinMax_
int256 debtToken1MinMax_
address to_
returns: uint256, int256[]
rebalance 0x1593a34b
int256
int256
int256
int256
returns: int256, int256
simulateLiquidate 0x3202937e
uint256 debtAmt_
bool absorb_
updateExchangePricesOnStorage 0x02161887
No parameters
returns: uint256, uint256, uint256, uint256

Recent Transactions

No transactions found for this address